From f83071fa78d9b70485d9ccce9c1ca7d77d1f24f5 Mon Sep 17 00:00:00 2001 From: hmalik144 Date: Sat, 24 Jul 2021 20:54:18 +0100 Subject: [PATCH] - Introduction on base view model and stateless widget classes - Implementation of firebase - Implementation of dependency injection Took 11 hours 26 minutes --- lib/Utils/ViewUtils.dart | 13 + lib/base/BaseModel.dart | 41 +++ lib/base/BaseStatelessWidget.dart | 32 +++ lib/constants/constants.dart | 12 + lib/data/FirebaseAuthData.dart | 49 ++++ lib/data/ViewState.dart | 20 ++ lib/locator.dart | 17 ++ lib/login/LoginScreen.dart | 336 ++++++++++++++++++++++++ lib/main.dart | 122 ++------- lib/managers/DialogManager.dart | 54 ++++ lib/model/DialogModels.dart | 26 ++ lib/model/UserProfileDetails.dart | 26 ++ lib/screens/OverviewScreen.dart | 8 + lib/services/DialogService.dart | 54 ++++ lib/services/NavigationService.dart | 21 ++ lib/viewmodels/LoginViewModel.dart | 14 + lib/viewmodels/UserAuthViewModel.dart | 16 ++ lib/widgets/ButtonWidget.dart | 23 ++ lib/widgets/CodeEnterWidget.dart | 74 ++++++ lib/widgets/DisplayUserDetails.dart | 85 ++++++ lib/widgets/LoaderWidget.dart | 61 +++++ lib/widgets/TextDetailWidget.dart | 26 ++ lib/widgets/router.dart | 28 ++ pubspec.lock | 361 ++++++++++++++++++++++++-- pubspec.yaml | 16 +- test/widget_test.dart | 30 --- web/index.html | 23 ++ 27 files changed, 1438 insertions(+), 150 deletions(-) create mode 100644 lib/Utils/ViewUtils.dart create mode 100644 lib/base/BaseModel.dart create mode 100644 lib/base/BaseStatelessWidget.dart create mode 100644 lib/constants/constants.dart create mode 100644 lib/data/FirebaseAuthData.dart create mode 100644 lib/data/ViewState.dart create mode 100644 lib/locator.dart create mode 100644 lib/login/LoginScreen.dart create mode 100644 lib/managers/DialogManager.dart create mode 100644 lib/model/DialogModels.dart create mode 100644 lib/model/UserProfileDetails.dart create mode 100644 lib/screens/OverviewScreen.dart create mode 100644 lib/services/DialogService.dart create mode 100644 lib/services/NavigationService.dart create mode 100644 lib/viewmodels/LoginViewModel.dart create mode 100644 lib/viewmodels/UserAuthViewModel.dart create mode 100644 lib/widgets/ButtonWidget.dart create mode 100644 lib/widgets/CodeEnterWidget.dart create mode 100644 lib/widgets/DisplayUserDetails.dart create mode 100644 lib/widgets/LoaderWidget.dart create mode 100644 lib/widgets/TextDetailWidget.dart create mode 100644 lib/widgets/router.dart delete mode 100644 test/widget_test.dart diff --git a/lib/Utils/ViewUtils.dart b/lib/Utils/ViewUtils.dart new file mode 100644 index 0000000..91c2323 --- /dev/null +++ b/lib/Utils/ViewUtils.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; +import 'package:toast/toast.dart'; + +class ViewUtils{ + + static displayToast(BuildContext context, String message){ + Toast.show( + message, context, + duration: Toast.LENGTH_SHORT, + gravity: Toast.BOTTOM + ); + } +} \ No newline at end of file diff --git a/lib/base/BaseModel.dart b/lib/base/BaseModel.dart new file mode 100644 index 0000000..340d788 --- /dev/null +++ b/lib/base/BaseModel.dart @@ -0,0 +1,41 @@ +import 'dart:async'; + +import 'package:days_left/data/ViewState.dart'; +import 'package:stacked/stacked.dart'; + +class BaseModel extends BaseViewModel { + ViewState _viewState = Idle(); + + ViewState get viewState => _viewState; + + void onStart() { + _viewState = HasStarted(); + notifyListeners(); + } + + void onSuccess(dynamic data) { + _viewState = HasData(data); + notifyListeners(); + } + + void onError(String error) { + _viewState = HasError(error); + notifyListeners(); + } + + void handleFuture(Future func) { + onStart(); + func.then((value) => onSuccess(value)).catchError((error) { + print(error); + onError(error); + }); + } + + void handleStream(Stream stream) { + stream.listen((event) { + onSuccess(event); + }).onError((handleError) { + onError(handleError); + }); + } +} diff --git a/lib/base/BaseStatelessWidget.dart b/lib/base/BaseStatelessWidget.dart new file mode 100644 index 0000000..a7db23e --- /dev/null +++ b/lib/base/BaseStatelessWidget.dart @@ -0,0 +1,32 @@ +import 'package:days_left/Utils/ViewUtils.dart'; +import 'package:days_left/data/ViewState.dart'; +import 'BaseModel.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:stacked/stacked.dart'; + +abstract class BaseStatelessWidget extends StatelessWidget { + BaseModel getBaseModel(); + Widget displayWidget(context, model, child); + + @override + Widget build(BuildContext parent) { + return ViewModelBuilder.reactive( + builder: (context, model, child) { + var state = model.viewState; + + if (state is HasStarted) + return Center( + child: CircularProgressIndicator(), + ); + else if (state is HasError) + WidgetsBinding.instance.addPostFrameCallback( + (_) => ViewUtils.displayToast(parent, state.error)); + + return Center( + child: displayWidget(context, model, child), + ); + }, + viewModelBuilder: () => getBaseModel()); + } +} diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart new file mode 100644 index 0000000..70ca964 --- /dev/null +++ b/lib/constants/constants.dart @@ -0,0 +1,12 @@ +const String PhoneNoViewRoute = "PhoneNo"; +const String CodeViewRoute = "Code"; +const String HomeViewRoute = "Home"; +const String UpdateDetailsViewRoute = "UpdateDetails"; +const String AddTestsViewRoute = "AddTests"; +const String TestListViewRoute = "TestList"; +const String TestOverviewViewRoute = "TestOverview"; +const String LoginViewRoute = "Login"; + +const String HasStarted = "hasStarted"; +const String HasData = "hasData"; +const String HasError = "hasError"; \ No newline at end of file diff --git a/lib/data/FirebaseAuthData.dart b/lib/data/FirebaseAuthData.dart new file mode 100644 index 0000000..e6e4eb5 --- /dev/null +++ b/lib/data/FirebaseAuthData.dart @@ -0,0 +1,49 @@ +// import 'package:firebase_auth/firebase_auth.dart'; + +import 'package:firebase_auth/firebase_auth.dart'; + +class FirebaseAuthData { + final FirebaseAuth firebaseAuth = FirebaseAuth.instance; + + User getUser() { + return firebaseAuth.currentUser; + } + + String getUid() { + return getUser()?.uid; + } + + Future signUpWithEmailAndPassword( + String email, String password) { + return firebaseAuth.createUserWithEmailAndPassword( + email: email, password: password); + } + + Future signIn(String email, String password) { + return firebaseAuth.signInWithEmailAndPassword( + email: email, password: password); + } + + Future resetPassword(String email) { + return firebaseAuth.sendPasswordResetEmail(email: email); + } + + Future signOut() { + return firebaseAuth.signOut(); + } + + Future updateUsername(String newEmail) { + return getUser().updateEmail(newEmail); + } + + Future updatePassword( + String email, String password, String newPassword) async { + UserCredential credentials = await signIn(email, password); + return credentials.user.updatePassword(newPassword); + } + + Future updateProfile({String displayName, String photoURL}) { + return getUser() + .updateProfile(displayName: displayName, photoURL: photoURL); + } +} diff --git a/lib/data/ViewState.dart b/lib/data/ViewState.dart new file mode 100644 index 0000000..f404538 --- /dev/null +++ b/lib/data/ViewState.dart @@ -0,0 +1,20 @@ +import 'package:sealed_class/sealed_class.dart'; + +@Sealed([Idle, HasStarted, HasData, HasError]) +abstract class ViewState {} + +class Idle implements ViewState {} + +class HasStarted implements ViewState {} + +class HasData implements ViewState { + final dynamic data; + + HasData(this.data); +} + +class HasError implements ViewState { + final String error; + + HasError(this.error); +} diff --git a/lib/locator.dart b/lib/locator.dart new file mode 100644 index 0000000..82b8d9d --- /dev/null +++ b/lib/locator.dart @@ -0,0 +1,17 @@ +import 'package:days_left/viewmodels/LoginViewModel.dart'; +import 'package:days_left/viewmodels/UserAuthViewModel.dart'; +import 'package:get_it/get_it.dart'; +import 'data/FirebaseAuthData.dart'; +import 'services/DialogService.dart'; +import 'services/NavigationService.dart'; + +GetIt locator = GetIt.instance; + +void setupLocator() { + final FirebaseAuthData firebaseSource = FirebaseAuthData(); + + locator.registerLazySingleton(() => NavigationService()); + locator.registerLazySingleton(() => DialogService()); + locator.registerLazySingleton(() => UserAuthViewModel(firebaseSource)); + locator.registerLazySingleton(() => LoginViewModel(firebaseSource)); +} diff --git a/lib/login/LoginScreen.dart b/lib/login/LoginScreen.dart new file mode 100644 index 0000000..fde3cb4 --- /dev/null +++ b/lib/login/LoginScreen.dart @@ -0,0 +1,336 @@ +import 'package:days_left/base/BaseModel.dart'; +import 'package:days_left/base/BaseStatelessWidget.dart'; +import 'package:days_left/login/forgot_password_6.dart'; +import 'package:days_left/login/signup_screen_6.dart'; +import 'package:days_left/login/values/values.dart'; +import 'package:days_left/login/widgets/custom_button.dart'; +import 'package:days_left/login/widgets/custom_divider.dart'; +import 'package:days_left/login/widgets/custom_text_form_field.dart'; +import 'package:days_left/login/widgets/spaces.dart'; +import 'package:days_left/viewmodels/LoginViewModel.dart'; +import 'package:flutter/material.dart'; + +import '../locator.dart'; + +class LoginScreen extends BaseStatelessWidget { + @override + BaseModel getBaseModel() { + final LoginViewModel _userAuthViewModel = locator(); + return _userAuthViewModel; + } + + @override + Widget displayWidget(context, model, child) { + ThemeData theme = Theme.of(context); + var heightOfScreen = MediaQuery.of(context).size.height; + var widthOfScreen = MediaQuery.of(context).size.width; + return Scaffold( + body: Container( + child: Stack( + children: [ + _buildHeader(context), + Container( + margin: EdgeInsets.only(top: heightOfScreen * 0.2), + decoration: BoxDecoration( + color: AppColors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(Sizes.RADIUS_24), + topRight: Radius.circular(Sizes.RADIUS_24), + ), + ), + child: ListView( + children: [ + Container( + margin: + EdgeInsets.symmetric(horizontal: widthOfScreen * 0.1), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + StringConst.LOG_IN, + style: theme.textTheme.headline4.copyWith( + color: AppColors.black, + ), + ), + SpaceH8(), + CustomDivider( + color: AppColors.violetShade2, + height: Sizes.HEIGHT_3, + width: Sizes.WIDTH_40, + ), + SpaceH20(), + Text( + StringConst.LOGIN_MSG, + style: theme.textTheme.bodyText1.copyWith( + color: AppColors.greyShade8, + fontWeight: FontWeight.w600, + fontSize: Sizes.TEXT_SIZE_14, + ), + ), + SpaceH24(), + _buildForm(context), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + + Widget _buildHeader(context) { + ThemeData theme = Theme.of(context); + var heightOfScreen = MediaQuery.of(context).size.height; + var widthOfScreen = MediaQuery.of(context).size.width; + return Stack( + children: [ + Image.asset( + ImagePath.SPLASH_1, + height: heightOfScreen * 0.3, + width: widthOfScreen, + fit: BoxFit.cover, + ), + Container( + height: heightOfScreen * 0.3, + width: widthOfScreen, + decoration: BoxDecoration(gradient: Gradients.headerOverlayGradient), + ), + Container( + margin: EdgeInsets.only( + top: heightOfScreen * 0.075, + left: widthOfScreen * 0.1, + ), + child: Text( + StringConst.APP_NAME, + style: theme.textTheme.headline4.copyWith( + color: AppColors.white, + ), + ), + ), + ], + ); + } + + Widget _buildForm(context) { + ThemeData theme = Theme.of(context); + var heightOfScreen = MediaQuery.of(context).size.height; + var widthOfScreen = MediaQuery.of(context).size.width; + return Column( + children: [ + CustomTextFormField( + textInputType: TextInputType.text, + hintTextStyle: Styles.customTextStyle(color: AppColors.greyShade8), + textStyle: Styles.customTextStyle(color: AppColors.greyShade8), + labelText: StringConst.EMAIL, + labelStyle: theme.textTheme.subtitle1.copyWith( + color: AppColors.violetShade2, + ), + hasSuffixIcon: true, + suffixIcon: Icon( + Icons.mail, + color: AppColors.greyShade8, + ), + border: + Borders.customOutlineInputBorder(borderRadius: Sizes.RADIUS_4), + enabledBorder: + Borders.customOutlineInputBorder(borderRadius: Sizes.RADIUS_4), + focusedBorder: Borders.customOutlineInputBorder( + borderRadius: Sizes.RADIUS_4, + color: AppColors.violetShade2, + width: Sizes.WIDTH_2, + ), + hintText: StringConst.EMAIL, + ), + SpaceH20(), + CustomTextFormField( + textInputType: TextInputType.text, + hintTextStyle: Styles.customTextStyle( + color: AppColors.greyShade8, + ), + textStyle: Styles.customTextStyle(color: AppColors.greyShade8), + labelText: StringConst.PASSWORD, + labelStyle: theme.textTheme.subtitle1.copyWith( + color: AppColors.violetShade2, + ), + hintText: StringConst.PASSWORD_HINT_TEXT, + hasSuffixIcon: true, + suffixIcon: Icon( + Icons.lock, + color: AppColors.greyShade8, + ), + border: + Borders.customOutlineInputBorder(borderRadius: Sizes.RADIUS_4), + enabledBorder: + Borders.customOutlineInputBorder(borderRadius: Sizes.RADIUS_4), + focusedBorder: Borders.customOutlineInputBorder( + borderRadius: Sizes.RADIUS_4, + color: AppColors.violetShade2, + width: Sizes.WIDTH_2, + ), + obscured: true, + ), + SpaceH16(), + GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ForgotPasswordScreen6(), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + StringConst.FORGOT_PASSWORD, + style: theme.textTheme.subtitle1.copyWith( + color: AppColors.black, + fontSize: Sizes.TEXT_SIZE_14, + ), + ), + ], + ), + ), + SpaceH24(), + CustomButton( + title: StringConst.LOG_IN, + color: AppColors.violetShade2, + borderRadius: Sizes.ELEVATION_4, + textStyle: theme.textTheme.button.copyWith( + color: AppColors.white, + fontSize: Sizes.TEXT_SIZE_16, + ), + onPressed: () {}, + ), + SizedBox( + height: heightOfScreen * 0.03, + ), + _buildSeparator(context), + SizedBox( + height: heightOfScreen * 0.03, + ), + Row( + children: [ + Expanded( + child: CustomButton( + title: StringConst.FACEBOOK, + textStyle: theme.textTheme.button + .copyWith(color: AppColors.greyShade8), + hasIcon: true, + color: AppColors.white, + elevation: Sizes.ELEVATION_0, + borderRadius: Sizes.ELEVATION_4, + borderSide: Borders.customBorder(color: AppColors.grey), + icon: Image.asset( + ImagePath.FACEBOOK, + height: Sizes.HEIGHT_24, + width: Sizes.WIDTH_24, + ), + onPressed: () {}, + ), + ), + SizedBox(width: widthOfScreen * 0.1), + Expanded( + child: CustomButton( + title: StringConst.GOOGLE, + hasIcon: true, + color: AppColors.white, + elevation: Sizes.ELEVATION_0, + borderRadius: Sizes.ELEVATION_4, + borderSide: Borders.customBorder(color: AppColors.grey), + textStyle: theme.textTheme.button + .copyWith(color: AppColors.greyShade8), + icon: Image.asset( + ImagePath.GOOGLE, + height: Sizes.HEIGHT_24, + width: Sizes.WIDTH_24, + ), + onPressed: () {}, + ), + ) + ], + ), + SizedBox( + height: heightOfScreen * 0.08, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + StringConst.DONT_HAVE_AN_ACCOUNT, + style: theme.textTheme.bodyText1.copyWith( + color: AppColors.black, + fontWeight: FontWeight.w600, + fontSize: Sizes.TEXT_SIZE_14, + ), + ), + SpaceW4(), + GestureDetector( + onTap: () => Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SignUpScreen6(), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + StringConst.SIGN_UP, + style: theme.textTheme.headline4.copyWith( + color: AppColors.orangeShade5, + fontSize: Sizes.TEXT_SIZE_14, + fontWeight: FontWeight.w600, + ), + ), + SpaceH2(), + CustomDivider( + color: AppColors.orangeShade5, + height: Sizes.HEIGHT_2, + width: Sizes.WIDTH_50, + ), + ], + ), + ), + ], + ), + SpaceH20(), + ], + ); + } + + Widget _buildSeparator(BuildContext context) { + var textTheme = Theme.of(context).textTheme; + var widthOfScreen = MediaQuery.of(context).size.width; + return Container( + margin: const EdgeInsets.symmetric(horizontal: Sizes.MARGIN_16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomDivider( + color: Colors.grey[300], + width: widthOfScreen * 0.15, + height: Sizes.HEIGHT_2, + ), + SpaceW16(), + Text( + StringConst.OR, + style: textTheme.subtitle1.copyWith( + color: AppColors.greyShade8, + ), + ), + SpaceW16(), + CustomDivider( + color: Colors.grey[300], + width: widthOfScreen * 0.15, + height: Sizes.HEIGHT_2, + ), + ], + ), + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index d8a0526..1fa12bf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,113 +1,39 @@ +import 'package:days_left/login/LoginScreen.dart'; +import 'package:days_left/screens/OverviewScreen.dart'; +import 'package:days_left/services/DialogService.dart'; +import 'package:days_left/services/NavigationService.dart'; +import 'package:days_left/viewmodels/UserAuthViewModel.dart'; +import 'package:days_left/widgets/router.dart'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'locator.dart'; +import 'managers/DialogManager.dart'; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + setupLocator(); runApp(MyApp()); } class MyApp extends StatelessWidget { - // This widget is the root of your application. + final UserAuthViewModel _userAuthViewModel = locator(); + @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. primarySwatch: Colors.blue, ), - home: MyHomePage(title: 'Flutter Demo Home Page'), + home: _userAuthViewModel.loggedIn ? OverviewScreen() : LoginScreen(), + onGenerateRoute: generateRoute, + builder: (context, child) => Navigator( + key: locator().dialogNavigationKey, + onGenerateRoute: (settings) => MaterialPageRoute( + builder: (context) => DialogManager(child: child)), + ), + navigatorKey: locator().navigationKey, ); } -} - -class MyHomePage extends StatefulWidget { - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} +} \ No newline at end of file diff --git a/lib/managers/DialogManager.dart b/lib/managers/DialogManager.dart new file mode 100644 index 0000000..7f1f2fc --- /dev/null +++ b/lib/managers/DialogManager.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:days_left/services/DialogService.dart'; + +import '../locator.dart'; +import '../model/DialogModels.dart'; + +class DialogManager extends StatefulWidget { + final Widget child; + DialogManager({Key key, this.child}) : super(key: key); + + _DialogManagerState createState() => _DialogManagerState(); +} + +class _DialogManagerState extends State { + DialogService _dialogService = locator(); + + @override + void initState() { + super.initState(); + _dialogService.registerDialogListener(_showDialog); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } + + void _showDialog(DialogRequest request) { + var isConfirmationDialog = request.cancelTitle != null; + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(request.title), + content: Text(request.description), + actions: [ + if (isConfirmationDialog) + FlatButton( + child: Text(request.cancelTitle), + onPressed: () { + _dialogService + .dialogComplete(DialogResponse(confirmed: false)); + }, + ), + FlatButton( + child: Text(request.buttonTitle), + onPressed: () { + _dialogService + .dialogComplete(DialogResponse(confirmed: true)); + }, + ), + ], + )); + } +} diff --git a/lib/model/DialogModels.dart b/lib/model/DialogModels.dart new file mode 100644 index 0000000..6bc7ec1 --- /dev/null +++ b/lib/model/DialogModels.dart @@ -0,0 +1,26 @@ +import 'package:flutter/foundation.dart'; + +class DialogRequest{ + final String title; + final String description; + final String buttonTitle; + final String cancelTitle; + + DialogRequest( + {@required this.title, + @required this.description, + @required this.buttonTitle, + this.cancelTitle}); +} + +class DialogResponse { + final String fieldOne; + final String fieldTwo; + final bool confirmed; + + DialogResponse({ + this.fieldOne, + this.fieldTwo, + this.confirmed, + }); +} \ No newline at end of file diff --git a/lib/model/UserProfileDetails.dart b/lib/model/UserProfileDetails.dart new file mode 100644 index 0000000..83fa14a --- /dev/null +++ b/lib/model/UserProfileDetails.dart @@ -0,0 +1,26 @@ + +class UserProfileDetails { + final String name; + final String dob; + final String postcode; + final String email; + final String phone; + final String sex; + + UserProfileDetails( + this.name, + this.dob, + this.postcode, + this.email, + this.phone, + this.sex + ); + + UserProfileDetails.fromSnapshot(Map snapshot) : + name = snapshot["name"], + dob = snapshot["dob"], + postcode = snapshot["postcode"], + email = snapshot["email"], + phone = snapshot["phone"], + sex = snapshot["sex"]; +} diff --git a/lib/screens/OverviewScreen.dart b/lib/screens/OverviewScreen.dart new file mode 100644 index 0000000..8ff7de2 --- /dev/null +++ b/lib/screens/OverviewScreen.dart @@ -0,0 +1,8 @@ +import 'package:flutter/cupertino.dart'; + +class OverviewScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Container(); + } +} diff --git a/lib/services/DialogService.dart b/lib/services/DialogService.dart new file mode 100644 index 0000000..0b67402 --- /dev/null +++ b/lib/services/DialogService.dart @@ -0,0 +1,54 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:days_left/model/DialogModels.dart'; + +class DialogService { + GlobalKey _dialogNavigationKey = GlobalKey(); + Function(DialogRequest) _showDialogListener; + Completer _dialogCompleter; + + GlobalKey get dialogNavigationKey => _dialogNavigationKey; + + /// Registers a callback function. Typically to show the dialog + void registerDialogListener(Function(DialogRequest) showDialogListener) { + _showDialogListener = showDialogListener; + } + + /// Calls the dialog listener and returns a Future that will wait for dialogComplete. + Future showDialog({ + String title, + String description, + String buttonTitle = 'Ok', + }) { + _dialogCompleter = Completer(); + _showDialogListener(DialogRequest( + title: title, + description: description, + buttonTitle: buttonTitle, + )); + return _dialogCompleter.future; + } + + /// Shows a confirmation dialog + Future showConfirmationDialog( + {String title, + String description, + String confirmationTitle = 'Ok', + String cancelTitle = 'Cancel'}) { + _dialogCompleter = Completer(); + _showDialogListener(DialogRequest( + title: title, + description: description, + buttonTitle: confirmationTitle, + cancelTitle: cancelTitle)); + return _dialogCompleter.future; + } + + /// Completes the _dialogCompleter to resume the Future's execution call + void dialogComplete(DialogResponse response) { + _dialogNavigationKey.currentState.pop(); + _dialogCompleter.complete(response); + _dialogCompleter = null; + } +} \ No newline at end of file diff --git a/lib/services/NavigationService.dart b/lib/services/NavigationService.dart new file mode 100644 index 0000000..246e545 --- /dev/null +++ b/lib/services/NavigationService.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class NavigationService { + GlobalKey _navigationKey = GlobalKey(); + + GlobalKey get navigationKey => _navigationKey; + + void pop() { + return _navigationKey.currentState.pop(); + } + + Future navigateTo(String routeName, {dynamic arguments}) { + return _navigationKey.currentState + .pushNamed(routeName, arguments: arguments); + } + + Future navigateToAndClearStack(String routeName, {dynamic arguments}) { + return _navigationKey.currentState + .pushNamedAndRemoveUntil(routeName, (r) => false, arguments: arguments); + } +} \ No newline at end of file diff --git a/lib/viewmodels/LoginViewModel.dart b/lib/viewmodels/LoginViewModel.dart new file mode 100644 index 0000000..3eaa6b6 --- /dev/null +++ b/lib/viewmodels/LoginViewModel.dart @@ -0,0 +1,14 @@ +import 'package:days_left/data/FirebaseAuthData.dart'; +import '../base/BaseModel.dart'; + +class LoginViewModel extends BaseModel { + FirebaseAuthData _firebaseAuthData; + LoginViewModel(this._firebaseAuthData); + + void tryLogin(String email, String password) { + // Todo: validate username & password + handleFuture(_firebaseAuthData.signIn(email, password)); + } + + +} diff --git a/lib/viewmodels/UserAuthViewModel.dart b/lib/viewmodels/UserAuthViewModel.dart new file mode 100644 index 0000000..0658210 --- /dev/null +++ b/lib/viewmodels/UserAuthViewModel.dart @@ -0,0 +1,16 @@ +import 'package:days_left/data/FirebaseAuthData.dart'; +import '../base/BaseModel.dart'; + +class UserAuthViewModel extends BaseModel { + bool _loggedIn = false; + bool get loggedIn => _loggedIn; + + FirebaseAuthData _firebaseAuthData; + UserAuthViewModel(this._firebaseAuthData){ + _firebaseAuthData.firebaseAuth.authStateChanges().listen((event) { + _loggedIn = event != null; + notifyListeners(); + }); + } + +} diff --git a/lib/widgets/ButtonWidget.dart b/lib/widgets/ButtonWidget.dart new file mode 100644 index 0000000..d087b12 --- /dev/null +++ b/lib/widgets/ButtonWidget.dart @@ -0,0 +1,23 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class ButtonWidget extends StatelessWidget{ + final String label; + final Function onPressed; + + ButtonWidget(this.label, {this.onPressed}); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(top: 20, bottom: 12), + child: ElevatedButton( + onPressed: onPressed, + child: Text(label), + ), + ); + } + + +} \ No newline at end of file diff --git a/lib/widgets/CodeEnterWidget.dart b/lib/widgets/CodeEnterWidget.dart new file mode 100644 index 0000000..6e92eb8 --- /dev/null +++ b/lib/widgets/CodeEnterWidget.dart @@ -0,0 +1,74 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; + +class CodeEnterWidget extends StatelessWidget { + final _formKey = GlobalKey(); + final GlobalKey scaffoldPageOne; + + CodeEnterWidget({this.scaffoldPageOne}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Form( + key: _formKey, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + ), + margin: EdgeInsets.all(20.0), + padding: EdgeInsets.all(12.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + child: Text( + "Sign in with your phone number below.", + style: TextStyle(color: Colors.white), + ), + color: Colors.blue, + alignment: Alignment.center, + padding: EdgeInsets.all(20.0), + ), + TextFormField( + keyboardType: TextInputType.phone, + validator: (value) { + if (value.isEmpty) { + return 'Enter the code sent to you'; + } + return null; + }, + ), + Container( + margin: EdgeInsets.only(top: 20, bottom: 12), + child: ElevatedButton( + onPressed: () { + if (_formKey.currentState.validate()) { + scaffoldPageOne.currentState.showSnackBar( + SnackBar(content: Text('Processing Data'))); + } + Timer(Duration(seconds: 2), () { + // Todo: change body + }); + }, + child: Text('Submit'), + ), + ), + ], + ), + ))), + ); + } +} diff --git a/lib/widgets/DisplayUserDetails.dart b/lib/widgets/DisplayUserDetails.dart new file mode 100644 index 0000000..553d290 --- /dev/null +++ b/lib/widgets/DisplayUserDetails.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:days_left/constants/constants.dart'; +import 'package:days_left/model/UserProfileDetails.dart'; +import 'package:days_left/services/NavigationService.dart'; + +import '../locator.dart'; +import 'TextDetailWidget.dart'; + +class DisplayUserDetails extends StatelessWidget { + final _formKey = GlobalKey(); + final NavigationService _navigationService = locator(); + + final UserProfileDetails userDetails; + final Function callback; + + DisplayUserDetails(this.userDetails, this.callback); + + @override + Widget build(BuildContext context) { + return Center( + child: Form( + key: _formKey, + child: Container( + width: 400, + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 5, + blurRadius: 7, + offset: Offset(0, 3), // changes position of shadow + ), + ], + ), + margin: EdgeInsets.all(20.0), + padding: EdgeInsets.all(12.0), + child: getInnerWidget(context), + )), + ); + } + + Widget getInnerWidget(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Container( + child: Text( + "User Profile", + style: TextStyle(color: Colors.white), + ), + color: Colors.blue, + alignment: Alignment.center, + padding: EdgeInsets.all(20.0), + ), + _getInnerWidgetList(), + Container( + margin: EdgeInsets.only(top: 20, bottom: 12), + child: ElevatedButton( + onPressed: () { + _navigationService.navigateTo(UpdateDetailsViewRoute, + arguments: userDetails); + }, + child: Text('Update'), + ), + ) + ], + ); + } + + Widget _getInnerWidgetList() { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + TextDetailWidget("Name", userDetails?.name ?? "n/a"), + TextDetailWidget("D.O.B", userDetails?.dob ?? "n/a"), + TextDetailWidget("Email", userDetails?.email ?? "n/a"), + TextDetailWidget("Phone no.", userDetails?.phone ?? "n/a"), + TextDetailWidget("Sex", userDetails?.sex ?? "n/a"), + ]); + } +} diff --git a/lib/widgets/LoaderWidget.dart b/lib/widgets/LoaderWidget.dart new file mode 100644 index 0000000..ac77704 --- /dev/null +++ b/lib/widgets/LoaderWidget.dart @@ -0,0 +1,61 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class LoaderWidget extends StatefulWidget { + + final Widget body; + LoaderWidget({this.body, Key key}): super(key: key); + + @override + LoaderWidgetState createState() => LoaderWidgetState(); +} + +class LoaderWidgetState extends State { + bool _status = false; + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Center( + child: _status ? Center(child: CircularProgressIndicator()) + : Center( + child: widget.body, + ), + ); + } + + void changeState(){ + setState(() { + _status = !_status; + }); + } +} + +abstract class LoaderStatelessWidget extends StatelessWidget{ + final loaderKey = GlobalKey(); + + void changeState(){ + loaderKey.currentState.changeState(); + } + + Widget LoaderWidgeted({Widget body}){ + return LoaderWidget(body: body, key: loaderKey); + } +} + +abstract class LoaderStatefulWidget extends State{ + final loaderKey = GlobalKey(); + + void changeState(){ + loaderKey.currentState.changeState(); + } + + Widget LoaderWidgeted(Widget body, Key key){ + return LoaderWidget(body: body, key: key); + } +} \ No newline at end of file diff --git a/lib/widgets/TextDetailWidget.dart b/lib/widgets/TextDetailWidget.dart new file mode 100644 index 0000000..327c956 --- /dev/null +++ b/lib/widgets/TextDetailWidget.dart @@ -0,0 +1,26 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class TextDetailWidget extends StatelessWidget { + final String labelText; + final String bodyText; + + TextDetailWidget(this.labelText, this.bodyText); + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + labelText, + style: TextStyle(color: Colors.blue), + ), + Text( + bodyText, + style: TextStyle(color: Colors.black), + ) + ]); + } +} diff --git a/lib/widgets/router.dart b/lib/widgets/router.dart new file mode 100644 index 0000000..6081d8d --- /dev/null +++ b/lib/widgets/router.dart @@ -0,0 +1,28 @@ +import 'package:days_left/login/login_screen_6.dart'; +import 'package:flutter/material.dart'; +import '../constants/constants.dart'; + + +Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case LoginViewRoute: + return _getPageRoute( + routeName: settings.name, + viewToShow: LoginScreen6(), + ); + default: + return MaterialPageRoute( + builder: (_) => Scaffold( + body: Center( + child: Text('No route defined for ${settings.name}')), + )); + } +} + +PageRoute _getPageRoute({String routeName, Widget viewToShow}) { + return MaterialPageRoute( + settings: RouteSettings( + name: routeName, + ), + builder: (_) => viewToShow); +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index a5a1eda..31b604e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,62 +1,202 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.39.17" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.7.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" + build: + dependency: transitive + description: + name: build + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.3.1" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.3.0" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.2" cupertino_icons: dependency: "direct main" description: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.3" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.6" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.0" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + firebase_database: + dependency: "direct main" + description: + name: firebase_database + url: "https://pub.dartlang.org" + source: hosted + version: "7.1.2" flutter: dependency: "direct main" description: flutter @@ -67,87 +207,268 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + get: + dependency: transitive + description: + name: get + url: "https://pub.dartlang.org" + source: hosted + version: "3.26.0" + get_it: + dependency: transitive + description: + name: get_it + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.1" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+4" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.4" + version: "1.7.0" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + node_interop: + dependency: transitive + description: + name: node_interop + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.1" + node_io: + dependency: transitive + description: + name: node_io + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + observable_ish: + dependency: transitive + description: + name: observable_ish + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.5" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + provider: + dependency: transitive + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "5.0.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + sealed_class: + dependency: "direct main" + description: + name: sealed_class + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" + sealed_class_generator: + dependency: "direct dev" + description: + name: sealed_class_generator + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.10+2" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.2" + version: "1.10.0" + stacked: + dependency: "direct main" + description: + name: stacked + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.8" + stacked_services: + dependency: "direct main" + description: + name: stacked_services + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.11" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" + version: "0.4.2" + toast: + dependency: "direct main" + description: + name: toast + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.5" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+15" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.1" sdks: - dart: ">=2.10.0-110 <=2.11.0-213.1.beta" + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.16.0" diff --git a/pubspec.yaml b/pubspec.yaml index 9d6f259..91fc45b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,15 +23,25 @@ environment: dependencies: flutter: sdk: flutter - - # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.0 + # utils + toast: ^0.1.5 + sealed_class: + # stacked + stacked: ^1.6.0 + stacked_services: ^0.4.3 + # Firebase + firebase_core: 1.4.0 + cloud_firestore: 2.4.0 + firebase_auth: 3.0.1 + firebase_database: 7.1.2 dev_dependencies: flutter_test: sdk: flutter + sealed_class_generator: # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec @@ -44,6 +54,8 @@ flutter: # the material Icons class. uses-material-design: true + assets: + - assets/images/ # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 8bb1b93..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:days_left/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/web/index.html b/web/index.html index b5e354f..8313006 100644 --- a/web/index.html +++ b/web/index.html @@ -33,6 +33,29 @@ + + + + + + + + + +