Working on login screen
This commit is contained in:
parent
1811c15b82
commit
cb3f316c4d
|
|
@ -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}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}''';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue