diff --git a/android/app/build.gradle b/android/app/build.gradle index f6d57d0..4ce2067 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,7 +34,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.example.chatapp" - minSdkVersion 16 + minSdkVersion 18 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig index 592ceee..e8efba1 100644 --- a/ios/Flutter/Debug.xcconfig +++ b/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig index 592ceee..399e934 100644 --- a/ios/Flutter/Release.xcconfig +++ b/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..1e8c3c9 --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '9.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/main.dart b/lib/main.dart index cc5703d..ce20a76 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,23 @@ import 'package:chatapp/src/routes/routes.dart'; +import 'package:chatapp/src/services/auth.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - title: 'Chat App', - routes: appRoutes, - initialRoute: 'users', + return MultiProvider( + providers: [ + ChangeNotifierProvider(create: ( _ ) => AuthService() ) + ], + child: MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Chat App', + routes: appRoutes, + initialRoute: 'loading', + ), ); } } \ No newline at end of file diff --git a/lib/src/global/enviroment.dart b/lib/src/global/enviroment.dart new file mode 100644 index 0000000..78b5f5c --- /dev/null +++ b/lib/src/global/enviroment.dart @@ -0,0 +1,7 @@ + +import 'dart:io'; + +class Environment { + static String apiURL = 'http://192.168.0.22:3000/api'; + static String socketUrl = 'http://192.168.0.22:3000'; +} \ No newline at end of file diff --git a/lib/src/helpers/showAlert.dart b/lib/src/helpers/showAlert.dart new file mode 100644 index 0000000..ce51f1d --- /dev/null +++ b/lib/src/helpers/showAlert.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + + +Future showSimpleAlert( BuildContext context, String title, String subtitle ){ + if( Platform.isAndroid ){ + return showDialog( + context: context, + builder: ( _ ) => AlertDialog( + title: Text('$title'), + content: Text('$subtitle'), + actions: [ + MaterialButton( + child: Text('Aceptar'), + onPressed: () => Navigator.of(context).pop(), + ) + ], + ) + ); + } else { + return showDialog( + context: context, + builder: ( _ ) => CupertinoAlertDialog( + title: Text('$title'), + content: Text('$subtitle'), + actions: [ + CupertinoDialogAction( + child: Text('Aceptar'), + onPressed: () => Navigator.of(context).pop(), + ) + ], + ) + ); + } +} \ No newline at end of file diff --git a/lib/src/models/loginResponse.dart b/lib/src/models/loginResponse.dart new file mode 100644 index 0000000..8fc103b --- /dev/null +++ b/lib/src/models/loginResponse.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'package:chatapp/src/models/user.dart'; + +LoginResponse loginResponseFromJson(String str) => LoginResponse.fromJson(json.decode(str)); +String loginResponseToJson(LoginResponse data) => json.encode(data.toJson()); + +class LoginResponse { + LoginResponse({ + this.ok, + this.user, + this.token, + }); + + bool ok; + User user; + String token; + + factory LoginResponse.fromJson(Map json) => LoginResponse( + ok: json["ok"], + user: User.fromJson(json["user"]), + token: json["token"], + ); + + Map toJson() => { + "ok": ok, + "user": user.toJson(), + "token": token, + }; +} \ No newline at end of file diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart index 70f9ac2..5c36181 100644 --- a/lib/src/models/user.dart +++ b/lib/src/models/user.dart @@ -1,18 +1,27 @@ - class User { - - final bool isOnline; - final String email; - final String name; - final String uid; - User({ - this.isOnline, - this.email, - this.name, - this.uid + this.isOnline, + this.name, + this.email, + this.uid, }); - + bool isOnline; + String name; + String email; + String uid; + + factory User.fromJson(Map json) => User( + isOnline: json["isOnline"], + name: json["name"], + email: json["email"], + uid: json["uid"] + ); + Map toJson() => { + "isOnline": isOnline, + "name": name, + "email": email, + "uid": uid + }; } \ No newline at end of file diff --git a/lib/src/pages/loading.dart b/lib/src/pages/loading.dart index 1436ce2..a4f52dd 100644 --- a/lib/src/pages/loading.dart +++ b/lib/src/pages/loading.dart @@ -1,10 +1,43 @@ +import 'package:chatapp/src/pages/login.dart'; +import 'package:chatapp/src/pages/users.dart'; +import 'package:chatapp/src/services/auth.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class LoadingPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - + body: FutureBuilder( + future: checkLoginState( context ), + builder: (context, snapshot) { + return Center( + child: CircularProgressIndicator() + ); + } + ), ); } + + Future checkLoginState( BuildContext context ) async { + final AuthService authService = Provider.of(context, listen: false); + final isValidateToken = await authService.isLoggedIn(); + + if( isValidateToken ){ + // TODO: Conectar al socket service + Navigator.of(context).pushReplacement( + PageRouteBuilder( + pageBuilder: ( _, __, ___ ) => UsersPage(), + transitionDuration: Duration(milliseconds: 0) + ) + ); + } else { + Navigator.of(context).pushReplacement( + PageRouteBuilder( + pageBuilder: ( _, __, ___ ) => LoginPage(), + transitionDuration: Duration(milliseconds: 0) + ) + ); + } + } } \ No newline at end of file diff --git a/lib/src/pages/login.dart b/lib/src/pages/login.dart index 8802e46..b833591 100644 --- a/lib/src/pages/login.dart +++ b/lib/src/pages/login.dart @@ -1,8 +1,11 @@ +import 'package:chatapp/src/helpers/showAlert.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:chatapp/src/widgets/customInput.dart'; import 'package:chatapp/src/widgets/customButton.dart'; import 'package:chatapp/src/widgets/labels.dart'; import 'package:chatapp/src/widgets/logo.dart'; +import 'package:chatapp/src/services/auth.dart'; class LoginPage extends StatelessWidget { @override @@ -47,6 +50,7 @@ class __FormState extends State<_Form> { @override Widget build(BuildContext context) { + final AuthService authServices = Provider.of( context ); return Container( margin: EdgeInsets.only(top: 40), padding: EdgeInsets.symmetric(horizontal: 50), @@ -66,10 +70,17 @@ class __FormState extends State<_Form> { ), CustomButton( title: 'Iniciar Sesión', - callback: (){ - print( _txtEmail.text ); - print( _txtPassword.text ); - }, + callback: authServices.isAuthNow ? null : () async { + FocusScope.of(context).unfocus(); + final loginSuccess = await authServices.login( _txtEmail.text.trim(), _txtPassword.text ); + if( loginSuccess ){ + // TODO: Conectar a nuestro socket server + Navigator.of(context).pushReplacementNamed('users'); + // TODO: Navegar a otra pantalla + } else { + showSimpleAlert(context, 'Login incorrecto', 'Email o contraseña incorrecto.'); + } + } ) ], ), diff --git a/lib/src/pages/register.dart b/lib/src/pages/register.dart index acdbd84..cd4d42c 100644 --- a/lib/src/pages/register.dart +++ b/lib/src/pages/register.dart @@ -1,8 +1,11 @@ +import 'package:chatapp/src/helpers/showAlert.dart'; +import 'package:chatapp/src/services/auth.dart'; import 'package:flutter/material.dart'; import 'package:chatapp/src/widgets/customInput.dart'; import 'package:chatapp/src/widgets/customButton.dart'; import 'package:chatapp/src/widgets/labels.dart'; import 'package:chatapp/src/widgets/logo.dart'; +import 'package:provider/provider.dart'; class RegisterPage extends StatelessWidget { @override @@ -48,6 +51,7 @@ class __FormState extends State<_Form> { @override Widget build(BuildContext context) { + final AuthService authService = Provider.of( context ); return Container( margin: EdgeInsets.only(top: 40), padding: EdgeInsets.symmetric(horizontal: 50), @@ -73,9 +77,13 @@ class __FormState extends State<_Form> { ), CustomButton( title: 'Crear cuenta', - callback: (){ - print( _txtEmail.text ); - print( _txtPassword.text ); + callback: authService.isAuthNow ? null : () async { + final registerSuccess = await authService.register(_txtName.text, _txtEmail.text.trim(), _txtPassword.text); + if( registerSuccess ){ + showSimpleAlert(context, 'Completado', 'Se ha registrado con éxito.').then((value) => Navigator.of(context).pushReplacementNamed('login')); + } else { + showSimpleAlert(context, 'Error', 'Ya existe una cuenta con este correo electrónico'); + } }, ) ], diff --git a/lib/src/pages/users.dart b/lib/src/pages/users.dart index dc4c165..a1998cc 100644 --- a/lib/src/pages/users.dart +++ b/lib/src/pages/users.dart @@ -1,4 +1,6 @@ +import 'package:chatapp/src/services/auth.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; import 'package:chatapp/src/models/user.dart'; @@ -21,14 +23,20 @@ class _UsersPageState extends State { @override Widget build(BuildContext context) { + final authService = Provider.of(context); + final user = authService.user; return Scaffold( appBar: AppBar( - title: Text('Mi nombre', style: TextStyle(color: Colors.black87)), + title: Text('${user.name}', style: TextStyle(color: Colors.black87)), elevation: 1, backgroundColor: Colors.white, leading: IconButton( icon: Icon( Icons.exit_to_app, color: Colors.black87 ), - onPressed: (){}, + onPressed: (){ + // TODO: Desconectar el socket server + Navigator.of(context).pushReplacementNamed('login'); + AuthService.deleteToken(); + }, ), actions: [ Container( diff --git a/lib/src/services/auth.dart b/lib/src/services/auth.dart new file mode 100644 index 0000000..75359c7 --- /dev/null +++ b/lib/src/services/auth.dart @@ -0,0 +1,110 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:http/http.dart' as http; +import 'package:chatapp/src/global/enviroment.dart'; +import 'package:chatapp/src/models/loginResponse.dart'; +import 'package:chatapp/src/models/user.dart'; + +class AuthService with ChangeNotifier { + + User user; + final _storage = FlutterSecureStorage(); + + + bool _isAuthNow = false; + bool get isAuthNow => this._isAuthNow; + set setAuthNow( bool value ) { + this._isAuthNow = value; + notifyListeners(); + } + + Future login( String email, String password ) async { + this.setAuthNow = true; + + final data = { + 'email': email, + 'password': password + }; + + print('${Environment.apiURL}/login/'); + + final response = await http.post( + '${Environment.apiURL}/login/', + body: json.encode( data ), + headers: {'Content-Type': 'application/json'} + ); + + if( response.statusCode == 200 ){ + final loginResponse = loginResponseFromJson( response.body ); + this.user = loginResponse.user; + await this._saveToken( loginResponse.token ); + this.setAuthNow = false; + return true; + } else { + this.setAuthNow = false; + return false; + } + } + + Future register( String name, String email, String password ) async { + this.setAuthNow = true; + + final data = { + 'name': name, + 'email': email, + 'password': password + }; + + final response = await http.post( + '${Environment.apiURL}/login/new/', + body: json.encode( data ), + headers: {'Content-Type': 'application/json'} + ); + + if( response.statusCode == 200 ){ + final loginResponse = loginResponseFromJson( response.body ); + this.user = loginResponse.user; + await this._saveToken( loginResponse.token ); + this.setAuthNow = false; + return true; + } else { + this.setAuthNow = false; + return false; + } + } + + // Getters and Setters static + static Future getToken() async { + final _storage = FlutterSecureStorage(); + final token = await _storage.read(key: 'ChatApp_AuthToken'); + return token; + } + static Future deleteToken() async { + final _storage = FlutterSecureStorage(); + await _storage.delete(key: 'ChatApp_AuthToken'); + } + Future isLoggedIn() async { + final token = await this._storage.read(key: 'ChatApp_AuthToken'); + final response = await http.get( + '${Environment.apiURL}/login/renew', + headers: { + 'Content-Type': 'application/json', + 'x-token': token, + } + ); + if( response.statusCode == 200 ){ + final loginResponse = loginResponseFromJson( response.body ); + this.user = loginResponse.user; + await this._saveToken( loginResponse.token ); + print(response.body); + return true; + } else { + this._logout(); + return false; + } + + } + Future _saveToken( String token ) async => await _storage.write(key: 'ChatApp_AuthToken', value: token); + Future _logout() async => await _storage.delete(key: 'ChatApp_AuthToken'); +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index fe5738c..9af0ef1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -62,11 +62,32 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_secure_storage: + dependency: "direct main" + description: + name: flutter_secure_storage + url: "https://pub.dartlang.org" + source: hosted + version: "3.3.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + http: + dependency: "direct main" + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.4" matcher: dependency: transitive description: @@ -81,6 +102,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.8" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" path: dependency: transitive description: @@ -88,6 +116,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.0" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.0" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.3.2+2" pull_to_refresh: dependency: "direct main" description: @@ -158,4 +200,4 @@ packages: version: "2.0.8" sdks: dart: ">=2.9.0-14.0.dev <3.0.0" - flutter: ">=0.1.4 <2.0.0" + flutter: ">=1.16.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index d0eac58..e9d9b97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,20 +1,8 @@ name: chatapp description: A new Flutter project. -# The following line prevents the package from being accidentally published to -# pub.dev using `pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html version: 1.0.0+1 environment: @@ -24,11 +12,11 @@ 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: ^0.1.3 pull_to_refresh: ^1.6.1 + provider: ^4.3.2+2 + http: ^0.12.2 + flutter_secure_storage: ^3.3.3 dev_dependencies: flutter_test: