Working on login screen

This commit is contained in:
unknown 2018-05-17 13:49:32 -07:00
parent 1811c15b82
commit cb3f316c4d
9 changed files with 279 additions and 89 deletions

View File

@ -1 +1,23 @@
export '../../data/models/entities.dart'; export 'package:invoiceninja/data/models/entities.dart';
class User {
final String token;
final String id;
User(this.token, this.id);
Map<String, dynamic> toJSON() => <String, dynamic>{
'token': this.token,
'id': this.id
};
factory User.fromJSON(Map<String, dynamic> json) => new User(
json['token'],
json['id'],
);
@override
String toString() {
return '{token: $token, id: $id}';
}
}

View File

@ -1,15 +1,15 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart'; import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart'; import 'package:redux/redux.dart';
import 'redux/app/app_state.dart'; import 'package:invoiceninja/redux/app/app_state.dart';
import 'package:invoiceninja/ui/dashboard_screen.dart'; import 'package:invoiceninja/ui/dashboard_screen.dart';
import 'package:invoiceninja/ui/client/client_list.dart'; import 'package:invoiceninja/ui/client/client_list.dart';
import 'package:invoiceninja/ui/product/product_screen.dart'; import 'package:invoiceninja/ui/product/product_screen.dart';
import 'package:invoiceninja/ui/app/login_screen.dart'; import 'package:invoiceninja/ui/auth/login_screen.dart';
import 'routes.dart'; import 'package:invoiceninja/routes.dart';
import 'redux/product/product_actions.dart'; import 'package:invoiceninja/redux/product/product_actions.dart';
import 'redux/product/product_middleware.dart'; import 'package:invoiceninja/redux/product/product_middleware.dart';
import 'redux/app/app_reducer.dart'; import 'package:invoiceninja/redux/app/app_reducer.dart';
void main() { void main() {

View File

@ -1,11 +1,24 @@
import 'package:invoiceninja/redux/app/app_state.dart'; import 'package:invoiceninja/redux/app/app_state.dart';
import 'package:invoiceninja/redux/app/loading_reducer.dart'; import 'package:invoiceninja/redux/app/loading_reducer.dart';
import 'package:invoiceninja/redux/product/product_reducer.dart'; import 'package:invoiceninja/redux/product/product_reducer.dart';
import 'package:invoiceninja/redux/auth/auth_reducer.dart';
// We create the State reducer by combining many smaller reducers into one! // We create the State reducer by combining many smaller reducers into one!
AppState appReducer(AppState state, action) { AppState appReducer(AppState state, action) {
/*
if (action is PersistLoadedAction<AppState>) {
return action.state ?? state;
} else {
return new AppState(
auth: authReducer(state.auth, action),
);
}
*/
return AppState( return AppState(
isLoading: loadingReducer(state.isLoading, action), isLoading: loadingReducer(state.isLoading, action),
auth: authReducer(state.auth, action),
products: productsReducer(state.products, action), products: productsReducer(state.products, action),
); );
} }

View File

@ -1,23 +1,39 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:invoiceninja/data/models/models.dart'; import 'package:invoiceninja/data/models/models.dart';
import 'package:invoiceninja/redux/auth/auth_state.dart';
@immutable @immutable
class AppState { class AppState {
final bool isLoading; final bool isLoading;
final AuthState auth;
final List<ProductEntity> products; final List<ProductEntity> products;
AppState( AppState(
{this.isLoading = false, {this.isLoading = false,
AuthState auth,
this.products = const []}); this.products = const []});
factory AppState.loading() => AppState(isLoading: true); factory AppState.loading() => AppState(isLoading: true);
/*
static AppState rehydrationJSON(dynamic json) => new AppState(
auth: new AuthState.fromJSON(json['auth'])
);
Map<String, dynamic> toJson() => {
'auth': auth.toJSON()
};
*/
AppState copyWith({ AppState copyWith({
bool isLoading, bool isLoading,
AuthState auth,
List<ProductEntity> products, List<ProductEntity> products,
}) { }) {
return AppState( return AppState(
isLoading: isLoading ?? this.isLoading, isLoading: isLoading ?? this.isLoading,
auth: auth ?? this.auth,
products: products ?? this.products, products: products ?? this.products,
); );
} }
@ -25,6 +41,7 @@ class AppState {
@override @override
int get hashCode => int get hashCode =>
products.hashCode ^ products.hashCode ^
auth.hashCode ^
isLoading.hashCode; isLoading.hashCode;
@override @override
@ -33,6 +50,7 @@ class AppState {
other is AppState && other is AppState &&
runtimeType == other.runtimeType && runtimeType == other.runtimeType &&
products == other.products && products == other.products &&
auth == other.auth &&
isLoading == other.isLoading; isLoading == other.isLoading;
@override @override

View File

@ -0,0 +1,39 @@
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:invoiceninja/redux/app/app_state.dart';
import 'package:invoiceninja/data/models/models.dart';
class UserLoginRequest {}
class UserLoginSuccess {
final User user;
UserLoginSuccess(this.user);
}
class UserLoginFailure {
final String error;
UserLoginFailure(this.error);
}
class UserLogout {}
final Function login = (BuildContext context, String username, String password) {
return (Store<AppState> store) {
store.dispatch(new UserLoginRequest());
if (username == 'asd' && password == 'asd') {
store.dispatch(new UserLoginSuccess(new User('placeholder_token', 'placeholder_id')));
Navigator.of(context).pushNamedAndRemoveUntil('/main', (_) => false);
} else {
store.dispatch(new UserLoginFailure('Username or password were incorrect.'));
}
};
};
final Function logout = (BuildContext context) {
return (Store<AppState> store) {
store.dispatch(new UserLogout());
Navigator.of(context).pushNamedAndRemoveUntil('/login', (_) => false);
};
};

View File

@ -0,0 +1,38 @@
import 'package:redux/redux.dart';
import 'package:invoiceninja/redux/auth/auth_actions.dart';
import 'package:invoiceninja/redux/auth/auth_state.dart';
Reducer<AuthState> authReducer = combineReducers([
new TypedReducer<AuthState, UserLoginRequest>(userLoginRequestReducer),
new TypedReducer<AuthState, UserLoginSuccess>(userLoginSuccessReducer),
new TypedReducer<AuthState, UserLoginFailure>(userLoginFailureReducer),
new TypedReducer<AuthState, UserLogout>(userLogoutReducer),
]);
AuthState userLoginRequestReducer(AuthState auth, UserLoginRequest action) {
return new AuthState().copyWith(
isAuthenticated: false,
isAuthenticating: true,
);
}
AuthState userLoginSuccessReducer(AuthState auth, UserLoginSuccess action) {
return new AuthState().copyWith(
isAuthenticated: true,
isAuthenticating: false,
user: action.user
);
}
AuthState userLoginFailureReducer(AuthState auth, UserLoginFailure action) {
return new AuthState().copyWith(
isAuthenticated: false,
isAuthenticating: false,
error: action.error
);
}
AuthState userLogoutReducer(AuthState auth, UserLogout action) {
return new AuthState();
}

View File

@ -0,0 +1,60 @@
import 'package:meta/meta.dart';
import 'package:invoiceninja/data/models/models.dart';
@immutable
class AuthState {
// properties
final bool isAuthenticated;
final bool isAuthenticating;
final User user;
final String error;
// constructor with default
AuthState({
this.isAuthenticated = false,
this.isAuthenticating = false,
this.user,
this.error,
});
// allows to modify AuthState parameters while cloning previous ones
AuthState copyWith({
bool isAuthenticated,
bool isAuthenticating,
String error,
User user
}) {
return new AuthState(
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
isAuthenticating: isAuthenticating ?? this.isAuthenticating,
error: error ?? this.error,
user: user ?? this.user,
);
}
factory AuthState.fromJSON(Map<String, dynamic> json) => new AuthState(
isAuthenticated: json['isAuthenticated'],
isAuthenticating: json['isAuthenticating'],
error: json['error'],
user: json['user'] == null ? null : new User.fromJSON(json['user']),
);
Map<String, dynamic> toJSON() => <String, dynamic>{
'isAuthenticated': this.isAuthenticated,
'isAuthenticating': this.isAuthenticating,
'user': this.user == null ? null : this.user.toJSON(),
'error': this.error,
};
@override
String toString() {
return '''{
isAuthenticated: $isAuthenticated,
isAuthenticating: $isAuthenticating,
user: $user,
error: $error
}''';
}
}

View File

@ -1,82 +0,0 @@
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
static String tag = 'login-page';
@override
_LoginScreenState createState() => new _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
@override
Widget build(BuildContext context) {
final email = TextFormField(
keyboardType: TextInputType.emailAddress,
autofocus: false,
validator: (value) {
if (value.isEmpty) {
return 'This field is required';
}
},
decoration: InputDecoration(
labelText: 'Email',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
),
);
final password = TextFormField(
autofocus: false,
obscureText: true,
decoration: InputDecoration(
labelText: 'Password',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
),
);
final loginButton = Padding(
padding: EdgeInsets.symmetric(vertical: 16.0),
child: Material(
//borderRadius: BorderRadius.circular(30.0),
shadowColor: Colors.lightBlueAccent.shade100,
elevation: 5.0,
child: MaterialButton(
minWidth: 200.0,
height: 42.0,
onPressed: () {
//Navigator.of(context).pushNamed(HomeScreen.tag);
},
color: Colors.lightBlueAccent,
child: Text('Log In', style: TextStyle(color: Colors.white)),
),
),
);
/*
final forgotLabel = FlatButton(
child: Text(
'Forgot password?',
style: TextStyle(color: Colors.black54),
),
onPressed: () {},
);
*/
return Scaffold(
//backgroundColor: Colors.white,
body: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
SizedBox(height: 48.0),
email,
SizedBox(height: 8.0),
password,
SizedBox(height: 24.0),
loginButton,
//forgotLabel
],
),
),
);
}
}

View File

@ -0,0 +1,82 @@
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:invoiceninja/redux/app/app_state.dart';
import 'package:invoiceninja/redux/auth/auth_actions.dart';
class LoginScreen extends StatefulWidget {
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _formKey = GlobalKey<FormState>();
String _username;
String _password;
void _submit() {
final form = _formKey.currentState;
if (form.validate()) {
form.save();
}
}
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, dynamic>(
converter: (Store<AppState> store) {
return (BuildContext context, String username, String password) =>
store.dispatch(login(context, username, password));
}, builder: (BuildContext context, loginAction) {
return Scaffold(
body: Form(
key: _formKey,
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0, top: 40.0),
children: [
TextFormField(
decoration: InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
//contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
//autofocus: false,
validator: (val) =>
val.isEmpty ? 'Please enter your email.' : null,
onSaved: (val) => _username = val,
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
validator: (val) =>
val.isEmpty ? 'Please enter your password.' : null,
onSaved: (val) => _password = val,
obscureText: true,
),
Padding(
padding: EdgeInsets.only(top: 20.0),
child: Material(
//borderRadius: BorderRadius.circular(30.0),
shadowColor: Colors.lightBlueAccent.shade100,
elevation: 5.0,
child: MaterialButton(
minWidth: 200.0,
height: 42.0,
onPressed: () {
_submit();
loginAction(context, _username, _password);
//Navigator.of(context).pushNamed(HomeScreen.tag);
},
color: Colors.lightBlueAccent,
child:
Text('Log In', style: TextStyle(color: Colors.white)),
),
),
),
],
),
),
);
});
}
}