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

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