- Introduction on base view model and stateless widget classes

- Implementation of firebase
- Implementation of dependency injection

Took 11 hours 26 minutes
This commit is contained in:
2021-07-24 20:54:18 +01:00
parent 3d438ffc5b
commit f83071fa78
27 changed files with 1438 additions and 150 deletions

13
lib/Utils/ViewUtils.dart Normal file
View File

@@ -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
);
}
}

41
lib/base/BaseModel.dart Normal file
View File

@@ -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<dynamic> func) {
onStart();
func.then((value) => onSuccess(value)).catchError((error) {
print(error);
onError(error);
});
}
void handleStream(Stream<dynamic> stream) {
stream.listen((event) {
onSuccess(event);
}).onError((handleError) {
onError(handleError);
});
}
}

View File

@@ -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<BaseModel>.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());
}
}

View File

@@ -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";

View File

@@ -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<UserCredential> signUpWithEmailAndPassword(
String email, String password) {
return firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
}
Future<UserCredential> signIn(String email, String password) {
return firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
}
Future<void> resetPassword(String email) {
return firebaseAuth.sendPasswordResetEmail(email: email);
}
Future<void> signOut() {
return firebaseAuth.signOut();
}
Future<void> updateUsername(String newEmail) {
return getUser().updateEmail(newEmail);
}
Future<void> updatePassword(
String email, String password, String newPassword) async {
UserCredential credentials = await signIn(email, password);
return credentials.user.updatePassword(newPassword);
}
Future<void> updateProfile({String displayName, String photoURL}) {
return getUser()
.updateProfile(displayName: displayName, photoURL: photoURL);
}
}

20
lib/data/ViewState.dart Normal file
View File

@@ -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);
}

17
lib/locator.dart Normal file
View File

@@ -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));
}

336
lib/login/LoginScreen.dart Normal file
View File

@@ -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<LoginViewModel>();
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: <Widget>[
_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: <Widget>[
Container(
margin:
EdgeInsets.symmetric(horizontal: widthOfScreen * 0.1),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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: <Widget>[
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,
),
],
),
);
}
}

View File

@@ -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<UserAuthViewModel>();
@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'),
);
}
}
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<MyHomePage> {
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: <Widget>[
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.
home: _userAuthViewModel.loggedIn ? OverviewScreen() : LoginScreen(),
onGenerateRoute: generateRoute,
builder: (context, child) => Navigator(
key: locator<DialogService>().dialogNavigationKey,
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => DialogManager(child: child)),
),
navigatorKey: locator<NavigationService>().navigationKey,
);
}
}

View File

@@ -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<DialogManager> {
DialogService _dialogService = locator<DialogService>();
@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: <Widget>[
if (isConfirmationDialog)
FlatButton(
child: Text(request.cancelTitle),
onPressed: () {
_dialogService
.dialogComplete(DialogResponse(confirmed: false));
},
),
FlatButton(
child: Text(request.buttonTitle),
onPressed: () {
_dialogService
.dialogComplete(DialogResponse(confirmed: true));
},
),
],
));
}
}

View File

@@ -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,
});
}

View File

@@ -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<dynamic, dynamic> snapshot) :
name = snapshot["name"],
dob = snapshot["dob"],
postcode = snapshot["postcode"],
email = snapshot["email"],
phone = snapshot["phone"],
sex = snapshot["sex"];
}

View File

@@ -0,0 +1,8 @@
import 'package:flutter/cupertino.dart';
class OverviewScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container();
}
}

View File

@@ -0,0 +1,54 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:days_left/model/DialogModels.dart';
class DialogService {
GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
Function(DialogRequest) _showDialogListener;
Completer<DialogResponse> _dialogCompleter;
GlobalKey<NavigatorState> 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<DialogResponse> showDialog({
String title,
String description,
String buttonTitle = 'Ok',
}) {
_dialogCompleter = Completer<DialogResponse>();
_showDialogListener(DialogRequest(
title: title,
description: description,
buttonTitle: buttonTitle,
));
return _dialogCompleter.future;
}
/// Shows a confirmation dialog
Future<DialogResponse> showConfirmationDialog(
{String title,
String description,
String confirmationTitle = 'Ok',
String cancelTitle = 'Cancel'}) {
_dialogCompleter = Completer<DialogResponse>();
_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;
}
}

View File

@@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
class NavigationService {
GlobalKey<NavigatorState> _navigationKey = GlobalKey<NavigatorState>();
GlobalKey<NavigatorState> get navigationKey => _navigationKey;
void pop() {
return _navigationKey.currentState.pop();
}
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
return _navigationKey.currentState
.pushNamed(routeName, arguments: arguments);
}
Future<dynamic> navigateToAndClearStack(String routeName, {dynamic arguments}) {
return _navigationKey.currentState
.pushNamedAndRemoveUntil(routeName, (r) => false, arguments: arguments);
}
}

View File

@@ -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));
}
}

View File

@@ -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();
});
}
}

View File

@@ -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),
),
);
}
}

View File

@@ -0,0 +1,74 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class CodeEnterWidget extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
final GlobalKey<ScaffoldState> 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: <Widget>[
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'),
),
),
],
),
))),
);
}
}

View File

@@ -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<FormState>();
final NavigationService _navigationService = locator<NavigationService>();
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: <Widget>[
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: <Widget>[
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"),
]);
}
}

View File

@@ -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<LoaderWidget> {
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<LoaderWidgetState>();
void changeState(){
loaderKey.currentState.changeState();
}
Widget LoaderWidgeted({Widget body}){
return LoaderWidget(body: body, key: loaderKey);
}
}
abstract class LoaderStatefulWidget<T extends StatefulWidget> extends State<T>{
final loaderKey = GlobalKey<LoaderWidgetState>();
void changeState(){
loaderKey.currentState.changeState();
}
Widget LoaderWidgeted(Widget body, Key key){
return LoaderWidget(body: body, key: key);
}
}

View File

@@ -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: <Widget>[
Text(
labelText,
style: TextStyle(color: Colors.blue),
),
Text(
bodyText,
style: TextStyle(color: Colors.black),
)
]);
}
}

28
lib/widgets/router.dart Normal file
View File

@@ -0,0 +1,28 @@
import 'package:days_left/login/login_screen_6.dart';
import 'package:flutter/material.dart';
import '../constants/constants.dart';
Route<dynamic> 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);
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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);
});
}

View File

@@ -33,6 +33,29 @@
<!-- This script installs service_worker.js to provide PWA functionality to
application. For more information, see:
https://developers.google.com/web/fundamentals/primers/service-workers -->
<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.17.1/firebase-firestore.js"></script>
<!-- https://firebase.google.com/docs/web/setup#available-libraries -->
<script src="https://www.gstatic.com/firebasejs/8.0.0/firebase-analytics.js"></script>
<script>
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "AIzaSyDZk-H3yoMupYsTt85kXS3h3GCe2F8MjmU",
authDomain: "farmr-8a496.firebaseapp.com",
databaseURL: "https://farmr-8a496.firebaseio.com",
projectId: "farmr-8a496",
storageBucket: "farmr-8a496.appspot.com",
messagingSenderId: "640643168912",
appId: "1:640643168912:web:d3a99b921e48d1dd2bb615"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
</script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('flutter-first-frame', function () {