From a6c425460f03421efc6f0345f05d85b7729f4506 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 02:25:05 -0700 Subject: [PATCH 1/7] Start work on assessments screen --- ios/Podfile.lock | 33 ++++++----- lib/src/app.dart | 18 ++++-- lib/src/blocs/checkup/checkup_bloc.dart | 59 +++++++++++++++---- lib/src/blocs/checkup/checkup_state.dart | 12 +++- lib/src/data/models/assessments.dart | 22 +++++++ lib/src/data/models/assessments.g.dart | 22 +++++++ lib/src/data/models/checkups.dart | 2 +- lib/src/data/repositories/checkups.dart | 26 ++++++++ lib/src/ui/router.dart | 3 + lib/src/ui/screens/checkup/assessment.dart | 14 +++++ lib/src/ui/screens/checkup/checkup.dart | 47 +++++++-------- .../checkup/checkup_completing_hud.dart | 23 ++++++++ .../screens/checkup/checkup_loaded_body.dart | 2 +- .../screens/checkup/checkup_progress_bar.dart | 24 ++++++-- pubspec.lock | 7 +++ pubspec.yaml | 1 + 16 files changed, 247 insertions(+), 68 deletions(-) create mode 100644 lib/src/data/models/assessments.dart create mode 100644 lib/src/data/models/assessments.g.dart create mode 100644 lib/src/data/repositories/checkups.dart create mode 100644 lib/src/ui/screens/checkup/assessment.dart create mode 100644 lib/src/ui/screens/checkup/checkup_completing_hud.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c44bc95..4c964be 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,16 +1,15 @@ PODS: - Flutter (1.0.0) - - FMDB/SQLCipher (2.7.5): - - SQLCipher - - moor_ffi (0.0.1): + - geolocator (5.3.0): + - Flutter + - google_api_availability (2.0.3): + - Flutter + - location_permissions (2.0.5): - Flutter - path_provider (0.0.1): - Flutter - path_provider_macos (0.0.1): - Flutter - - sqflite (0.0.1): - - Flutter - - FMDB/SQLCipher (~> 2.7.5) - SQLCipher (4.1.0): - SQLCipher/standard (= 4.1.0) - SQLCipher/common (4.1.0) @@ -19,36 +18,38 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) - - moor_ffi (from `.symlinks/plugins/moor_ffi/ios`) + - geolocator (from `.symlinks/plugins/geolocator/ios`) + - google_api_availability (from `.symlinks/plugins/google_api_availability/ios`) + - location_permissions (from `.symlinks/plugins/location_permissions/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) - - sqflite (from `.symlinks/plugins/sqflite/ios`) - SQLCipher (~> 4.1.0) SPEC REPOS: trunk: - - FMDB - SQLCipher EXTERNAL SOURCES: Flutter: :path: Flutter - moor_ffi: - :path: ".symlinks/plugins/moor_ffi/ios" + geolocator: + :path: ".symlinks/plugins/geolocator/ios" + google_api_availability: + :path: ".symlinks/plugins/google_api_availability/ios" + location_permissions: + :path: ".symlinks/plugins/location_permissions/ios" path_provider: :path: ".symlinks/plugins/path_provider/ios" path_provider_macos: :path: ".symlinks/plugins/path_provider_macos/ios" - sqflite: - :path: ".symlinks/plugins/sqflite/ios" SPEC CHECKSUMS: Flutter: 0e3d915762c693b495b44d77113d4970485de6ec - FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a - moor_ffi: d66c9470c18e9cb333423bbcb493c105c6c774c6 + geolocator: 7cdcf71180b80913b3cd84ab715d3b5365b378af + google_api_availability: 526574c9a5a0ae541e18c65f98e47afc11f53c8b + location_permissions: 4a49d4e5bec5b653643e551ab77963cc99bb0e4a path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 - sqflite: dc1dd7fd0c3603c33bd1c0b2e90d38182e3ddb37 SQLCipher: efbdb52cdbe340bcd892b1b14297df4e07241b7f PODFILE CHECKSUM: c1f56ec578e7dc933512aa9b9b86b80eb2a5b47d diff --git a/lib/src/app.dart b/lib/src/app.dart index b858655..430b3ed 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; +import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; import 'package:coronavirus_diary/src/blocs/questions/questions.dart'; +import 'package:coronavirus_diary/src/data/repositories/checkups.dart'; import 'package:coronavirus_diary/src/data/repositories/questions.dart'; import 'package:coronavirus_diary/src/ui/assets/theme.dart'; import 'package:coronavirus_diary/src/ui/router.dart'; @@ -27,11 +29,17 @@ class DiaryApp extends StatelessWidget { ], child: BlocBuilder( builder: (context, state) { - return MaterialApp( - title: 'Coronavirus Diary', - theme: appTheme, - routes: appRoutes, - initialRoute: initialRoute, + return BlocProvider( + create: (context) => CheckupBloc( + preferencesState: state, + checkupsRepository: CheckupsRepository(), + ), + child: MaterialApp( + title: 'Coronavirus Diary', + theme: appTheme, + routes: appRoutes, + initialRoute: initialRoute, + ), ); }, ), diff --git a/lib/src/blocs/checkup/checkup_bloc.dart b/lib/src/blocs/checkup/checkup_bloc.dart index 0fc8d03..ba01216 100644 --- a/lib/src/blocs/checkup/checkup_bloc.dart +++ b/lib/src/blocs/checkup/checkup_bloc.dart @@ -4,14 +4,20 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; import 'package:coronavirus_diary/src/data/models/checkups.dart'; +import 'package:coronavirus_diary/src/data/repositories/checkups.dart'; import 'checkup.dart'; +export 'package:coronavirus_diary/src/data/models/assessments.dart'; export 'package:coronavirus_diary/src/data/models/checkups.dart'; class CheckupBloc extends Bloc { final PreferencesState preferencesState; + final CheckupsRepository checkupsRepository; - CheckupBloc({this.preferencesState}); + CheckupBloc({ + this.preferencesState, + this.checkupsRepository, + }); @override CheckupState get initialState => CheckupStateNotCreated(); @@ -35,17 +41,21 @@ class CheckupBloc extends Bloc { } Stream _mapStartCheckupToState(StartCheckup event) async* { - // Create checkup using API yield CheckupStateCreating(); - yield CheckupStateInProgress( - checkup: Checkup( - userId: preferencesState.preferences.userId, - ), - ); + + // Create checkup using API + final Checkup newCheckup = await checkupsRepository.createCheckup(Checkup( + userId: preferencesState.preferences.userId, + )); + + yield CheckupStateInProgress(checkup: newCheckup); } Stream _mapUpdateLocalCheckupToState( UpdateLocalCheckup event) async* { + // Exit if checkup is not in progress + if (state is! CheckupStateInProgress) return; + // Don't send to API yet print(event.updatedCheckup); yield CheckupStateInProgress( @@ -55,13 +65,40 @@ class CheckupBloc extends Bloc { Stream _mapUpdateRemoteCheckupToState( UpdateRemoteCheckup event) async* { - // Patch checkup using API - print('Saving checkup to server'); + // Exit if checkup is not in progress + if (state is! CheckupStateInProgress) return; + + // Retrieve current checkup + final CheckupStateInProgress currentState = state; + final Checkup currentCheckup = currentState.checkup; + + // Patch checkup using API and return it (to handle server-side updates) + final Checkup checkup = + await checkupsRepository.updateCheckup(currentCheckup); + yield CheckupStateInProgress(checkup: checkup); } Stream _mapCompleteCheckupToState( CompleteCheckup event) async* { - // Patch checkup using API - yield CheckupStateCompleted(); + // Exit if checkup is not in progress + if (state is! CheckupStateInProgress) return; + + // Notify app that we are waiting to submit data + yield CheckupStateCompleting(); + + // Retrieve current checkup + final CheckupStateInProgress currentState = state; + final Checkup currentCheckup = currentState.checkup; + + // Make sure checkup is up to date on server + final Checkup checkup = + await checkupsRepository.updateCheckup(currentCheckup); + + // Complete checkup + final Assessment assessment = + await checkupsRepository.completeCheckup(checkup.id); + + // Complete checkup using API + yield CheckupStateCompleted(assessment: assessment); } } diff --git a/lib/src/blocs/checkup/checkup_state.dart b/lib/src/blocs/checkup/checkup_state.dart index 87e1c82..95aaa7e 100644 --- a/lib/src/blocs/checkup/checkup_state.dart +++ b/lib/src/blocs/checkup/checkup_state.dart @@ -1,3 +1,4 @@ +import 'package:coronavirus_diary/src/data/models/assessments.dart'; import 'package:coronavirus_diary/src/data/models/checkups.dart'; abstract class CheckupState { @@ -17,4 +18,13 @@ class CheckupStateInProgress extends CheckupState { String toString() => 'CheckupStateInProgress { checkup: $checkup }'; } -class CheckupStateCompleted extends CheckupState {} +class CheckupStateCompleting extends CheckupState {} + +class CheckupStateCompleted extends CheckupState { + final Assessment assessment; + + const CheckupStateCompleted({this.assessment}); + + @override + String toString() => 'CheckupStateCompleted { assessment: $assessment }'; +} diff --git a/lib/src/data/models/assessments.dart b/lib/src/data/models/assessments.dart new file mode 100644 index 0000000..59f627b --- /dev/null +++ b/lib/src/data/models/assessments.dart @@ -0,0 +1,22 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'assessments.g.dart'; + +@JsonSerializable() +class Assessment { + DateTime processed; + bool matchesPuiSymptoms; + + Assessment({ + this.processed, + this.matchesPuiSymptoms, + }); + + factory Assessment.fromJson(Map json) => + _$AssessmentFromJson(json); + Map toJson() => _$AssessmentToJson(this); + + @override + String toString() => + 'Assessment { processed: $processed, matchesPuiSymptoms: $matchesPuiSymptoms }'; +} diff --git a/lib/src/data/models/assessments.g.dart b/lib/src/data/models/assessments.g.dart new file mode 100644 index 0000000..db5ba8f --- /dev/null +++ b/lib/src/data/models/assessments.g.dart @@ -0,0 +1,22 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'assessments.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Assessment _$AssessmentFromJson(Map json) { + return Assessment( + processed: json['processed'] == null + ? null + : DateTime.parse(json['processed'] as String), + matchesPuiSymptoms: json['matches_pui_symptoms'] as bool, + ); +} + +Map _$AssessmentToJson(Assessment instance) => + { + 'processed': instance.processed?.toIso8601String(), + 'matches_pui_symptoms': instance.matchesPuiSymptoms, + }; diff --git a/lib/src/data/models/checkups.dart b/lib/src/data/models/checkups.dart index 5381267..a7c2d5f 100644 --- a/lib/src/data/models/checkups.dart +++ b/lib/src/data/models/checkups.dart @@ -30,7 +30,7 @@ class Checkup { @override String toString() => - 'Checkup { id: $id, created: $created, dataContributionPreference: $dataContributionPreference, ' + 'Checkup { id: $id, userId: $userId, created: $created, dataContributionPreference: $dataContributionPreference, ' 'location: $location, subjectiveResponses: $subjectiveResponses, vitalsResponses: $vitalsResponses }'; } diff --git a/lib/src/data/repositories/checkups.dart b/lib/src/data/repositories/checkups.dart new file mode 100644 index 0000000..6d85fc5 --- /dev/null +++ b/lib/src/data/repositories/checkups.dart @@ -0,0 +1,26 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:coronavirus_diary/src/data/models/assessments.dart'; +import 'package:coronavirus_diary/src/data/models/checkups.dart'; + +class CheckupsRepository { + Future createCheckup(Checkup checkup) async { + await sleep(Duration(seconds: 1)); + return checkup; + } + + Future updateCheckup(Checkup updatedCheckup) async { + await sleep(Duration(seconds: 1)); + return updatedCheckup; + } + + Future completeCheckup(String id) async { + await sleep(Duration(seconds: 1)); + return Assessment( + processed: DateTime.now(), + matchesPuiSymptoms: Random().nextBool(), + ); + } +} diff --git a/lib/src/ui/router.dart b/lib/src/ui/router.dart index b7c5f36..3c0baa7 100644 --- a/lib/src/ui/router.dart +++ b/lib/src/ui/router.dart @@ -1,12 +1,15 @@ +import 'screens/checkup/assessment.dart'; import 'screens/checkup/checkup.dart'; import 'screens/home/home.dart'; +export 'screens/checkup/assessment.dart'; export 'screens/checkup/checkup.dart'; export 'screens/home/home.dart'; var appRoutes = { HomeScreen.routeName: (context) => HomeScreen(), CheckupScreen.routeName: (context) => CheckupScreen(), + AssessmentScreen.routeName: (context) => AssessmentScreen(), }; const initialRoute = HomeScreen.routeName; diff --git a/lib/src/ui/screens/checkup/assessment.dart b/lib/src/ui/screens/checkup/assessment.dart new file mode 100644 index 0000000..f9ebb91 --- /dev/null +++ b/lib/src/ui/screens/checkup/assessment.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; + +class AssessmentScreen extends StatelessWidget { + static const routeName = '/checkup/assessment'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Your Personalized Assessment'), + ), + ); + } +} diff --git a/lib/src/ui/screens/checkup/checkup.dart b/lib/src/ui/screens/checkup/checkup.dart index 82c15e4..7d93a2c 100644 --- a/lib/src/ui/screens/checkup/checkup.dart +++ b/lib/src/ui/screens/checkup/checkup.dart @@ -3,35 +3,18 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; -import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; import 'package:coronavirus_diary/src/blocs/questions/questions.dart'; import 'package:coronavirus_diary/src/ui/widgets/loading_indicator.dart'; import 'checkup_loaded_body.dart'; -class CheckupScreen extends StatelessWidget { +class CheckupScreen extends StatefulWidget { static const routeName = '/checkup'; @override - Widget build(BuildContext context) { - // Initializing the bloc provider here so that the bloc is - // accessible to all functions in the checkup screen body - return BlocBuilder( - builder: (context, state) { - return BlocProvider( - create: (context) => CheckupBloc(preferencesState: state), - child: CheckupScreenBody(), - ); - }, - ); - } -} - -class CheckupScreenBody extends StatefulWidget { - @override - _CheckupScreenBodyState createState() => _CheckupScreenBodyState(); + _CheckupScreenState createState() => _CheckupScreenState(); } -class _CheckupScreenBodyState extends State { +class _CheckupScreenState extends State { // Storing the page controller at this level so that we can access it // across the entire checkup experience PageController _pageController; @@ -52,12 +35,12 @@ class _CheckupScreenBodyState extends State { CheckupState checkupState, QuestionsState questionsState, ) { - if (checkupState is CheckupStateNotCreated) { - context.bloc().add(StartCheckup()); - } if (questionsState is QuestionsStateNotLoaded) { context.bloc().add(LoadQuestions()); } + if (checkupState is CheckupStateNotCreated) { + context.bloc().add(StartCheckup()); + } return LoadingIndicator('Loading your health checkup'); } @@ -67,22 +50,32 @@ class _CheckupScreenBodyState extends State { if (state is QuestionsStateLoaded && state.questions.length == 0) { errorBody = Text( 'The checkup experience is not currently available. Please try again later.', + textAlign: TextAlign.center, ); } else { errorBody = Text( - 'There was an error retrieving the checkup experience. Please try again later.'); + 'There was an error retrieving the checkup experience. Please try again later.', + textAlign: TextAlign.center, + ); } - return errorBody; + return Center( + child: Padding( + padding: EdgeInsets.all(20), + child: errorBody, + ), + ); } Widget _getBody(CheckupState checkupState, QuestionsState questionsState) { if (questionsState is QuestionsStateNotLoaded || questionsState is QuestionsStateLoading || - checkupState is! CheckupStateInProgress) { + checkupState is CheckupStateNotCreated || + checkupState is CheckupStateCreating) { return _getUnloadedBody(checkupState, questionsState); } else if (questionsState is QuestionsStateLoaded && - questionsState.questions.length > 0) { + questionsState.questions.length > 0 && + checkupState is CheckupStateInProgress) { return CheckupLoadedBody(); } else { return _getErrorBody(questionsState); diff --git a/lib/src/ui/screens/checkup/checkup_completing_hud.dart b/lib/src/ui/screens/checkup/checkup_completing_hud.dart new file mode 100644 index 0000000..12297ac --- /dev/null +++ b/lib/src/ui/screens/checkup/checkup_completing_hud.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hud/flutter_hud.dart'; + +class CheckupCompletingHUD extends StatelessWidget { + final Widget Function(BuildContext) builder; + final bool show; + + const CheckupCompletingHUD({ + this.builder, + this.show, + }); + + @override + Widget build(BuildContext context) { + return WidgetHUD( + showHUD: show, + builder: builder, + hud: HUD( + label: 'Retrieving your assessment', + ), + ); + } +} diff --git a/lib/src/ui/screens/checkup/checkup_loaded_body.dart b/lib/src/ui/screens/checkup/checkup_loaded_body.dart index 89087a4..10b32bf 100644 --- a/lib/src/ui/screens/checkup/checkup_loaded_body.dart +++ b/lib/src/ui/screens/checkup/checkup_loaded_body.dart @@ -62,7 +62,7 @@ class _CheckupLoadedBodyState extends State { }); // Destination-specific actions - if (currentIndex > 1) { + if (currentIndex > 1 && currentIndex < steps.length - 1) { context.bloc().add(UpdateRemoteCheckup()); } } diff --git a/lib/src/ui/screens/checkup/checkup_progress_bar.dart b/lib/src/ui/screens/checkup/checkup_progress_bar.dart index a63a466..95da545 100644 --- a/lib/src/ui/screens/checkup/checkup_progress_bar.dart +++ b/lib/src/ui/screens/checkup/checkup_progress_bar.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; +import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; +import 'package:coronavirus_diary/src/ui/router.dart'; + class CheckupProgressBar extends StatelessWidget { final int currentIndex; final int stepsLength; @@ -10,6 +14,19 @@ class CheckupProgressBar extends StatelessWidget { this.stepsLength, }); + _handleNextButton(BuildContext context) { + bool isLastPage = currentIndex == stepsLength - 1; + if (isLastPage) { + context.bloc().add(CompleteCheckup()); + Navigator.pushNamed(context, AssessmentScreen.routeName); + } else { + Provider.of(context, listen: false).nextPage( + duration: Duration(milliseconds: 400), + curve: Curves.easeInOut, + ); + } + } + @override Widget build(BuildContext context) { double percentComplete = (currentIndex) / (stepsLength - 1); @@ -38,12 +55,7 @@ class CheckupProgressBar extends StatelessWidget { ), ), RaisedButton( - onPressed: () => - Provider.of(context, listen: false) - .nextPage( - duration: Duration(milliseconds: 400), - curve: Curves.easeInOut, - ), + onPressed: () => _handleNextButton(context), child: Text(isLastPage ? 'Submit' : 'Continue'), ), ], diff --git a/pubspec.lock b/pubspec.lock index 8fe50b0..936a05d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -188,6 +188,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.2.0" + flutter_hud: + dependency: "direct main" + description: + name: flutter_hud + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.1" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 1522e5f..7ff203a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: flutter: sdk: flutter flutter_bloc: ^3.2.0 + flutter_hud: ^0.2.1 flutter_xlider: ^3.2.0 font_awesome_flutter: ^8.7.0 geolocator: ^5.3.0 From a6e1158a1a2a9e90488c5a70587cb02ae9729e31 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 02:30:46 -0700 Subject: [PATCH 2/7] Remove questions state from checkup --- lib/src/app.dart | 2 +- lib/src/ui/screens/checkup/checkup.dart | 67 +++++++------------------ 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 430b3ed..3377f94 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -23,7 +23,7 @@ class DiaryApp extends StatelessWidget { create: (context) { return QuestionsBloc( questionsRepository: QuestionsRepository(), - ); + )..add(LoadQuestions()); }, ), ], diff --git a/lib/src/ui/screens/checkup/checkup.dart b/lib/src/ui/screens/checkup/checkup.dart index 7d93a2c..e90a490 100644 --- a/lib/src/ui/screens/checkup/checkup.dart +++ b/lib/src/ui/screens/checkup/checkup.dart @@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; -import 'package:coronavirus_diary/src/blocs/questions/questions.dart'; import 'package:coronavirus_diary/src/ui/widgets/loading_indicator.dart'; import 'checkup_loaded_body.dart'; @@ -33,52 +32,33 @@ class _CheckupScreenState extends State { Widget _getUnloadedBody( CheckupState checkupState, - QuestionsState questionsState, ) { - if (questionsState is QuestionsStateNotLoaded) { - context.bloc().add(LoadQuestions()); - } if (checkupState is CheckupStateNotCreated) { context.bloc().add(StartCheckup()); } return LoadingIndicator('Loading your health checkup'); } - Widget _getErrorBody(QuestionsState state) { - Widget errorBody; - - if (state is QuestionsStateLoaded && state.questions.length == 0) { - errorBody = Text( - 'The checkup experience is not currently available. Please try again later.', - textAlign: TextAlign.center, - ); - } else { - errorBody = Text( - 'There was an error retrieving the checkup experience. Please try again later.', - textAlign: TextAlign.center, - ); - } - + Widget _getErrorBody() { return Center( child: Padding( padding: EdgeInsets.all(20), - child: errorBody, + child: Text( + 'There was an error retrieving the checkup experience. Please try again later.', + textAlign: TextAlign.center, + ), ), ); } - Widget _getBody(CheckupState checkupState, QuestionsState questionsState) { - if (questionsState is QuestionsStateNotLoaded || - questionsState is QuestionsStateLoading || - checkupState is CheckupStateNotCreated || + Widget _getBody(CheckupState checkupState) { + if (checkupState is CheckupStateNotCreated || checkupState is CheckupStateCreating) { - return _getUnloadedBody(checkupState, questionsState); - } else if (questionsState is QuestionsStateLoaded && - questionsState.questions.length > 0 && - checkupState is CheckupStateInProgress) { + return _getUnloadedBody(checkupState); + } else if (checkupState is CheckupStateInProgress) { return CheckupLoadedBody(); } else { - return _getErrorBody(questionsState); + return _getErrorBody(); } } @@ -88,24 +68,15 @@ class _CheckupScreenState extends State { builder: (context, state) { final CheckupState checkupState = state; - return BlocBuilder( - builder: (context, state) { - final QuestionsState questionsState = state; - - return ChangeNotifierProvider( - create: (context) => _pageController, - child: Scaffold( - appBar: AppBar( - title: Text('Your Health Checkup'), - ), - backgroundColor: Theme.of(context).primaryColor, - body: _getBody( - checkupState, - questionsState, - ), - ), - ); - }, + return ChangeNotifierProvider( + create: (context) => _pageController, + child: Scaffold( + appBar: AppBar( + title: Text('Your Health Checkup'), + ), + backgroundColor: Theme.of(context).primaryColor, + body: _getBody(checkupState), + ), ); }, ); From 512283ec652588335702dc9ce5252d3a710cc535 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 16:33:07 -0700 Subject: [PATCH 3/7] Fix delay issues --- lib/src/data/repositories/checkups.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/data/repositories/checkups.dart b/lib/src/data/repositories/checkups.dart index 6d85fc5..ea2eefd 100644 --- a/lib/src/data/repositories/checkups.dart +++ b/lib/src/data/repositories/checkups.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math'; import 'package:coronavirus_diary/src/data/models/assessments.dart'; @@ -7,17 +6,17 @@ import 'package:coronavirus_diary/src/data/models/checkups.dart'; class CheckupsRepository { Future createCheckup(Checkup checkup) async { - await sleep(Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 1)); return checkup; } Future updateCheckup(Checkup updatedCheckup) async { - await sleep(Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 1)); return updatedCheckup; } Future completeCheckup(String id) async { - await sleep(Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 1)); return Assessment( processed: DateTime.now(), matchesPuiSymptoms: Random().nextBool(), From 4a811a1c0ec52256f68ffd0a5d31d5b8a87a1bb1 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 18:57:57 -0700 Subject: [PATCH 4/7] Add positive and negative assessment screens --- ios/Podfile.lock | 6 ++ lib/src/data/repositories/checkups.dart | 2 +- lib/src/ui/router.dart | 4 +- lib/src/ui/screens/assessment/assessment.dart | 44 ++++++++++++++ .../screens/assessment/assessments/index.dart | 2 + .../assessment/assessments/negative.dart | 60 +++++++++++++++++++ .../assessment/assessments/positive.dart | 59 ++++++++++++++++++ lib/src/ui/screens/assessment/share.dart | 59 ++++++++++++++++++ lib/src/ui/screens/checkup/assessment.dart | 14 ----- lib/src/ui/screens/checkup/checkup.dart | 4 ++ .../checkup/checkup_completing_hud.dart | 23 ------- .../screens/checkup/checkup_loaded_body.dart | 7 +-- .../screens/checkup/checkup_progress_bar.dart | 5 +- pubspec.lock | 7 +++ pubspec.yaml | 1 + 15 files changed, 248 insertions(+), 49 deletions(-) create mode 100644 lib/src/ui/screens/assessment/assessment.dart create mode 100644 lib/src/ui/screens/assessment/assessments/index.dart create mode 100644 lib/src/ui/screens/assessment/assessments/negative.dart create mode 100644 lib/src/ui/screens/assessment/assessments/positive.dart create mode 100644 lib/src/ui/screens/assessment/share.dart delete mode 100644 lib/src/ui/screens/checkup/assessment.dart delete mode 100644 lib/src/ui/screens/checkup/checkup_completing_hud.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4c964be..5bb832c 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -10,6 +10,8 @@ PODS: - Flutter - path_provider_macos (0.0.1): - Flutter + - share (0.5.2): + - Flutter - SQLCipher (4.1.0): - SQLCipher/standard (= 4.1.0) - SQLCipher/common (4.1.0) @@ -23,6 +25,7 @@ DEPENDENCIES: - location_permissions (from `.symlinks/plugins/location_permissions/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`) + - share (from `.symlinks/plugins/share/ios`) - SQLCipher (~> 4.1.0) SPEC REPOS: @@ -42,6 +45,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider/ios" path_provider_macos: :path: ".symlinks/plugins/path_provider_macos/ios" + share: + :path: ".symlinks/plugins/share/ios" SPEC CHECKSUMS: Flutter: 0e3d915762c693b495b44d77113d4970485de6ec @@ -50,6 +55,7 @@ SPEC CHECKSUMS: location_permissions: 4a49d4e5bec5b653643e551ab77963cc99bb0e4a path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0 + share: bae0a282aab4483288913fc4dc0b935d4b491f2e SQLCipher: efbdb52cdbe340bcd892b1b14297df4e07241b7f PODFILE CHECKSUM: c1f56ec578e7dc933512aa9b9b86b80eb2a5b47d diff --git a/lib/src/data/repositories/checkups.dart b/lib/src/data/repositories/checkups.dart index ea2eefd..bc64bc2 100644 --- a/lib/src/data/repositories/checkups.dart +++ b/lib/src/data/repositories/checkups.dart @@ -16,7 +16,7 @@ class CheckupsRepository { } Future completeCheckup(String id) async { - await Future.delayed(Duration(seconds: 1)); + await Future.delayed(Duration(seconds: 2)); return Assessment( processed: DateTime.now(), matchesPuiSymptoms: Random().nextBool(), diff --git a/lib/src/ui/router.dart b/lib/src/ui/router.dart index 3c0baa7..9b9b5ea 100644 --- a/lib/src/ui/router.dart +++ b/lib/src/ui/router.dart @@ -1,8 +1,8 @@ -import 'screens/checkup/assessment.dart'; +import 'screens/assessment/assessment.dart'; import 'screens/checkup/checkup.dart'; import 'screens/home/home.dart'; -export 'screens/checkup/assessment.dart'; +export 'screens/assessment/assessment.dart'; export 'screens/checkup/checkup.dart'; export 'screens/home/home.dart'; diff --git a/lib/src/ui/screens/assessment/assessment.dart b/lib/src/ui/screens/assessment/assessment.dart new file mode 100644 index 0000000..37bc37f --- /dev/null +++ b/lib/src/ui/screens/assessment/assessment.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_hud/flutter_hud.dart'; + +import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; +import 'assessments/index.dart'; +import 'share.dart'; + +class AssessmentScreen extends StatelessWidget { + static const routeName = '/assessment'; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + Widget body; + if (state is CheckupStateInProgress) { + context.bloc().add(CompleteCheckup()); + } else if (state is CheckupStateCompleted) { + switch (state.assessment.matchesPuiSymptoms) { + case true: + body = PositiveAssessment(); + break; + case false: + body = NegativeAssessment(); + break; + } + } + return WidgetHUD( + showHUD: state is CheckupStateCompleting, + hud: HUD(label: 'Loading your assessment'), + builder: (context) { + return Scaffold( + appBar: AppBar( + title: Text('Your Personalized Assessment'), + ), + body: body, + ); + }, + ); + }, + ); + } +} diff --git a/lib/src/ui/screens/assessment/assessments/index.dart b/lib/src/ui/screens/assessment/assessments/index.dart new file mode 100644 index 0000000..2452092 --- /dev/null +++ b/lib/src/ui/screens/assessment/assessments/index.dart @@ -0,0 +1,2 @@ +export 'positive.dart'; +export 'negative.dart'; diff --git a/lib/src/ui/screens/assessment/assessments/negative.dart b/lib/src/ui/screens/assessment/assessments/negative.dart new file mode 100644 index 0000000..f07260e --- /dev/null +++ b/lib/src/ui/screens/assessment/assessments/negative.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +import 'package:coronavirus_diary/src/ui/screens/assessment/share.dart'; + +class NegativeAssessment extends StatefulWidget { + @override + _NegativeAssessmentState createState() => _NegativeAssessmentState(); +} + +class _NegativeAssessmentState extends State { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(bottom: 10), + padding: EdgeInsets.symmetric(horizontal: 40), + child: FaIcon( + FontAwesomeIcons.solidSmile, + color: Colors.white, + size: 70, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 20), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + "You don't meet testing criteria", + style: Theme.of(context).textTheme.title, + textAlign: TextAlign.center, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 20), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + "If you continue to experience symptoms, please check in tomorrow. ", + style: Theme.of(context).textTheme.body2.copyWith(fontSize: 16), + textAlign: TextAlign.center, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 40), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + "If they become serious, please consult a physician.", + style: Theme.of(context).textTheme.body2.copyWith(fontSize: 16), + textAlign: TextAlign.center, + ), + ), + ShareApp(), + ], + ), + ); + } +} diff --git a/lib/src/ui/screens/assessment/assessments/positive.dart b/lib/src/ui/screens/assessment/assessments/positive.dart new file mode 100644 index 0000000..42df8bb --- /dev/null +++ b/lib/src/ui/screens/assessment/assessments/positive.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; + +import 'package:coronavirus_diary/src/ui/screens/assessment/share.dart'; + +class PositiveAssessment extends StatefulWidget { + @override + _PositiveAssessmentState createState() => _PositiveAssessmentState(); +} + +class _PositiveAssessmentState extends State { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + margin: EdgeInsets.only(bottom: 5), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + '🤔', + style: TextStyle(fontSize: 70), + ), + ), + Container( + margin: EdgeInsets.only(bottom: 20), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + "Please contact your physician", + style: Theme.of(context).textTheme.title, + textAlign: TextAlign.center, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 20), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + "You are showing symptoms that may be of concern. " + "Please limit your contact with other people until you have a chance to follow up with a physician", + style: Theme.of(context).textTheme.body2.copyWith(fontSize: 16), + textAlign: TextAlign.center, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 40), + padding: EdgeInsets.symmetric(horizontal: 40), + child: Text( + "Do not panic. This is only a preliminary assessment and not a formal medical diagnosis.", + style: Theme.of(context).textTheme.body2.copyWith(fontSize: 16), + textAlign: TextAlign.center, + ), + ), + ShareApp(), + ], + ), + ); + } +} diff --git a/lib/src/ui/screens/assessment/share.dart b/lib/src/ui/screens/assessment/share.dart new file mode 100644 index 0000000..901ab68 --- /dev/null +++ b/lib/src/ui/screens/assessment/share.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/fa_icon.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:share/share.dart'; + +class ShareApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + void _shareApp() { + Share.share( + 'Worried that you might have COVID-19? ' + 'Download this app to checkup on your health and support your community: APP_LINK', + ); + } + + return Container( + color: Colors.white.withOpacity(0.2), + padding: EdgeInsets.symmetric( + horizontal: 40, + vertical: 20, + ), + child: Column( + children: [ + Container( + margin: EdgeInsets.only(bottom: 10), + child: FaIcon( + FontAwesomeIcons.heartbeat, + color: Colors.red, + size: 40, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 10), + child: Text( + "Protect Your Community", + style: Theme.of(context).textTheme.title, + textAlign: TextAlign.center, + ), + ), + Container( + margin: EdgeInsets.only(bottom: 20), + child: Text( + "Share this app with your friends, coworkers, and family (especially grandparents).", + style: Theme.of(context).textTheme.body1.copyWith( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + RaisedButton( + onPressed: _shareApp, + child: Text('Share now'), + ), + ], + ), + ); + } +} diff --git a/lib/src/ui/screens/checkup/assessment.dart b/lib/src/ui/screens/checkup/assessment.dart deleted file mode 100644 index f9ebb91..0000000 --- a/lib/src/ui/screens/checkup/assessment.dart +++ /dev/null @@ -1,14 +0,0 @@ -import 'package:flutter/material.dart'; - -class AssessmentScreen extends StatelessWidget { - static const routeName = '/checkup/assessment'; - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Your Personalized Assessment'), - ), - ); - } -} diff --git a/lib/src/ui/screens/checkup/checkup.dart b/lib/src/ui/screens/checkup/checkup.dart index e90a490..6e58683 100644 --- a/lib/src/ui/screens/checkup/checkup.dart +++ b/lib/src/ui/screens/checkup/checkup.dart @@ -73,6 +73,10 @@ class _CheckupScreenState extends State { child: Scaffold( appBar: AppBar( title: Text('Your Health Checkup'), + leading: IconButton( + icon: Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), ), backgroundColor: Theme.of(context).primaryColor, body: _getBody(checkupState), diff --git a/lib/src/ui/screens/checkup/checkup_completing_hud.dart b/lib/src/ui/screens/checkup/checkup_completing_hud.dart deleted file mode 100644 index 12297ac..0000000 --- a/lib/src/ui/screens/checkup/checkup_completing_hud.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_hud/flutter_hud.dart'; - -class CheckupCompletingHUD extends StatelessWidget { - final Widget Function(BuildContext) builder; - final bool show; - - const CheckupCompletingHUD({ - this.builder, - this.show, - }); - - @override - Widget build(BuildContext context) { - return WidgetHUD( - showHUD: show, - builder: builder, - hud: HUD( - label: 'Retrieving your assessment', - ), - ); - } -} diff --git a/lib/src/ui/screens/checkup/checkup_loaded_body.dart b/lib/src/ui/screens/checkup/checkup_loaded_body.dart index 10b32bf..c9697e6 100644 --- a/lib/src/ui/screens/checkup/checkup_loaded_body.dart +++ b/lib/src/ui/screens/checkup/checkup_loaded_body.dart @@ -54,17 +54,14 @@ class _CheckupLoadedBodyState extends State { if (checkupState.checkup.dataContributionPreference == true) { await _saveCurrentLocation(checkupState); } + } else if (currentIndex > 0 && currentIndex < steps.length - 1) { + context.bloc().add(UpdateRemoteCheckup()); } setState(() { currentIndex = index; currentStep = steps[index]; }); - - // Destination-specific actions - if (currentIndex > 1 && currentIndex < steps.length - 1) { - context.bloc().add(UpdateRemoteCheckup()); - } } @override diff --git a/lib/src/ui/screens/checkup/checkup_progress_bar.dart b/lib/src/ui/screens/checkup/checkup_progress_bar.dart index 95da545..1a4a526 100644 --- a/lib/src/ui/screens/checkup/checkup_progress_bar.dart +++ b/lib/src/ui/screens/checkup/checkup_progress_bar.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; import 'package:coronavirus_diary/src/ui/router.dart'; class CheckupProgressBar extends StatelessWidget { @@ -17,8 +15,7 @@ class CheckupProgressBar extends StatelessWidget { _handleNextButton(BuildContext context) { bool isLastPage = currentIndex == stepsLength - 1; if (isLastPage) { - context.bloc().add(CompleteCheckup()); - Navigator.pushNamed(context, AssessmentScreen.routeName); + Navigator.pushReplacementNamed(context, AssessmentScreen.routeName); } else { Provider.of(context, listen: false).nextPage( duration: Duration(milliseconds: 400), diff --git a/pubspec.lock b/pubspec.lock index 936a05d..b44805c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -487,6 +487,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.23.1" + share: + dependency: "direct main" + description: + name: share + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3+6" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7ff203a..209c2d8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: path_provider: ^1.6.5 path: ^1.6.4 provider: ^4.0.4 + share: ^0.6.3+6 uuid: ^2.0.4 dev_dependencies: From 2d170ad98e8c9e5d0662c48421b59cdf36dabc15 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 19:15:05 -0700 Subject: [PATCH 5/7] Save last assessment --- lib/src/data/models/preferences.dart | 17 +++-- lib/src/data/models/preferences.g.dart | 4 ++ lib/src/ui/screens/assessment/assessment.dart | 63 ++++++++++++------- 3 files changed, 56 insertions(+), 28 deletions(-) diff --git a/lib/src/data/models/preferences.dart b/lib/src/data/models/preferences.dart index 0b67f3e..568362c 100644 --- a/lib/src/data/models/preferences.dart +++ b/lib/src/data/models/preferences.dart @@ -2,23 +2,32 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:uuid/uuid.dart'; import 'package:uuid/uuid_util.dart'; +import 'assessments.dart'; + part 'preferences.g.dart'; @JsonSerializable() class Preferences { final String userId; + final Assessment lastAssessment; Preferences({ userId, + this.lastAssessment, }) : userId = userId ?? Uuid().v4(options: { 'grng': UuidUtil.cryptoRNG, }); - Preferences.clone(Preferences preferences) - : this( - userId: preferences.userId, - ); + Preferences cloneWith({ + Preferences preferences, + Assessment lastAssessment, + }) { + return Preferences( + userId: preferences.userId, + lastAssessment: lastAssessment ?? preferences.lastAssessment, + ); + } factory Preferences.fromJson(Map json) => _$PreferencesFromJson(json); diff --git a/lib/src/data/models/preferences.g.dart b/lib/src/data/models/preferences.g.dart index 61c850e..ef185c0 100644 --- a/lib/src/data/models/preferences.g.dart +++ b/lib/src/data/models/preferences.g.dart @@ -9,10 +9,14 @@ part of 'preferences.dart'; Preferences _$PreferencesFromJson(Map json) { return Preferences( userId: json['user_id'], + lastAssessment: json['last_assessment'] == null + ? null + : Assessment.fromJson(json['last_assessment'] as Map), ); } Map _$PreferencesToJson(Preferences instance) => { 'user_id': instance.userId, + 'last_assessment': instance.lastAssessment?.toJson(), }; diff --git a/lib/src/ui/screens/assessment/assessment.dart b/lib/src/ui/screens/assessment/assessment.dart index 37bc37f..34bff09 100644 --- a/lib/src/ui/screens/assessment/assessment.dart +++ b/lib/src/ui/screens/assessment/assessment.dart @@ -3,38 +3,53 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_hud/flutter_hud.dart'; import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; +import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; import 'assessments/index.dart'; -import 'share.dart'; class AssessmentScreen extends StatelessWidget { static const routeName = '/assessment'; @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { - Widget body; - if (state is CheckupStateInProgress) { - context.bloc().add(CompleteCheckup()); - } else if (state is CheckupStateCompleted) { - switch (state.assessment.matchesPuiSymptoms) { - case true: - body = PositiveAssessment(); - break; - case false: - body = NegativeAssessment(); - break; - } - } - return WidgetHUD( - showHUD: state is CheckupStateCompleting, - hud: HUD(label: 'Loading your assessment'), - builder: (context) { - return Scaffold( - appBar: AppBar( - title: Text('Your Personalized Assessment'), - ), - body: body, + final PreferencesState preferencesState = state; + + return BlocBuilder( + builder: (context, state) { + Widget body; + if (state is CheckupStateInProgress) { + context.bloc().add(CompleteCheckup()); + } else if (state is CheckupStateCompleted) { + // Remember assessment + Preferences newPreferences = + preferencesState.preferences.cloneWith( + lastAssessment: state.assessment, + ); + context + .bloc() + .add(UpdatePreferences(newPreferences)); + + switch (state.assessment.matchesPuiSymptoms) { + case true: + body = PositiveAssessment(); + break; + case false: + body = NegativeAssessment(); + break; + } + } + return WidgetHUD( + showHUD: state is CheckupStateCompleting, + hud: HUD(label: 'Loading your assessment'), + builder: (context) { + return Scaffold( + appBar: AppBar( + title: Text('Your Personalized Assessment'), + ), + body: body, + ); + }, ); }, ); From e9bbeae75bcc25fef063bf94909622aebf274473 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 20:30:14 -0700 Subject: [PATCH 6/7] Refine home page --- .../blocs/preferences/preferences_state.dart | 3 + lib/src/data/models/preferences.dart | 9 +- lib/src/ui/screens/assessment/assessment.dart | 17 +-- .../assessment/assessments/negative.dart | 4 +- .../assessment/assessments/positive.dart | 4 +- lib/src/ui/screens/home/home.dart | 104 +++++++++++++++--- .../assessment => widgets}/share.dart | 0 7 files changed, 112 insertions(+), 29 deletions(-) rename lib/src/ui/{screens/assessment => widgets}/share.dart (100%) diff --git a/lib/src/blocs/preferences/preferences_state.dart b/lib/src/blocs/preferences/preferences_state.dart index 0550886..f7b71ef 100644 --- a/lib/src/blocs/preferences/preferences_state.dart +++ b/lib/src/blocs/preferences/preferences_state.dart @@ -4,4 +4,7 @@ class PreferencesState { final Preferences preferences; const PreferencesState({this.preferences}); + + @override + String toString() => 'PreferencesState { preferences: $preferences }'; } diff --git a/lib/src/data/models/preferences.dart b/lib/src/data/models/preferences.dart index 568362c..e8ae5a3 100644 --- a/lib/src/data/models/preferences.dart +++ b/lib/src/data/models/preferences.dart @@ -20,16 +20,19 @@ class Preferences { }); Preferences cloneWith({ - Preferences preferences, Assessment lastAssessment, }) { return Preferences( - userId: preferences.userId, - lastAssessment: lastAssessment ?? preferences.lastAssessment, + userId: this.userId, + lastAssessment: lastAssessment ?? this.lastAssessment, ); } factory Preferences.fromJson(Map json) => _$PreferencesFromJson(json); Map toJson() => _$PreferencesToJson(this); + + @override + String toString() => + 'Preferences { userId: $userId, lastAssessment: $lastAssessment }'; } diff --git a/lib/src/ui/screens/assessment/assessment.dart b/lib/src/ui/screens/assessment/assessment.dart index 34bff09..2e0676e 100644 --- a/lib/src/ui/screens/assessment/assessment.dart +++ b/lib/src/ui/screens/assessment/assessment.dart @@ -22,13 +22,16 @@ class AssessmentScreen extends StatelessWidget { context.bloc().add(CompleteCheckup()); } else if (state is CheckupStateCompleted) { // Remember assessment - Preferences newPreferences = - preferencesState.preferences.cloneWith( - lastAssessment: state.assessment, - ); - context - .bloc() - .add(UpdatePreferences(newPreferences)); + if (preferencesState.preferences.lastAssessment != + state.assessment) { + Preferences newPreferences = + preferencesState.preferences.cloneWith( + lastAssessment: state.assessment, + ); + context + .bloc() + .add(UpdatePreferences(newPreferences)); + } switch (state.assessment.matchesPuiSymptoms) { case true: diff --git a/lib/src/ui/screens/assessment/assessments/negative.dart b/lib/src/ui/screens/assessment/assessments/negative.dart index f07260e..80b7a5b 100644 --- a/lib/src/ui/screens/assessment/assessments/negative.dart +++ b/lib/src/ui/screens/assessment/assessments/negative.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:coronavirus_diary/src/ui/screens/assessment/share.dart'; +import 'package:coronavirus_diary/src/ui/widgets/share.dart'; class NegativeAssessment extends StatefulWidget { @override @@ -38,7 +38,7 @@ class _NegativeAssessmentState extends State { margin: EdgeInsets.only(bottom: 20), padding: EdgeInsets.symmetric(horizontal: 40), child: Text( - "If you continue to experience symptoms, please check in tomorrow. ", + "If you continue to experience symptoms, please check in tomorrow.", style: Theme.of(context).textTheme.body2.copyWith(fontSize: 16), textAlign: TextAlign.center, ), diff --git a/lib/src/ui/screens/assessment/assessments/positive.dart b/lib/src/ui/screens/assessment/assessments/positive.dart index 42df8bb..0010704 100644 --- a/lib/src/ui/screens/assessment/assessments/positive.dart +++ b/lib/src/ui/screens/assessment/assessments/positive.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:coronavirus_diary/src/ui/screens/assessment/share.dart'; +import 'package:coronavirus_diary/src/ui/widgets/share.dart'; class PositiveAssessment extends StatefulWidget { @override @@ -37,7 +37,7 @@ class _PositiveAssessmentState extends State { padding: EdgeInsets.symmetric(horizontal: 40), child: Text( "You are showing symptoms that may be of concern. " - "Please limit your contact with other people until you have a chance to follow up with a physician", + "Please limit your contact with other people until you have a chance to follow up with a physician.", style: Theme.of(context).textTheme.body2.copyWith(fontSize: 16), textAlign: TextAlign.center, ), diff --git a/lib/src/ui/screens/home/home.dart b/lib/src/ui/screens/home/home.dart index 2b9b6d4..e6a5739 100644 --- a/lib/src/ui/screens/home/home.dart +++ b/lib/src/ui/screens/home/home.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; import 'package:coronavirus_diary/src/ui/router.dart'; +import 'package:coronavirus_diary/src/ui/widgets/share.dart'; class HomeScreen extends StatefulWidget { static const routeName = '/'; @@ -12,6 +14,90 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { + Widget _getBody(PreferencesState state) { + final DateTime now = DateTime.now(); + final DateTime today = DateTime(now.year, now.month, now.day); + + if (state.preferences.lastAssessment == null || + state.preferences.lastAssessment.processed.isBefore(today)) { + return Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 20), + child: FaIcon( + FontAwesomeIcons.questionCircle, + color: Colors.white, + size: 80, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 20), + child: Text( + "Concerned about your health?", + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.title, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 20), + child: Text( + "Are you experiencing symptoms? Have you been in contact with someone who is infected?", + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.body2, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 40), + width: double.infinity, + child: RaisedButton( + onPressed: () => + Navigator.pushNamed(context, CheckupScreen.routeName), + child: Text('Check up on your health'), + ), + ), + ShareApp(), + ], + ); + } else { + return Column( + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 20), + child: FaIcon( + FontAwesomeIcons.checkCircle, + color: Colors.white, + size: 80, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 20), + child: Text( + 'You have completed your checkup for today!', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.title, + ), + ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 40), + child: Text( + 'If you continue to experience symptoms, please check back tomorrow.', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.subtitle, + ), + ), + ShareApp(), + ], + ); + } + } + @override Widget build(BuildContext context) { return BlocBuilder( @@ -21,21 +107,9 @@ class _HomeScreenState extends State { title: Text('Coronavirus Diary'), ), body: Container( - padding: EdgeInsets.all(20), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox( - width: double.infinity, - child: RaisedButton( - onPressed: () => - Navigator.pushNamed(context, CheckupScreen.routeName), - child: Text('Start checkup'), - ), - ), - ], - ), + padding: EdgeInsets.symmetric(vertical: 40), + alignment: Alignment.center, + child: _getBody(state), ), ); }, diff --git a/lib/src/ui/screens/assessment/share.dart b/lib/src/ui/widgets/share.dart similarity index 100% rename from lib/src/ui/screens/assessment/share.dart rename to lib/src/ui/widgets/share.dart From a176a03249a9d83920f80dc8b11f7428d2065698 Mon Sep 17 00:00:00 2001 From: Josh Smith Date: Fri, 20 Mar 2020 21:01:09 -0700 Subject: [PATCH 7/7] Take checkup logic out of assessments screen and allow user to view last assessment --- lib/src/ui/screens/assessment/assessment.dart | 63 +++---------- .../screens/assessment/assessments/index.dart | 14 +++ lib/src/ui/screens/checkup/checkup.dart | 92 ++++++++++++++----- .../screens/checkup/checkup_progress_bar.dart | 14 +-- lib/src/ui/screens/home/home.dart | 27 +++++- 5 files changed, 126 insertions(+), 84 deletions(-) diff --git a/lib/src/ui/screens/assessment/assessment.dart b/lib/src/ui/screens/assessment/assessment.dart index 2e0676e..8dfbc71 100644 --- a/lib/src/ui/screens/assessment/assessment.dart +++ b/lib/src/ui/screens/assessment/assessment.dart @@ -1,62 +1,27 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flutter_hud/flutter_hud.dart'; -import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; -import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; +import 'package:coronavirus_diary/src/data/models/assessments.dart'; import 'assessments/index.dart'; +class AssessmentScreenArguments { + final Assessment assessment; + + AssessmentScreenArguments({this.assessment}); +} + class AssessmentScreen extends StatelessWidget { static const routeName = '/assessment'; @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final PreferencesState preferencesState = state; - - return BlocBuilder( - builder: (context, state) { - Widget body; - if (state is CheckupStateInProgress) { - context.bloc().add(CompleteCheckup()); - } else if (state is CheckupStateCompleted) { - // Remember assessment - if (preferencesState.preferences.lastAssessment != - state.assessment) { - Preferences newPreferences = - preferencesState.preferences.cloneWith( - lastAssessment: state.assessment, - ); - context - .bloc() - .add(UpdatePreferences(newPreferences)); - } + final AssessmentScreenArguments args = + ModalRoute.of(context).settings.arguments; - switch (state.assessment.matchesPuiSymptoms) { - case true: - body = PositiveAssessment(); - break; - case false: - body = NegativeAssessment(); - break; - } - } - return WidgetHUD( - showHUD: state is CheckupStateCompleting, - hud: HUD(label: 'Loading your assessment'), - builder: (context) { - return Scaffold( - appBar: AppBar( - title: Text('Your Personalized Assessment'), - ), - body: body, - ); - }, - ); - }, - ); - }, + return Scaffold( + appBar: AppBar( + title: Text('Your Personalized Assessment'), + ), + body: getAssessmentView(args.assessment), ); } } diff --git a/lib/src/ui/screens/assessment/assessments/index.dart b/lib/src/ui/screens/assessment/assessments/index.dart index 2452092..6dfa33c 100644 --- a/lib/src/ui/screens/assessment/assessments/index.dart +++ b/lib/src/ui/screens/assessment/assessments/index.dart @@ -1,2 +1,16 @@ +import 'package:flutter/material.dart'; + +import 'package:coronavirus_diary/src/data/models/assessments.dart'; +import 'positive.dart'; +import 'negative.dart'; + export 'positive.dart'; export 'negative.dart'; + +Widget getAssessmentView(Assessment assessment) { + if (assessment.matchesPuiSymptoms) { + return PositiveAssessment(); + } else { + return NegativeAssessment(); + } +} diff --git a/lib/src/ui/screens/checkup/checkup.dart b/lib/src/ui/screens/checkup/checkup.dart index 6e58683..6feadcb 100644 --- a/lib/src/ui/screens/checkup/checkup.dart +++ b/lib/src/ui/screens/checkup/checkup.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_hud/flutter_hud.dart'; import 'package:provider/provider.dart'; import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; +import 'package:coronavirus_diary/src/blocs/preferences/preferences.dart'; +import 'package:coronavirus_diary/src/ui/router.dart'; import 'package:coronavirus_diary/src/ui/widgets/loading_indicator.dart'; import 'checkup_loaded_body.dart'; @@ -51,36 +54,79 @@ class _CheckupScreenState extends State { ); } - Widget _getBody(CheckupState checkupState) { - if (checkupState is CheckupStateNotCreated || - checkupState is CheckupStateCreating) { - return _getUnloadedBody(checkupState); - } else if (checkupState is CheckupStateInProgress) { - return CheckupLoadedBody(); - } else { - return _getErrorBody(); + void _handleCheckupCompletion( + PreferencesState preferencesState, + CheckupStateCompleted checkupState, + ) { + // Remember assessment + if (preferencesState.preferences.lastAssessment != + checkupState.assessment) { + Preferences newPreferences = preferencesState.preferences.cloneWith( + lastAssessment: checkupState.assessment, + ); + context.bloc().add(UpdatePreferences(newPreferences)); + } + + // Navigate to assessment view + Navigator.pushReplacementNamed( + context, + AssessmentScreen.routeName, + arguments: AssessmentScreenArguments( + assessment: checkupState.assessment, + ), + ); + } + + Widget _getBody( + PreferencesState preferencesState, + CheckupState checkupState, + ) { + switch (checkupState.runtimeType) { + case CheckupStateNotCreated: + case CheckupStateCreating: + return _getUnloadedBody(checkupState); + case CheckupStateInProgress: + case CheckupStateCompleting: + return CheckupLoadedBody(); + case CheckupStateCompleted: + _handleCheckupCompletion(preferencesState, checkupState); + return null; + default: + return _getErrorBody(); } } @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocBuilder( builder: (context, state) { - final CheckupState checkupState = state; + final PreferencesState preferencesState = state; + + return BlocBuilder( + builder: (context, state) { + final CheckupState checkupState = state; - return ChangeNotifierProvider( - create: (context) => _pageController, - child: Scaffold( - appBar: AppBar( - title: Text('Your Health Checkup'), - leading: IconButton( - icon: Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ), - backgroundColor: Theme.of(context).primaryColor, - body: _getBody(checkupState), - ), + return WidgetHUD( + showHUD: checkupState is CheckupStateCompleting, + hud: HUD(label: 'Loading your assessment'), + builder: (context) { + return ChangeNotifierProvider( + create: (context) => _pageController, + child: Scaffold( + appBar: AppBar( + title: Text('Your Health Checkup'), + leading: IconButton( + icon: Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ), + backgroundColor: Theme.of(context).primaryColor, + body: _getBody(preferencesState, checkupState), + ), + ); + }, + ); + }, ); }, ); diff --git a/lib/src/ui/screens/checkup/checkup_progress_bar.dart b/lib/src/ui/screens/checkup/checkup_progress_bar.dart index 1a4a526..4ccae00 100644 --- a/lib/src/ui/screens/checkup/checkup_progress_bar.dart +++ b/lib/src/ui/screens/checkup/checkup_progress_bar.dart @@ -1,21 +1,24 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import 'package:coronavirus_diary/src/ui/router.dart'; +import 'package:coronavirus_diary/src/blocs/checkup/checkup.dart'; class CheckupProgressBar extends StatelessWidget { final int currentIndex; final int stepsLength; + final bool isLastPage; + final double percentComplete; const CheckupProgressBar({ this.currentIndex, this.stepsLength, - }); + }) : isLastPage = currentIndex == stepsLength - 1, + percentComplete = (currentIndex) / (stepsLength - 1); _handleNextButton(BuildContext context) { - bool isLastPage = currentIndex == stepsLength - 1; if (isLastPage) { - Navigator.pushReplacementNamed(context, AssessmentScreen.routeName); + context.bloc().add(CompleteCheckup()); } else { Provider.of(context, listen: false).nextPage( duration: Duration(milliseconds: 400), @@ -26,9 +29,6 @@ class CheckupProgressBar extends StatelessWidget { @override Widget build(BuildContext context) { - double percentComplete = (currentIndex) / (stepsLength - 1); - bool isLastPage = currentIndex == stepsLength - 1; - // Remember to update this if steps are added that do not count towards the total String percentCompleteText = 'Step ${currentIndex} of ${stepsLength - 1}'; diff --git a/lib/src/ui/screens/home/home.dart b/lib/src/ui/screens/home/home.dart index e6a5739..b1dfe95 100644 --- a/lib/src/ui/screens/home/home.dart +++ b/lib/src/ui/screens/home/home.dart @@ -85,13 +85,28 @@ class _HomeScreenState extends State { ), Container( padding: EdgeInsets.symmetric(horizontal: 40), - margin: EdgeInsets.only(bottom: 40), + margin: EdgeInsets.only(bottom: 20), child: Text( 'If you continue to experience symptoms, please check back tomorrow.', textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle, ), ), + Container( + padding: EdgeInsets.symmetric(horizontal: 40), + margin: EdgeInsets.only(bottom: 40), + width: double.infinity, + child: RaisedButton( + onPressed: () => Navigator.pushNamed( + context, + AssessmentScreen.routeName, + arguments: AssessmentScreenArguments( + assessment: state.preferences.lastAssessment, + ), + ), + child: Text('View my assessment'), + ), + ), ShareApp(), ], ); @@ -106,10 +121,12 @@ class _HomeScreenState extends State { appBar: AppBar( title: Text('Coronavirus Diary'), ), - body: Container( - padding: EdgeInsets.symmetric(vertical: 40), - alignment: Alignment.center, - child: _getBody(state), + body: SingleChildScrollView( + child: Container( + padding: EdgeInsets.symmetric(vertical: 40), + alignment: Alignment.center, + child: _getBody(state), + ), ), ); },