mirror of
https://github.com/hmalik144/days_left_flutter.git
synced 2025-12-10 03:05:21 +00:00
- 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:
13
lib/Utils/ViewUtils.dart
Normal file
13
lib/Utils/ViewUtils.dart
Normal 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
41
lib/base/BaseModel.dart
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
32
lib/base/BaseStatelessWidget.dart
Normal file
32
lib/base/BaseStatelessWidget.dart
Normal 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());
|
||||
}
|
||||
}
|
||||
12
lib/constants/constants.dart
Normal file
12
lib/constants/constants.dart
Normal 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";
|
||||
49
lib/data/FirebaseAuthData.dart
Normal file
49
lib/data/FirebaseAuthData.dart
Normal 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
20
lib/data/ViewState.dart
Normal 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
17
lib/locator.dart
Normal 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
336
lib/login/LoginScreen.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
122
lib/main.dart
122
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<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.
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
54
lib/managers/DialogManager.dart
Normal file
54
lib/managers/DialogManager.dart
Normal 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));
|
||||
},
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
26
lib/model/DialogModels.dart
Normal file
26
lib/model/DialogModels.dart
Normal 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,
|
||||
});
|
||||
}
|
||||
26
lib/model/UserProfileDetails.dart
Normal file
26
lib/model/UserProfileDetails.dart
Normal 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"];
|
||||
}
|
||||
8
lib/screens/OverviewScreen.dart
Normal file
8
lib/screens/OverviewScreen.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
class OverviewScreen extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
54
lib/services/DialogService.dart
Normal file
54
lib/services/DialogService.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
21
lib/services/NavigationService.dart
Normal file
21
lib/services/NavigationService.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
14
lib/viewmodels/LoginViewModel.dart
Normal file
14
lib/viewmodels/LoginViewModel.dart
Normal 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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
16
lib/viewmodels/UserAuthViewModel.dart
Normal file
16
lib/viewmodels/UserAuthViewModel.dart
Normal 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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
23
lib/widgets/ButtonWidget.dart
Normal file
23
lib/widgets/ButtonWidget.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
74
lib/widgets/CodeEnterWidget.dart
Normal file
74
lib/widgets/CodeEnterWidget.dart
Normal 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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
))),
|
||||
);
|
||||
}
|
||||
}
|
||||
85
lib/widgets/DisplayUserDetails.dart
Normal file
85
lib/widgets/DisplayUserDetails.dart
Normal 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"),
|
||||
]);
|
||||
}
|
||||
}
|
||||
61
lib/widgets/LoaderWidget.dart
Normal file
61
lib/widgets/LoaderWidget.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
26
lib/widgets/TextDetailWidget.dart
Normal file
26
lib/widgets/TextDetailWidget.dart
Normal 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
28
lib/widgets/router.dart
Normal 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);
|
||||
}
|
||||
361
pubspec.lock
361
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"
|
||||
|
||||
16
pubspec.yaml
16
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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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 () {
|
||||
|
||||
Reference in New Issue
Block a user