Aitrainer_app 1.1.0

Login and Registraion
This commit is contained in:
Bossanyi Tibor
2020-06-12 21:34:15 +02:00
parent 2c10480ffd
commit c8f0ced24d
41 changed files with 612 additions and 768 deletions
+117 -9
View File
@@ -1,23 +1,32 @@
import 'package:aitrainer_app/model/auth.dart';
import 'package:aitrainer_app/service/api.dart';
import 'package:aitrainer_app/service/customer_service.dart';
import 'package:aitrainer_app/view/customer_modify_page.dart';
import 'package:aitrainer_app/view/customer_new_page.dart';
import 'package:aitrainer_app/view/login.dart';
import 'package:aitrainer_app/view/exercise_new_page.dart';
import 'package:aitrainer_app/view/exercise_type_modify_page.dart';
import 'package:aitrainer_app/view/exercise_type_new_page.dart';
import 'package:aitrainer_app/view/registration.dart';
import 'package:aitrainer_app/viewmodel/exercise_changing_view_model.dart';
import 'package:flutter/material.dart';
import 'package:aitrainer_app/view/customer_list_page.dart';
import 'package:aitrainer_app/view/exercise_type_list_page.dart';
import 'package:aitrainer_app/widgets/nav_drawer.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(
ChangeNotifierProvider(
MultiProvider(
// Initialize the model in the builder. That way, Provider
// can own Models's lifecycle, making sure to call `dispose`
// when not needed anymore.
create: (context) => ExerciseChangingViewModel(null),
providers: [
ChangeNotifierProvider(create: (context) => ExerciseChangingViewModel(null)),
],
child: AitrainerApp(),
),
);
@@ -48,6 +57,8 @@ class AitrainerApp extends StatelessWidget {
'exerciseTypeNewPage': (context) => ExerciseTypeNewPage(),
'exerciseTypeModifyPage': (context) => ExerciseTypeModifyPage(),
'exerciseNewPage': (context) => ExerciseNewPage(),
'login': (context) => LoginPage(),
'registration': (context) => RegistrationPage(),
},
initialRoute: 'home',
title: 'Aitrainer Demo',
@@ -59,20 +70,117 @@ class AitrainerApp extends StatelessWidget {
}
}
class AitrainerHome extends StatelessWidget {
final _biggerFont = const TextStyle(fontSize: 24.0, color: Color.fromRGBO(94, 123, 122, 0.9));
class AitrainerHome extends StatefulWidget {
static final String routeName = 'home';
@override
State<StatefulWidget> createState() {
return new _HomePageState();
}
}
class _HomePageState extends State<AitrainerHome> {
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Auth _auth = Auth();
SharedPreferences _sharedPreferences;
var _authToken;
@override
void initState() {
super.initState();
_fetchSessionAndNavigate();
}
_fetchSessionAndNavigate() async {
_sharedPreferences = await _prefs;
String authToken = Auth.getToken(_sharedPreferences);
var customerId = _sharedPreferences.getInt(Auth.customerIdKey);
if ( _auth.firstLoad ) {
_fetchToken(_sharedPreferences);
}
}
/*
Auth flow of the user, see auth.dart
*/
_fetchToken(SharedPreferences prefs) async {
var responseJson = await APIClient.authenticateUser(
Auth.username,
Auth.password
);
if(responseJson['error'] != null) {
showSnackBar(_scaffoldKey, responseJson['error']);
print("************** Here big error - no authentication");
} else if (responseJson['token'] != null) {
prefs.setString(Auth.authTokenKey, responseJson['token']);
Auth auth = Auth();
auth.authToken = responseJson['token'];
if ( prefs.get(Auth.customerIdKey) == null ) {
print("************** Registration");
// registration
Navigator.of(context).pushNamed('registration');
prefs.setBool(Auth.isRegisteredKey, true);
} else {
DateTime now = DateTime.now();
DateTime lastStoreDate = DateTime.parse( prefs.get(Auth.lastStoreDateKey) );
DateTime minStoreDate = now.add(Duration(days: -10));
if ( lastStoreDate == null ||
lastStoreDate.difference(minStoreDate) > Duration(days: 10) ||
prefs.get(Auth.isLoggedInKey) == null ||
prefs.get(Auth.isLoggedInKey) == false ) {
print("************* Login");
Navigator.of(context).pushNamed('login');
} else {
print("************** Store SharedPreferences");
// get API customer
await CustomerApi().getCustomer( prefs.getInt(Auth.customerIdKey));
}
}
setState(() {
_auth.firstLoad = false;
_authToken = auth.authToken;
_sharedPreferences.setString(Auth.authTokenKey, _authToken);
});
}
}
static showSnackBar(GlobalKey<ScaffoldState> scaffoldKey, String message) {
scaffoldKey.currentState.showSnackBar(
new SnackBar(
content: new Text(message ?? 'You are offline'),
)
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
drawer: NavDrawer(),
appBar: AppBar(
title: Text('Menu'),
title: Text('Home'),
backgroundColor: Colors.transparent
),
body: Center(
child: Text(
'Welcome to the AITRAINER',
style: _biggerFont),
body: Image.asset('asset/WT01_loading_layers.png',
fit: BoxFit.fill,
height: double.infinity,
width: double.infinity,
alignment: Alignment.center,
),
);
}
}
+105
View File
@@ -0,0 +1,105 @@
import 'package:aitrainer_app/model/customer.dart';
import 'package:shared_preferences/shared_preferences.dart';
enum SharePrefsChange {
login,
registration,
logout,
}
/*
Auth flow of the app
1. During the login screen the authentication will be executed
- if not successful: message: Network error, try again later
- if successful
- get the stored shared preferences and customer id
- if customer_id not present -> registration page
- if present, check if the expiration_date > 10 days -> login page
- else get the API customer by the stored customer_id
- After registration / login store the preferences:
- AuthToken
- customer_id
- last_store_date
- is_registered
- is_logged_in
*/
class Auth {
static final Auth _singleton = Auth._internal();
// Keys to store and fetch data from SharedPreferences
static final String authTokenKey = 'auth_token';
static final String customerIdKey = 'customer_id';
static final String lastStoreDateKey = 'last_date';
static final String isRegisteredKey = 'is_registered';
static final String isLoggedInKey = 'is_logged_in';
static final String _baseUrl = 'http://andio.eu:8888/api/';
static final String username = 'bosi';
static final String password = 'andio2009';
String authToken = "";
Customer userLoggedIn;
bool firstLoad = true;
factory Auth() {
return _singleton;
}
Auth._internal();
String getAuthToken() {
return this.authToken;
}
static String getToken(SharedPreferences prefs) {
return prefs.getString(authTokenKey);
}
static String getBaseUrl() {
return _baseUrl;
}
afterRegistration(Customer customer) {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
userLoggedIn = customer;
setPreferences(prefs, SharePrefsChange.registration, customer.customerId);
}
afterLogin(Customer customer) {
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
userLoggedIn = customer;
setPreferences(prefs, SharePrefsChange.login, customer.customerId);
}
logout(){
userLoggedIn = null;
authToken = "";
//firstLoad = true;
Future<SharedPreferences> prefs = SharedPreferences.getInstance();
setPreferences(prefs, SharePrefsChange.logout, 0);
}
setPreferences(Future<SharedPreferences> prefs,
SharePrefsChange type,
int customerId) async {
SharedPreferences sharedPreferences;
sharedPreferences = await prefs;
DateTime now = DateTime.now();
sharedPreferences.setString(Auth.lastStoreDateKey, now.toString());
if ( type == SharePrefsChange.registration ) {
sharedPreferences.setInt(Auth.customerIdKey, customerId);
sharedPreferences.setBool(Auth.isRegisteredKey, true);
sharedPreferences.setBool(Auth.isLoggedInKey, true);
} else if ( type == SharePrefsChange.login ) {
sharedPreferences.setInt(Auth.customerIdKey, customerId);
sharedPreferences.setBool(Auth.isLoggedInKey, true);
} else if ( type == SharePrefsChange.login ) {
sharedPreferences.setBool(Auth.isLoggedInKey, false);
sharedPreferences.setInt(Auth.customerIdKey, 0);
}
}
}
+4 -2
View File
@@ -6,9 +6,10 @@ class Customer {
int age;
String active;
int customerId;
String password;
Customer({this.customerId, this.name, this.firstName, this.email, this.sex, this.age, this.active});
Customer({this.customerId, this.name, this.firstName, this.email, this.sex, this.age, this.active, this.password});
Customer.fromJson(Map json) {
this.customerId = json['customer_id'];
@@ -27,6 +28,7 @@ class Customer {
"email": email,
"age": age,
"sex": sex,
"active": 'Y'
"active": 'Y',
"password": password,
};
}
+15
View File
@@ -0,0 +1,15 @@
class User {
String email;
String password;
int customerId;
User({this.customerId, this.email, this.password});
Map<String, dynamic> toJson() =>
{
"username": email,
"password": password,
};
}
+69 -7
View File
@@ -1,12 +1,19 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:http/http.dart' as http;
import 'package:aitrainer_app/model/auth.dart';
class APIClient {
final String _baseUrl = 'http://andio.eu:8888/api/';
class APIClient extends ChangeNotifier {
Future<String> get(String endPoint, String param) async {
final url = _baseUrl + endPoint + param;
final response = await http.get(url, headers: {'Content-Type': 'application/json'});
final url = Auth.getBaseUrl() + endPoint + param;
Auth auth = Auth();
final response = await http.get(url,
headers: {
'Content-Type': 'application/json',
'Authorization' : "Bearer " + auth.getAuthToken() }
);
notifyListeners();
if(response.statusCode == 200) {
return utf8.decode(response.bodyBytes);
} else {
@@ -15,14 +22,69 @@ class APIClient {
}
Future<String> post(String endPoint, String body) async {
final url = _baseUrl + endPoint;
print(" ------------ http/post endpoint $endPoint body $body");
final url = Auth.getBaseUrl() + endPoint;
print(" ------------ http/post endpoint $endPoint body $body - url: $url ");
Auth auth = Auth();
final response = await http.post(url,
headers: {'Content-Type': 'application/json'},
headers: {
'Content-Type': 'application/json',
'Authorization' : "Bearer " + auth.getAuthToken()
},
body: body
);
print(" ------------ response: " + response.body.toString());
notifyListeners();
return response.body;
}
static dynamic authenticateUser(String email, String password) async {
var uri = Auth.getBaseUrl() + "authenticate";
try {
final body = '{"username":"$email", "password":"$password"}';
final response = await http.post(
uri,
headers: {
'Authorization': '1',
'Content-Type': 'application/json'
},
body: body
);
final responseCode = response.statusCode;
if ( responseCode != 200) {
return { "error" : "Authentication error, total failure", };
}
final responseJson = json.decode(response.body);
return responseJson;
} catch (exception) {
return { "error" : "Network error, try again later"};
}
}
static fetch(var authToken, var endPoint) async {
var uri = Auth.getBaseUrl() + endPoint;
try {
final response = await http.get(
uri,
headers: {
'Authorization': authToken
},
);
final responseJson = json.decode(response.body);
return responseJson;
} catch (exception) {
print(exception);
if(exception.toString().contains('SocketException')) {
return 'NetworkError';
} else {
return null;
}
}
}
}
+36 -1
View File
@@ -1,7 +1,8 @@
import 'dart:convert';
import 'package:aitrainer_app/model/customer.dart';
import 'package:aitrainer_app/model/user.dart';
import 'package:aitrainer_app/service/api.dart';
import 'package:aitrainer_app/model/auth.dart';
class CustomerApi {
final APIClient _client=new APIClient();
@@ -29,4 +30,38 @@ class CustomerApi {
"customers",
body);
}
Future<void> addUser(User user) async {
String body = JsonEncoder().convert(user.toJson());
print(" ===== register new user: " + body );
final String responseBody = await _client.post(
"registration",
body);
Auth auth = Auth();
Customer customer = Customer.fromJson(jsonDecode(responseBody));
auth.afterRegistration(customer);
}
Future<void> getUser(User user) async {
String body = JsonEncoder().convert(user.toJson());
print(" ===== login the user: " + body );
final String responseBody = await _client.post(
"login",
body);
Auth auth = Auth();
Customer customer = Customer.fromJson(jsonDecode(responseBody));
auth.afterRegistration(customer);
}
Future<void> getCustomer(int customerId) async {
String body = "";
print(" ===== get the customer by id: " + customerId.toString() );
final String responseBody = await _client.get(
"customers/"+customerId.toString(),
body);
Auth auth = Auth();
Customer customer = Customer.fromJson(jsonDecode(responseBody));
auth.afterRegistration(customer);
}
}
+16
View File
@@ -0,0 +1,16 @@
import 'dart:collection';
class Common {
static String toJson( Map<String, String> map ) {
String rc = "{";
map.forEach((key, value) {
rc += "'$key':'$value'";
});
rc += "}";
return rc;
}
}
+81
View File
@@ -0,0 +1,81 @@
import 'package:aitrainer_app/viewmodel/user_changing_view_model.dart';
import 'package:aitrainer_app/viewmodel/user_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:aitrainer_app/widgets/nav_drawer.dart';
class LoginPage extends StatefulWidget{
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State {
final UserViewModel user = UserViewModel();
bool _obscureText = true;
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
UserChangingViewModel model = UserChangingViewModel(user);
user.createNew();
return Scaffold(
drawer: NavDrawer(),
appBar: AppBar(
title: Text('Login'),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email',
icon: const Padding(
padding:const EdgeInsets.only(left: 20.0, top: 50.0),
child: const Icon(Icons.people)
)
),
validator: (String input) {
RegExp exp = new RegExp(r"[\w._]+\@[\w._]+.[a-z]+",
caseSensitive: false,
multiLine: false,);
String ret = exp.hasMatch(input) == true ?
null:
"Please type an email address";
return ret;
},
onChanged: (input) => user.setEmail(input),
),
new TextFormField(
decoration: const InputDecoration(
labelText: 'Password',
icon: const Padding(
padding: const EdgeInsets.only(left: 20.0, top: 15.0),
child: const Icon(Icons.lock))),
validator: (val) => val.length < 6 ? 'Password too short.' : null,
obscureText: _obscureText,
onChanged: (input) => user.setPassword(input),
),
new InkWell(
child: new Text('SignUp'),
onTap: () => Navigator.of(context).pushNamed('registration'),
),
new FloatingActionButton(
child: Icon(Icons.cloud_done,),
onPressed:() => {
if (_formKey.currentState.validate()) {
model = UserChangingViewModel(user),
model.getUser(),
Navigator.pop(context),
}
})
])
),
),
);
}
}
+85
View File
@@ -0,0 +1,85 @@
import 'package:aitrainer_app/viewmodel/user_changing_view_model.dart';
import 'package:aitrainer_app/viewmodel/user_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:aitrainer_app/widgets/nav_drawer.dart';
class RegistrationPage extends StatefulWidget{
_RegistrationPageState createState() => _RegistrationPageState();
}
class _RegistrationPageState extends State {
final UserViewModel user = UserViewModel();
bool _obscureText = true;
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
UserChangingViewModel model = UserChangingViewModel(user);
user.createNew();
return Scaffold(
drawer: NavDrawer(),
appBar: AppBar(
title: Text('Registration'),
),
body: Center(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Email',
icon: const Padding(
padding:const EdgeInsets.only(left: 20.0, top: 50.0),
child: const Icon(Icons.people)
)
),
/* validator: (String input) {
RegExp exp = new RegExp(r"[\w._]+\@[\w._]+.[a-z]+",
caseSensitive: false,
multiLine: false,);
String ret = exp.hasMatch(input) == true ?
null:
"Please type an email address";
return ret;
},*/
onChanged: (input) => user.setEmail(input),
),
new TextFormField(
decoration: const InputDecoration(
labelText: 'Password',
icon: const Padding(
padding: const EdgeInsets.only(left: 20.0, top: 15.0),
child: const Icon(Icons.lock))),
/* validator: (String input) {
String rc = input.length < 4 ? 'Password too short.' : null;
return rc;
}, */
onChanged: (input) => user.setPassword(input),
obscureText: _obscureText,
),
new InkWell(
child: new Text('I have an account'),
onTap: () => Navigator.of(context).pushNamed('login'),
),
new FloatingActionButton(
child: Icon(Icons.cloud_done,),
onPressed:() => {
if (_formKey.currentState.validate()) {
model = UserChangingViewModel(user),
model.addUser(),
Navigator.pop(context),
}
}
)
])
),
),
);
}
}
@@ -0,0 +1,22 @@
import 'package:aitrainer_app/model/user.dart';
import 'package:aitrainer_app/service/customer_service.dart';
import 'package:aitrainer_app/viewmodel/user_view_model.dart';
import 'package:flutter/cupertino.dart';
class UserChangingViewModel extends ChangeNotifier {
UserViewModel userViewModel = UserViewModel();
UserChangingViewModel(userViewModel) {
this.userViewModel = userViewModel;
}
Future<void> addUser() async {
final User modelUser = userViewModel.getUser();
await CustomerApi().addUser(modelUser);
}
Future<void> getUser() async {
final User modelUser = userViewModel.getUser();
await CustomerApi().getUser(modelUser);
}
}
+23
View File
@@ -0,0 +1,23 @@
import 'package:aitrainer_app/model/user.dart';
class UserViewModel {
User user;
UserViewModel({this.user});
setEmail(String email) {
this.user.email = email;
}
setPassword(String password) {
this.user.password = password;
}
User getUser() {
return this.user;
}
createNew() {
this.user = User();
}
}
+1 -1
View File
@@ -57,7 +57,7 @@ class _CustomerListWidget extends State<CustomerListWidget> {
),
),
onTap: () { setState( () {
customer.visibleDetails = true;
customer.visibleDetails = customer.visibleDetails ? false : true;
});
},
);
+15
View File
@@ -1,6 +1,8 @@
import 'package:aitrainer_app/model/auth.dart';
import 'package:flutter/material.dart';
class NavDrawer extends StatelessWidget {
final Auth auth = Auth();
@override
Widget build(BuildContext context) {
return Drawer(
@@ -37,6 +39,19 @@ class NavDrawer extends StatelessWidget {
title: Text("TRAINING!"),
onTap: () => Navigator.of(context).pushNamed('exerciseNewPage'),
),
ListTile(
leading: Icon(Icons.perm_identity),
title: Text('Login'),
onTap: () => Navigator.of(context).pushNamed('login'),
),
ListTile(
leading: Icon(Icons.cancel),
title: Text("Logout"),
onTap: () => {
auth.logout(),
Navigator.of(context).pushNamed('home'),
}
),
],
),
);