mirror of
				https://gitlab.com/comunic/comunicmobile
				synced 2025-11-04 04:04:18 +00:00 
			
		
		
		
	Can sign in user
This commit is contained in:
		@@ -1,3 +1,8 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
import 'package:comunic/models/login_tokens.dart';
 | 
			
		||||
import 'package:shared_preferences/shared_preferences.dart';
 | 
			
		||||
 | 
			
		||||
/// Accounts credentials helper
 | 
			
		||||
///
 | 
			
		||||
/// Stores current account tokens
 | 
			
		||||
@@ -8,7 +13,27 @@ class AccountCredentialsHelper {
 | 
			
		||||
 | 
			
		||||
  /// Checkout whether current user is signed in or not
 | 
			
		||||
  Future<bool> signedIn() async {
 | 
			
		||||
    return false; // TODO : implement
 | 
			
		||||
    return await get() != null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Set new login tokens
 | 
			
		||||
  Future<void> set(LoginTokens tokens) async {
 | 
			
		||||
    SharedPreferences prefs = await SharedPreferences.getInstance();
 | 
			
		||||
    await prefs.setString("login_tokens", tokens.toString());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Get current [LoginTokens]. Returns null if none or in case of failure
 | 
			
		||||
  Future<LoginTokens> get() async {
 | 
			
		||||
    try {
 | 
			
		||||
 | 
			
		||||
      SharedPreferences prefs = await SharedPreferences.getInstance();
 | 
			
		||||
      final string = prefs.getString("login_tokens");
 | 
			
		||||
      if(string == null) return null;
 | 
			
		||||
      return LoginTokens.fromJSON(jsonDecode(string));
 | 
			
		||||
 | 
			
		||||
    } on Exception catch(e){
 | 
			
		||||
      print(e.toString());
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								lib/helpers/account_helper.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/helpers/account_helper.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import 'package:comunic/helpers/account_credentials_helper.dart';
 | 
			
		||||
import 'package:comunic/helpers/api_helper.dart';
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:comunic/models/authentication_details.dart';
 | 
			
		||||
import 'package:comunic/models/login_tokens.dart';
 | 
			
		||||
 | 
			
		||||
/// Account helper
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
enum AuthResult {
 | 
			
		||||
  SUCCESS,
 | 
			
		||||
  TOO_MANY_ATTEMPTS,
 | 
			
		||||
  NETWORK_ERROR,
 | 
			
		||||
  INVALID_CREDENTIALS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AccountHelper {
 | 
			
		||||
  /// Sign in user
 | 
			
		||||
  Future<AuthResult> signIn(AuthenticationDetails auth) async {
 | 
			
		||||
    final request = APIRequest(uri: "account/login");
 | 
			
		||||
    request.addString("userMail", auth.email);
 | 
			
		||||
    request.addString("userPassword", auth.password);
 | 
			
		||||
 | 
			
		||||
    final response = await APIHelper().exec(request);
 | 
			
		||||
 | 
			
		||||
    // Handle errors
 | 
			
		||||
    if (response.code == 401)
 | 
			
		||||
      return AuthResult.INVALID_CREDENTIALS;
 | 
			
		||||
    else if (response.code == 429)
 | 
			
		||||
      return AuthResult.TOO_MANY_ATTEMPTS;
 | 
			
		||||
    else if (response.code != 200) return AuthResult.NETWORK_ERROR;
 | 
			
		||||
 | 
			
		||||
    //Save login tokens
 | 
			
		||||
    final tokensObj = response.getObject()["tokens"];
 | 
			
		||||
    await AccountCredentialsHelper()
 | 
			
		||||
        .set(LoginTokens(tokensObj["token1"], tokensObj["token2"]));
 | 
			
		||||
 | 
			
		||||
    return AuthResult.SUCCESS;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								lib/helpers/api_helper.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								lib/helpers/api_helper.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
 | 
			
		||||
import 'package:comunic/models/api_request.dart';
 | 
			
		||||
import 'package:comunic/models/api_response.dart';
 | 
			
		||||
import 'package:comunic/models/config.dart';
 | 
			
		||||
 | 
			
		||||
/// API Helper
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class APIHelper {
 | 
			
		||||
  final _httpClient = HttpClient();
 | 
			
		||||
 | 
			
		||||
  /// Execute a [request] on the server and returns a [APIResponse]
 | 
			
		||||
  ///
 | 
			
		||||
  /// This method should never throw but the response code of the [APIResponse]
 | 
			
		||||
  /// should be verified before accessing response content
 | 
			
		||||
  Future<APIResponse> exec(APIRequest request) async {
 | 
			
		||||
    try {
 | 
			
		||||
      //Add API tokens
 | 
			
		||||
      request.addString("serviceName", config().serviceName);
 | 
			
		||||
      request.addString("serviceToken", config().serviceToken);
 | 
			
		||||
 | 
			
		||||
      //Add user tokens (if required)
 | 
			
		||||
      if (request.needLogin) throw "Can add user tokens right now !";
 | 
			
		||||
 | 
			
		||||
      // Prepare request body
 | 
			
		||||
      String requestBody = "";
 | 
			
		||||
      request.args.forEach((key, value) => requestBody +=
 | 
			
		||||
          Uri.encodeQueryComponent(key) +
 | 
			
		||||
              "=" +
 | 
			
		||||
              Uri.encodeQueryComponent(value) +
 | 
			
		||||
              "&");
 | 
			
		||||
 | 
			
		||||
      List<int> bodyBytes = utf8.encode(requestBody);
 | 
			
		||||
 | 
			
		||||
      // Determine server URL
 | 
			
		||||
      final path = config().apiServerUri + request.uri;
 | 
			
		||||
      Uri url;
 | 
			
		||||
      if (!config().apiServerSecure)
 | 
			
		||||
        url = Uri.http(config().apiServerName, path);
 | 
			
		||||
      else
 | 
			
		||||
        url = Uri.https(config().apiServerName, path);
 | 
			
		||||
 | 
			
		||||
      //Connect to server
 | 
			
		||||
      final connection = await _httpClient.postUrl(url);
 | 
			
		||||
      connection.headers.set("Content-Length", bodyBytes.length.toString());
 | 
			
		||||
      connection.headers
 | 
			
		||||
          .set("Content-Type", "application/x-www-form-urlencoded");
 | 
			
		||||
      connection.add(bodyBytes);
 | 
			
		||||
 | 
			
		||||
      final response = await connection.close();
 | 
			
		||||
 | 
			
		||||
      if (response.statusCode != HttpStatus.ok)
 | 
			
		||||
        return APIResponse(response.statusCode, null);
 | 
			
		||||
 | 
			
		||||
      //Save & return response
 | 
			
		||||
      final responseBody = await response.transform(utf8.decoder).join();
 | 
			
		||||
 | 
			
		||||
      return APIResponse(response.statusCode, responseBody);
 | 
			
		||||
    } on Exception catch (e) {
 | 
			
		||||
      print(e.toString());
 | 
			
		||||
      print("Could not execute a request!");
 | 
			
		||||
      return APIResponse(-1, null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,8 @@
 | 
			
		||||
import 'package:comunic/helpers/account_credentials_helper.dart';
 | 
			
		||||
import 'package:comunic/ui/login_route.dart';
 | 
			
		||||
import 'package:comunic/models/config.dart';
 | 
			
		||||
import 'package:comunic/ui/routes/home_route.dart';
 | 
			
		||||
import 'package:comunic/ui/routes/login_route.dart';
 | 
			
		||||
import 'package:comunic/utils/ui_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Main file of the application
 | 
			
		||||
@@ -7,6 +10,16 @@ import 'package:flutter/material.dart';
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  /// Initialize application configuration
 | 
			
		||||
  ///
 | 
			
		||||
  // TODO : Adapt to production
 | 
			
		||||
  Config.set(Config(
 | 
			
		||||
      apiServerName: "devweb.local",
 | 
			
		||||
      apiServerUri: "/comunic/api/",
 | 
			
		||||
      apiServerSecure: false,
 | 
			
		||||
      serviceName: "ComunicFlutter",
 | 
			
		||||
      serviceToken: "G9sZCBmb3IgVWJ1bnR1CkNvbW1lbnRbbmVdPeCkieCkrOCkq"));
 | 
			
		||||
 | 
			
		||||
  runApp(ComunicApplication());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -15,20 +28,37 @@ class ComunicApplication extends StatelessWidget {
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
      debugShowCheckedModeBanner: false,
 | 
			
		||||
      // We need to know whether the user is signed in or not to continue
 | 
			
		||||
      home: FutureBuilder(
 | 
			
		||||
        future: AccountCredentialsHelper().signedIn(),
 | 
			
		||||
        builder: (b, AsyncSnapshot<bool> c){
 | 
			
		||||
          if(!c.hasData)
 | 
			
		||||
            return CircularProgressIndicator();
 | 
			
		||||
 | 
			
		||||
          else
 | 
			
		||||
            if(c.data)
 | 
			
		||||
              throw "Not supported yet !";
 | 
			
		||||
            else
 | 
			
		||||
              return LoginRoute();
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
      home: ComunicApplicationHome(),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ComunicApplicationHome extends StatefulWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  State<StatefulWidget> createState() => _ComunicApplicationHomeState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _ComunicApplicationHomeState extends State<ComunicApplicationHome> {
 | 
			
		||||
  bool _signedIn;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
 | 
			
		||||
    AccountCredentialsHelper().signedIn().then((v) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _signedIn = v;
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    if (_signedIn == null) return buildLoadingPage();
 | 
			
		||||
 | 
			
		||||
    if (_signedIn)
 | 
			
		||||
      return HomeRoute();
 | 
			
		||||
    else
 | 
			
		||||
      return LoginRoute();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								lib/models/api_request.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/models/api_request.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// API Request model
 | 
			
		||||
///
 | 
			
		||||
/// Contains all the information associated to an API request
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class APIRequest {
 | 
			
		||||
  final String uri;
 | 
			
		||||
  final bool needLogin;
 | 
			
		||||
  Map<String, String> args;
 | 
			
		||||
 | 
			
		||||
  APIRequest({
 | 
			
		||||
    @required this.uri,
 | 
			
		||||
    this.needLogin = false,
 | 
			
		||||
  })  : assert(uri != null),
 | 
			
		||||
        assert(needLogin != null),
 | 
			
		||||
        args = Map();
 | 
			
		||||
 | 
			
		||||
  void addString(String name, String value) => args[name] = value;
 | 
			
		||||
 | 
			
		||||
  void addInt(String name, int value) => args[name] = value.toString();
 | 
			
		||||
 | 
			
		||||
  void addBool(String name, bool value) =>
 | 
			
		||||
      args[name] = value ? "true" : "false";
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								lib/models/api_response.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/models/api_response.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
/// API response
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class APIResponse {
 | 
			
		||||
  final int code;
 | 
			
		||||
  final String content;
 | 
			
		||||
 | 
			
		||||
  const APIResponse(this.code, this.content) : assert(code != null);
 | 
			
		||||
 | 
			
		||||
  List<dynamic> getArray() => jsonDecode(this.content);
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> getObject() => jsonDecode(this.content);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										14
									
								
								lib/models/authentication_details.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								lib/models/authentication_details.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Authentication details
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class AuthenticationDetails {
 | 
			
		||||
  final String email;
 | 
			
		||||
  final String password;
 | 
			
		||||
 | 
			
		||||
  const AuthenticationDetails({@required this.email, @required this.password})
 | 
			
		||||
      : assert(email != null),
 | 
			
		||||
        assert(password != null);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										42
									
								
								lib/models/config.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								lib/models/config.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
import 'package:meta/meta.dart';
 | 
			
		||||
 | 
			
		||||
/// Application configuration model
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
/// Configuration class
 | 
			
		||||
class Config {
 | 
			
		||||
  final String apiServerName;
 | 
			
		||||
  final String apiServerUri;
 | 
			
		||||
  final bool apiServerSecure;
 | 
			
		||||
  final String serviceName;
 | 
			
		||||
  final String serviceToken;
 | 
			
		||||
 | 
			
		||||
  const Config(
 | 
			
		||||
      {@required this.apiServerName,
 | 
			
		||||
      @required this.apiServerUri,
 | 
			
		||||
      @required this.apiServerSecure,
 | 
			
		||||
      @required this.serviceName,
 | 
			
		||||
      @required this.serviceToken})
 | 
			
		||||
      : assert(apiServerName != null),
 | 
			
		||||
        assert(apiServerUri != null),
 | 
			
		||||
        assert(apiServerSecure != null),
 | 
			
		||||
        assert(serviceName != null),
 | 
			
		||||
        assert(serviceToken != null);
 | 
			
		||||
 | 
			
		||||
  /// Get and set static configuration
 | 
			
		||||
  static Config _config;
 | 
			
		||||
 | 
			
		||||
  static Config get() {
 | 
			
		||||
    return _config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static void set(Config conf) {
 | 
			
		||||
    _config = conf;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the current configuration of the application
 | 
			
		||||
Config config() {
 | 
			
		||||
  return Config.get();
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								lib/models/login_tokens.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								lib/models/login_tokens.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
 | 
			
		||||
/// Login tokens model
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class LoginTokens {
 | 
			
		||||
  final String tokenOne;
 | 
			
		||||
  final String tokenTwo;
 | 
			
		||||
 | 
			
		||||
  const LoginTokens(this.tokenOne, this.tokenTwo)
 | 
			
		||||
      : assert(tokenOne != null),
 | 
			
		||||
        assert(tokenTwo != null);
 | 
			
		||||
 | 
			
		||||
  LoginTokens.fromJSON(Map<String, dynamic> json)
 | 
			
		||||
      : tokenOne = json["token_one"],
 | 
			
		||||
        tokenTwo = json["token_two"];
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() {
 | 
			
		||||
    return jsonEncode({"token_one": tokenOne, "token_two": tokenTwo});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								lib/ui/routes/home_route.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								lib/ui/routes/home_route.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Main route of the application
 | 
			
		||||
///
 | 
			
		||||
/// @author Pierre HUBERT
 | 
			
		||||
 | 
			
		||||
class HomeRoute extends StatelessWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Text("Home route");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,9 @@
 | 
			
		||||
import 'package:comunic/helpers/account_helper.dart';
 | 
			
		||||
import 'package:comunic/models/authentication_details.dart';
 | 
			
		||||
import 'package:comunic/ui/routes/home_route.dart';
 | 
			
		||||
import 'package:comunic/utils/input_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/intl_utils.dart';
 | 
			
		||||
import 'package:comunic/utils/ui_utils.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// Login route
 | 
			
		||||
@@ -14,6 +18,7 @@ class LoginRoute extends StatefulWidget {
 | 
			
		||||
class _LoginRouteState extends State<LoginRoute> {
 | 
			
		||||
  String _currEmail;
 | 
			
		||||
  String _currPassword;
 | 
			
		||||
  AuthResult _authResult;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
@@ -35,26 +40,51 @@ class _LoginRouteState extends State<LoginRoute> {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Call this whenever the user request to perform login
 | 
			
		||||
  void _submitForm() {
 | 
			
		||||
  Future<void> _submitForm(BuildContext context) async {
 | 
			
		||||
    final loginResult = await AccountHelper().signIn(
 | 
			
		||||
        AuthenticationDetails(email: _currEmail, password: _currPassword));
 | 
			
		||||
 | 
			
		||||
    if (loginResult == AuthResult.SUCCESS)
 | 
			
		||||
      Navigator.pushReplacement(
 | 
			
		||||
          context, MaterialPageRoute(builder: (b) => HomeRoute()));
 | 
			
		||||
    else
 | 
			
		||||
      setState(() {
 | 
			
		||||
        _authResult = loginResult;
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Build error card
 | 
			
		||||
  Widget _buildErrorCard() {
 | 
			
		||||
    if (_authResult == null)
 | 
			
		||||
      return null;
 | 
			
		||||
 | 
			
		||||
    //Determine the right message
 | 
			
		||||
    final message = (_authResult == AuthResult.INVALID_CREDENTIALS ? tr(
 | 
			
		||||
        "Invalid credentials!") : (_authResult == AuthResult.TOO_MANY_ATTEMPTS
 | 
			
		||||
        ? tr("Too many unsuccessfull login attempts! Please try again later...")
 | 
			
		||||
        : tr("A network error occured!")));
 | 
			
		||||
 | 
			
		||||
    return buildErrorCard(message);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /// Build login form
 | 
			
		||||
  Widget _buildLoginForm(){
 | 
			
		||||
  Widget _buildLoginForm() {
 | 
			
		||||
    return SingleChildScrollView(
 | 
			
		||||
      child: Padding(
 | 
			
		||||
        padding: const EdgeInsets.all(8.0),
 | 
			
		||||
        child: Column(
 | 
			
		||||
          children: <Widget>[
 | 
			
		||||
            Text(tr("Please sign into your Comunic account: ")),
 | 
			
		||||
            Container(
 | 
			
		||||
              child: _buildErrorCard(),
 | 
			
		||||
            ),
 | 
			
		||||
            //Email address
 | 
			
		||||
            TextField(
 | 
			
		||||
              keyboardType: TextInputType.emailAddress,
 | 
			
		||||
              decoration: InputDecoration(
 | 
			
		||||
                  labelText: tr("Email address"),
 | 
			
		||||
                  alignLabelWithHint: true,
 | 
			
		||||
                  errorText:
 | 
			
		||||
                  _currEmail.length > 0 && !validateEmail(_currEmail)
 | 
			
		||||
                  errorText: _currEmail.length > 0 && !validateEmail(_currEmail)
 | 
			
		||||
                      ? tr("Invalid email address!")
 | 
			
		||||
                      : null),
 | 
			
		||||
              onChanged: _emailChanged,
 | 
			
		||||
@@ -72,7 +102,9 @@ class _LoginRouteState extends State<LoginRoute> {
 | 
			
		||||
 | 
			
		||||
            RaisedButton(
 | 
			
		||||
              child: Text(tr("Sign in")),
 | 
			
		||||
              onPressed: !validateEmail(_currEmail) || _currPassword.length < 3 ? null : _submitForm,
 | 
			
		||||
              onPressed: !validateEmail(_currEmail) || _currPassword.length < 3
 | 
			
		||||
                  ? null
 | 
			
		||||
                  : () => _submitForm(context),
 | 
			
		||||
            )
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
@@ -83,10 +115,9 @@ class _LoginRouteState extends State<LoginRoute> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        title: Text("Comunic"),
 | 
			
		||||
      ),
 | 
			
		||||
      body: _buildLoginForm()
 | 
			
		||||
    );
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          title: Text("Comunic"),
 | 
			
		||||
        ),
 | 
			
		||||
        body: _buildLoginForm());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								lib/utils/ui_utils.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/utils/ui_utils.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
/// User interface utilities
 | 
			
		||||
 | 
			
		||||
/// Build and return a full loading page
 | 
			
		||||
Widget buildLoadingPage() {
 | 
			
		||||
  return Scaffold(
 | 
			
		||||
    body: Center(
 | 
			
		||||
      child: CircularProgressIndicator(),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Build and return an error card
 | 
			
		||||
Widget buildErrorCard(String message) {
 | 
			
		||||
  return Card(
 | 
			
		||||
    elevation: 2.0,
 | 
			
		||||
    color: Colors.red,
 | 
			
		||||
    child: Padding(
 | 
			
		||||
      padding: const EdgeInsets.all(8.0),
 | 
			
		||||
      child: Row(
 | 
			
		||||
        children: <Widget>[
 | 
			
		||||
          Padding(
 | 
			
		||||
            padding: const EdgeInsets.only(right: 8.0),
 | 
			
		||||
            child: Icon(
 | 
			
		||||
              Icons.error,
 | 
			
		||||
              color: Colors.white,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          Flexible(
 | 
			
		||||
            child: Text(
 | 
			
		||||
              message,
 | 
			
		||||
              style: TextStyle(color: Colors.white),
 | 
			
		||||
              maxLines: null,
 | 
			
		||||
            ),
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -81,6 +81,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.1"
 | 
			
		||||
  shared_preferences:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: shared_preferences
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.5.2"
 | 
			
		||||
  sky_engine:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description: flutter
 | 
			
		||||
@@ -144,3 +151,4 @@ packages:
 | 
			
		||||
    version: "2.0.8"
 | 
			
		||||
sdks:
 | 
			
		||||
  dart: ">=2.1.0 <3.0.0"
 | 
			
		||||
  flutter: ">=0.1.4 <2.0.0"
 | 
			
		||||
 
 | 
			
		||||
@@ -24,6 +24,9 @@ dependencies:
 | 
			
		||||
  # Use with the CupertinoIcons class for iOS style icons.
 | 
			
		||||
  cupertino_icons: ^0.1.2
 | 
			
		||||
 | 
			
		||||
  # Preferences are useful for a lot of things (ex: login tokens)
 | 
			
		||||
  shared_preferences: ^0.5.2
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user