From cc66d141d44fce68c877dcb4e412b5904bd63811 Mon Sep 17 00:00:00 2001 From: Dat PHAM HOANG Date: Wed, 4 May 2022 20:15:40 +0700 Subject: [PATCH] TF-518 Apply new UI for login in mobile --- assets/images/ic_eye.svg | 4 + assets/images/ic_eye_off.svg | 4 + .../extensions/color_extension.dart | 12 +- .../presentation/resources/image_paths.dart | 2 + .../presentation/utils/responsive_utils.dart | 6 + .../views/text/input_decoration_builder.dart | 32 ++- .../login/presentation/login_controller.dart | 18 +- .../login/presentation/login_form_type.dart | 3 + .../login/presentation/login_view.dart | 260 +++++++++++++----- .../login/presentation/state/login_state.dart | 6 + .../login_input_decoration_builder.dart | 8 +- .../widgets/login_text_input_builder.dart | 196 +++++++++++++ lib/main/localizations/app_localizations.dart | 13 +- 13 files changed, 465 insertions(+), 99 deletions(-) create mode 100644 assets/images/ic_eye.svg create mode 100644 assets/images/ic_eye_off.svg create mode 100644 lib/features/login/presentation/login_form_type.dart create mode 100644 lib/features/login/presentation/widgets/login_text_input_builder.dart diff --git a/assets/images/ic_eye.svg b/assets/images/ic_eye.svg new file mode 100644 index 000000000..bf34f9665 --- /dev/null +++ b/assets/images/ic_eye.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/ic_eye_off.svg b/assets/images/ic_eye_off.svg new file mode 100644 index 000000000..a5d96b8ea --- /dev/null +++ b/assets/images/ic_eye_off.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index aad3162b7..4ceb29fc3 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -10,9 +10,15 @@ extension AppColor on Color { static const textFieldTextColor = Color(0xFF7E869B); static const textFieldLabelColor = Color(0xFF7E869B); static const textFieldHintColor = Color(0xFF757575); - static const textFieldBorderColor = Color(0xfff2f1fd); - static const textFieldFocusedBorderColor = Color(0xFF837DFF); - static const textFieldErrorBorderColor = Color(0xffFF5858); + static const textFieldBorderColor = Color(0xfff2f3f5); + static const textFieldFocusedBorderColor = Color(0xFF007AFF); + static const loginTextFieldBorderColor = Color(0xFFF2F3F5); + static const textFieldErrorBorderColor = Color(0xffE64646); + static const loginTextFieldErrorBorder = Color(0xffE64646); + static const loginTextFieldFocusedBorder = Color(0xFF007AFF); + static const loginTextFieldHintColor = Color(0xff818C99); + static const loginTextFieldBackgroundColor = Color(0xFFF2F3F5); + static const loginTextFieldBackgroundErrorColor = Color(0xFFFAEBEB); static const buttonColor = Color(0xFF837DFF); static const appColor = Color(0xFF3840F7); static const nameUserColor = Color(0xFF182952); diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 875527c74..855c58493 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -104,6 +104,8 @@ class ImagePaths { String get icDeleteDialogIdentity => _getImagePath('ic_delete_dialog_identity.svg'); String get icDeleteDialogFailed => _getImagePath('ic_delete_dialog_failed.svg'); String get icEdit => _getImagePath('ic_edit.svg'); + String get icEye => _getImagePath('ic_eye.svg'); + String get icEyeOff => _getImagePath('ic_eye_off.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/utils/responsive_utils.dart b/core/lib/presentation/utils/responsive_utils.dart index ce7c9fef2..e1724650a 100644 --- a/core/lib/presentation/utils/responsive_utils.dart +++ b/core/lib/presentation/utils/responsive_utils.dart @@ -3,6 +3,8 @@ import 'package:get/get.dart'; class ResponsiveUtils { + final int heightShortest = 600; + final int minDesktopWidth = 1200; final int minTabletWidth = 600; final int minTabletLargeWidth = 900; @@ -51,4 +53,8 @@ class ResponsiveUtils { isMobile(context) ? _loginTextFieldWidthSmallScreen : _loginTextFieldWidthLargeScreen; double getWidthLoginButton() => _loginButtonWidth; + + bool isHeightShortest(BuildContext context) { + return MediaQuery.of(context).size.shortestSide < 600; + } } \ No newline at end of file diff --git a/core/lib/presentation/views/text/input_decoration_builder.dart b/core/lib/presentation/views/text/input_decoration_builder.dart index 82223eb69..1b9846262 100644 --- a/core/lib/presentation/views/text/input_decoration_builder.dart +++ b/core/lib/presentation/views/text/input_decoration_builder.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; abstract class InputDecorationBuilder { String? prefixText; + TextStyle? prefixStyle; String? labelText; TextStyle? labelStyle; String? hintText; @@ -9,38 +10,43 @@ abstract class InputDecorationBuilder { EdgeInsets? contentPadding; OutlineInputBorder? enabledBorder; OutlineInputBorder? errorBorder; + OutlineInputBorder? focusBorder; String? errorText; TextStyle? errorTextStyle; - void setPrefixText(String newPrefixText) { + void setPrefixText(String? newPrefixText) { prefixText = newPrefixText; } - void setLabelText(String newLabelText) { + void setPrefixStyle(TextStyle? newPrefixStyle) { + prefixStyle = newPrefixStyle; + } + + void setLabelText(String? newLabelText) { labelText = newLabelText; } - void setLabelStyle(TextStyle newLabelStyle) { + void setLabelStyle(TextStyle? newLabelStyle) { labelStyle = newLabelStyle; } - void setHintText(String newHintText) { + void setHintText(String? newHintText) { hintText = newHintText; } - void setHintStyle(TextStyle newHintStyle) { + void setHintStyle(TextStyle? newHintStyle) { hintStyle = newHintStyle; } - void setContentPadding(EdgeInsets newContentPadding) { + void setContentPadding(EdgeInsets? newContentPadding) { contentPadding = newContentPadding; } - void setEnabledBorder(OutlineInputBorder newEnabledBorder) { + void setEnabledBorder(OutlineInputBorder? newEnabledBorder) { enabledBorder = newEnabledBorder; } - void setErrorBorder(OutlineInputBorder newErrorBorder) { + void setErrorBorder(OutlineInputBorder? newErrorBorder) { errorBorder = newErrorBorder; } @@ -48,10 +54,14 @@ abstract class InputDecorationBuilder { errorText = newText; } - void setErrorTextStyle(TextStyle newStyle) { + void setErrorTextStyle(TextStyle? newStyle) { errorTextStyle = newStyle; } + void setFocusBorder(OutlineInputBorder focusBorder) { + focusBorder = focusBorder; + } + InputDecoration build() { return InputDecoration( prefixText: prefixText, @@ -63,6 +73,8 @@ abstract class InputDecorationBuilder { errorBorder: errorBorder, errorText: errorText, errorStyle: errorTextStyle, - enabledBorder: enabledBorder); + enabledBorder: enabledBorder, + focusedBorder: focusBorder + ); } } \ No newline at end of file diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index b36cdd422..3caba72c5 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -6,6 +6,7 @@ import 'package:get/get.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart'; import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart'; +import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/state/login_state.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; @@ -26,6 +27,7 @@ class LoginController extends GetxController { ); var loginState = LoginState(Right(LoginInitAction())).obs; + final loginFormType = LoginFormType.baseUrlForm.obs; String? _urlText; String? _userNameText; @@ -43,6 +45,20 @@ class LoginController extends GetxController { Password? _parsePassword(String? password) => password != null ? Password(password.trim()) : null; + void handleNextInUrlInputFormPress() { + completeInputUrlAction(); + } + + void handleBackInCredentialForm() { + loginState.value = LoginState(Right(LoginInitAction())); + loginFormType.value = LoginFormType.baseUrlForm; + } + + void completeInputUrlAction() { + loginState.value = LoginState(Right(InputUrlCompletion())); + loginFormType.value = LoginFormType.credentialForm; + } + void handleLoginPressed() { final baseUri = kIsWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText); final userName = _parseUserName(_userNameText); @@ -82,7 +98,7 @@ class LoginController extends GetxController { @override void onClose() { - urlInputController.dispose(); + urlInputController.clear(); super.onClose(); } } \ No newline at end of file diff --git a/lib/features/login/presentation/login_form_type.dart b/lib/features/login/presentation/login_form_type.dart new file mode 100644 index 000000000..a5eb9d79b --- /dev/null +++ b/lib/features/login/presentation/login_form_type.dart @@ -0,0 +1,3 @@ +enum LoginFormType { + none, baseUrlForm, credentialForm +} \ No newline at end of file diff --git a/lib/features/login/presentation/login_view.dart b/lib/features/login/presentation/login_view.dart index 0a4877560..80b3ce229 100644 --- a/lib/features/login/presentation/login_view.dart +++ b/lib/features/login/presentation/login_view.dart @@ -1,18 +1,20 @@ import 'package:core/core.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart'; import 'package:tmail_ui_user/features/login/presentation/login_controller.dart'; +import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart'; import 'package:tmail_ui_user/features/login/presentation/state/login_state.dart'; import 'package:tmail_ui_user/features/login/presentation/widgets/login_input_decoration_builder.dart'; +import 'package:tmail_ui_user/features/login/presentation/widgets/login_text_input_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class LoginView extends GetWidget { final loginController = Get.find(); final imagePaths = Get.find(); - final responsiveUtils = Get.find(); + final _responsiveUtils = Get.find(); final keyboardUtils = Get.find(); LoginView({Key? key}) : super(key: key); @@ -20,97 +22,161 @@ class LoginView extends GetWidget { @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: false, + resizeToAvoidBottomInset: true, backgroundColor: AppColor.primaryLightColor, body: GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: SafeArea( - child: Center( - child: SingleChildScrollView( - reverse: true, - child: Padding( - padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), - child: Container( - alignment: Alignment.center, - padding: const EdgeInsets.only(top: 40), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _buildSlogan(context), - Obx(() => _buildLoginMessage(context, loginController.loginState.value)), - if (!kIsWeb) _buildUrlInput(context), - _buildUserNameInput(context), - _buildPasswordInput(context), - Obx(() => loginController.loginState.value.viewState.fold( - (failure) => _buildLoginButton(context), - (success) => success is LoginLoadingAction ? _buildLoadingCircularProgress() : _buildLoginButton(context))), - ], - ), - ), + child: _supportScrollForm(context) + ? Stack( + children: [ + Center(child: SingleChildScrollView(child: _buildCenterForm(context), scrollDirection: Axis.vertical)), + Obx(() { + return loginController.loginFormType.value == LoginFormType.credentialForm + ? _buildBackButton(context) + : const SizedBox.shrink(); + }) + ] + ) + : Stack( + children: [ + _buildCenterForm(context), + Obx(() { + return loginController.loginFormType.value == LoginFormType.credentialForm + ? _buildBackButton(context) + : const SizedBox.shrink(); + }) + ] + ), + ), + )); + } + + Widget _buildCenterForm(BuildContext context) { + return Center( + child: ConstrainedBox( + constraints: _supportScrollForm(context) + ? const BoxConstraints(minWidth: 240, maxWidth: 400) + : const BoxConstraints(minWidth: double.infinity), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsets.only( + top: _responsiveUtils.isHeightShortest(context) ? 64 : 0), + child: Text( + AppLocalizations.of(context).login, + style: const TextStyle(fontSize: 32, color: AppColor.colorNameEmail, fontWeight: FontWeight.w900) ) ), - ), + Obx(() => _buildLoginMessage(context, loginController.loginState.value)), + Obx(() { + switch (controller.loginFormType.value) { + case LoginFormType.baseUrlForm: + return _buildUrlInput(context); + case LoginFormType.credentialForm: + return _buildInputCredentialForm(context); + default: + return const SizedBox.shrink(); + } + }), + Obx(() { + switch (controller.loginFormType.value) { + case LoginFormType.baseUrlForm: + return _supportScrollForm(context) + ? _buildNextButton(context) + : _buildExpandedButton(context, _buildNextButton(context)); + case LoginFormType.credentialForm: + return Obx(() => loginController.loginState.value.viewState.fold( + (failure) => _buildLoginButtonInContext(context), + (success) => success is LoginLoadingAction + ? _buildLoadingCircularProgress() + : _buildLoginButtonInContext(context))); + default: + return const SizedBox.shrink(); + } + }) + ] ), - ), + ) ); } - Widget _buildSlogan(BuildContext context) { - return (SloganBuilder() - ..setSloganText(AppLocalizations.of(context).login_text_slogan) - ..setSloganTextAlign(TextAlign.center) - ..setSloganTextStyle(const TextStyle(color: AppColor.primaryColor, fontSize: 16, fontWeight: FontWeight.w700)) - ..setLogo(imagePaths.icLogoTMail)) - .build(); + Widget _buildBackButton(BuildContext context) { + return Positioned( + left: 12, + top: 12, + child: IconButton( + key: const Key('login_arrow_back_button'), + onPressed: () => controller.handleBackInCredentialForm(), + icon: SvgPicture.asset( + imagePaths.icBack, + alignment: Alignment.center, + color: AppColor.primaryColor + ) + ), + ); } Widget _buildLoginMessage(BuildContext context, LoginState loginState) { return Padding( - padding: const EdgeInsets.only(bottom: 24, top: 60), + padding: const EdgeInsets.only(top: 11, bottom: 36, left: 58, right: 58), child: SizedBox( - width: responsiveUtils.getWidthLoginTextField(context), + width: _responsiveUtils.getWidthLoginTextField(context), child: CenterTextBuilder() .key(const Key('login_message')) .text(loginState.viewState.fold( (failure) => failure is AuthenticationUserFailure ? AppLocalizations.of(context).unknown_error_login_message - : AppLocalizations.of(context).login_text_login_to_continue, - (success) => AppLocalizations.of(context).login_text_login_to_continue)) + : AppLocalizations.of(context).loginInputUrlMessage, + (success) => AppLocalizations.of(context).loginInputUrlMessage)) .textStyle(TextStyle( + fontSize: 15, + fontWeight: FontWeight.w400, color: loginState.viewState.fold( (failure) => failure is AuthenticationUserFailure ? AppColor.textFieldErrorBorderColor - : AppColor.appColor, - (success) => AppColor.appColor))) + : AppColor.colorNameEmail, + (success) => AppColor.colorNameEmail))) .build() ) ); } + Widget _buildInputCredentialForm(BuildContext context) { + return Column( + children: [ + _buildUserNameInput(context), + _buildPasswordInput(context) + ], + ); + } + Widget _buildUrlInput(BuildContext context) { return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: SizedBox( - width: responsiveUtils.getWidthLoginTextField(context), + padding: const EdgeInsets.only(right: 24, left: 24, bottom: 24), + child: Container( child: (TextFieldBuilder() ..key(const Key('login_url_input')) ..onChange((value) => loginController.formatUrl(value)) ..textInputAction(TextInputAction.next) ..addController(loginController.urlInputController) ..keyboardType(TextInputType.url) + ..onSubmitted((value) { + controller.handleNextInUrlInputFormPress(); + }) ..textDecoration((LoginInputDecorationBuilder() - ..setLabelText(AppLocalizations.of(context).prefix_https) - ..setPrefixText(AppLocalizations.of(context).prefix_https)) - .build())) + ..setLabelText(AppLocalizations.of(context).prefix_https) + ..setPrefixText(AppLocalizations.of(context).prefix_https)) + .build())) .build())); } Widget _buildUserNameInput(BuildContext context) { return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: SizedBox( - width: responsiveUtils.getWidthLoginTextField(context), + padding: const EdgeInsets.only(bottom: 16, right: 24, left: 24), + child: Container( child: (TextFieldBuilder() ..key(const Key('login_username_input')) ..onChange((value) => loginController.setUserNameText(value)) @@ -125,40 +191,78 @@ class LoginView extends GetWidget { Widget _buildPasswordInput(BuildContext context) { return Padding( - padding: const EdgeInsets.only(bottom: 40), - child: SizedBox( - width: responsiveUtils.getWidthLoginTextField(context), - child: (TextFieldBuilder() + padding: const EdgeInsets.only(bottom: 40, right: 24, left: 24), + child: Container( + child: (LoginTextInputBuilder(context, imagePaths) + ..setOnSubmitted((value) => loginController.handleLoginPressed()) + ..passwordInput(true) ..key(const Key('login_password_input')) ..obscureText(true) ..onChange((value) => loginController.setPasswordText(value)) ..textInputAction(TextInputAction.done) - ..textDecoration((LoginInputDecorationBuilder() - ..setLabelText(AppLocalizations.of(context).password) - ..setHintText(AppLocalizations.of(context).password)) - .build())) + ..hintText(AppLocalizations.of(context).password)) .build())); } + Widget _buildExpandedButton(BuildContext context, Widget child) { + return Expanded( + child: Align( + alignment: FractionalOffset.bottomCenter, + child: child + ) + ); + } + + Widget _buildNextButton(BuildContext context) { + return Container( + margin: const EdgeInsets.only(bottom: 16, left: 24, right: 24), + width: _responsiveUtils.getDeviceWidth(context),height: 48, + child: ElevatedButton( + key: const Key('nextToCredentialForm'), + style: ButtonStyle( + foregroundColor: MaterialStateProperty.resolveWith((Set states) => Colors.white), + backgroundColor: MaterialStateProperty.resolveWith((Set states) => AppColor.primaryColor), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide(width: 0, color: AppColor.primaryColor) + )) + ), + child: Text(AppLocalizations.of(context).next, + style: const TextStyle(fontSize: 16, color: Colors.white) + ), + onPressed: () { + loginController.handleNextInUrlInputFormPress(); + } + ) + ); + } + + Widget _buildLoginButtonInContext(BuildContext context) { + return _supportScrollForm(context) + ? _buildLoginButton(context) + : _buildExpandedButton(context, _buildLoginButton(context)); + } + Widget _buildLoginButton(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(bottom: 16), - child: SizedBox( - key: const Key('login_confirm_button'), - width: responsiveUtils.getWidthLoginButton(), - height: 48, - child: ElevatedButton( - style: ButtonStyle( - foregroundColor: MaterialStateProperty.resolveWith((Set states) => Colors.white), - backgroundColor: MaterialStateProperty.resolveWith((Set states) => AppColor.buttonColor), - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: BorderRadius.circular(80), - side: const BorderSide(width: 0, color: AppColor.buttonColor)))), - child: Text(AppLocalizations.of(context).login, style: const TextStyle(fontSize: 16, color: Colors.white)), - onPressed: () { - keyboardUtils.hideKeyboard(context); - loginController.handleLoginPressed(); - }), + return Container( + margin: const EdgeInsets.only(bottom: 16, left: 24, right: 24), + width: _responsiveUtils.getDeviceWidth(context),height: 48, + child: ElevatedButton( + key: const Key('loginSubmitForm'), + style: ButtonStyle( + foregroundColor: MaterialStateProperty.resolveWith((Set states) => Colors.white), + backgroundColor: MaterialStateProperty.resolveWith((Set states) => AppColor.primaryColor), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: const BorderSide(width: 0, color: AppColor.primaryColor) + )) + ), + child: Text(AppLocalizations.of(context).login, + style: const TextStyle(fontSize: 16, color: Colors.white) + ), + onPressed: () { + loginController.handleLoginPressed(); + } ) ); } @@ -170,4 +274,8 @@ class LoginView extends GetWidget { height: 40, child: CircularProgressIndicator(color: AppColor.primaryColor)); } + + bool _supportScrollForm(BuildContext context) { + return !(responsiveUtils.isMobile(context) && responsiveUtils.isPortrait(context)); + } } \ No newline at end of file diff --git a/lib/features/login/presentation/state/login_state.dart b/lib/features/login/presentation/state/login_state.dart index edbe51673..57474b060 100644 --- a/lib/features/login/presentation/state/login_state.dart +++ b/lib/features/login/presentation/state/login_state.dart @@ -7,6 +7,12 @@ class LoginState extends AppState { LoginState(Either viewState) : super(viewState); } +@immutable +class InputUrlCompletion extends ViewState { + @override + List get props => []; +} + @immutable class LoginLoadingAction extends ViewState { @override diff --git a/lib/features/login/presentation/widgets/login_input_decoration_builder.dart b/lib/features/login/presentation/widgets/login_input_decoration_builder.dart index 23e903d48..a741669ca 100644 --- a/lib/features/login/presentation/widgets/login_input_decoration_builder.dart +++ b/lib/features/login/presentation/widgets/login_input_decoration_builder.dart @@ -8,13 +8,13 @@ class LoginInputDecorationBuilder extends InputDecorationBuilder { InputDecoration build() { return InputDecoration( enabledBorder: enabledBorder ?? const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(29)), + borderRadius: BorderRadius.all(Radius.circular(10)), borderSide: BorderSide(width: 1, color: AppColor.textFieldBorderColor)), - focusedBorder: enabledBorder ?? const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(29)), + focusedBorder: focusBorder ?? const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), borderSide: BorderSide(width: 2, color: AppColor.textFieldFocusedBorderColor)), errorBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(29)), + borderRadius: BorderRadius.all(Radius.circular(10)), borderSide: BorderSide(width: 1, color: AppColor.textFieldErrorBorderColor)), prefixText: prefixText, labelText: labelText, diff --git a/lib/features/login/presentation/widgets/login_text_input_builder.dart b/lib/features/login/presentation/widgets/login_text_input_builder.dart new file mode 100644 index 000000000..7ae6605f9 --- /dev/null +++ b/lib/features/login/presentation/widgets/login_text_input_builder.dart @@ -0,0 +1,196 @@ +// LinShare is an open source filesharing software, part of the LinPKI software +// suite, developed by Linagora. +// +// Copyright (C) 2020 LINAGORA +// +// This program is free software: you can redistribute it and/or modify it under the +// terms of the GNU Affero General Public License as published by the Free Software +// Foundation, either version 3 of the License, or (at your option) any later version, +// provided you comply with the Additional Terms applicable for LinShare software by +// Linagora pursuant to Section 7 of the GNU Affero General Public License, +// subsections (b), (c), and (e), pursuant to which you must notably (i) retain the +// display in the interface of the “LinShare™” trademark/logo, the "Libre & Free" mention, +// the words “You are using the Free and Open Source version of LinShare™, powered by +// Linagora © 2009–2020. Contribute to Linshare R&D by subscribing to an Enterprise +// offer!”. You must also retain the latter notice in all asynchronous messages such as +// e-mails sent with the Program, (ii) retain all hypertext links between LinShare and +// http://www.linshare.org, between linagora.com and Linagora, and (iii) refrain from +// infringing Linagora intellectual property rights over its trademarks and commercial +// brands. Other Additional Terms apply, see +// ? _onTextChange; + TextInputAction? _textInputAction; + TextEditingController? _textEditingController; + bool? _passwordInput; + OnSubmitted? _onSubmitted; + + final BuildContext context; + final ImagePaths imagePaths; + + LoginTextInputBuilder(this.context, this.imagePaths); + + void key(Key key) { + _key = key; + } + + void title(String? title) { + _title = title; + } + + void hintText(String? hintText) { + _hintText = hintText; + } + + void labelText(String? labelText) { + _labelText = labelText; + } + + void prefixText(String? prefixText) { + _prefixText = prefixText; + } + + void textInputAction(TextInputAction inputAction) { + _textInputAction = inputAction; + } + + void obscureText(bool obscureText) { + _obscureText = obscureText; + } + + void onChange(ValueChanged onChange) { + _onTextChange = onChange; + } + + void setErrorString(SetErrorString setErrorString) { + _setErrorString = setErrorString; + } + + void passwordInput(bool? passwordInput) { + _passwordInput = passwordInput; + } + + void setTextEditingController(TextEditingController textEditingController) { + _textEditingController = textEditingController; + } + + void _onTextChanged(String name, StateSetter setState) { + if (_onTextChange != null) { + _onTextChange!(name); + } + if (_debounce?.isActive ?? false) _debounce?.cancel(); + _debounce = Timer(const Duration(milliseconds: 500), () { + setState(() { + _errorText = (_setErrorString != null) ? _setErrorString!(name) : ''; + }); + }); + } + + void errorText(String? errorText) { + _errorText = errorText; + } + + void _onObscureTextChanged(bool? value, StateSetter setState) { + setState(() { + _obscureText = value == true ? false : true; + }); + } + + void setOnSubmitted(OnSubmitted onSubmitted) { + _onSubmitted = onSubmitted; + } + + Widget build() { + return StatefulBuilder(builder: (BuildContext context, StateSetter setState) { + return Wrap( + key: _key, + children: [ + Text(_title ?? '', style: const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 14, fontWeight: FontWeight.normal)), + Padding( + padding: const EdgeInsets.only(top: 8), + child: Stack( + alignment: AlignmentDirectional.centerEnd, + children: [ + TextFormField( + onFieldSubmitted: _onSubmitted, + onChanged: (value) => _onTextChanged(value, setState), + obscureText: _obscureText ?? false, + textInputAction: _textInputAction, + controller: _textEditingController, + cursorColor: AppColor.primaryColor, + style: const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal), + decoration: (LoginInputDecorationBuilder() + ..setHintText(_hintText) + ..setPrefixText(_prefixText) + ..setErrorText(_errorText) + ..setHintStyle(const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal)) + ..setPrefixStyle(const TextStyle(color: AppColor.loginTextFieldHintColor, fontSize: 16, fontWeight: FontWeight.normal)) + ..setErrorTextStyle(const TextStyle(color: AppColor.loginTextFieldErrorBorder, fontSize: 13, fontWeight: FontWeight.normal)) + ..setFocusBorder(const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.loginTextFieldFocusedBorder))) + ..setEnabledBorder(const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.loginTextFieldBorderColor))) + ..setErrorBorder(const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(10)), + borderSide: BorderSide(width: 1, color: AppColor.loginTextFieldErrorBorder)))) + .build(), + ), + if (_passwordInput == true) + Transform( + transform: Matrix4.translationValues( + 0.0, + (_errorText != null && _errorText!.isNotEmpty) ? -10.0 : 0.0, + 0.0), + child: IconButton( + onPressed: () => _onObscureTextChanged(_obscureText, setState), + icon: SvgPicture.asset( + _obscureText == true ? imagePaths.icEye : imagePaths.icEyeOff, + width: 18, + height: 18, + fit: BoxFit.fill))) + ] + ) + ), + ], + ); + }); + } +} diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index fdade5b61..e42d304e5 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -29,6 +29,14 @@ class AppLocalizations { name: 'login_text_slogan'); } + String get loginInputUrlMessage { + return Intl.message('To login and access your message please connect to your JMAP server', name: 'loginInputUrlMessage'); + } + + String get next { + return Intl.message('Next', name: 'next'); + } + String get prefix_https { return Intl.message('https://', name: 'prefix_https'); @@ -49,11 +57,6 @@ class AppLocalizations { name: 'login'); } - String get login_text_login_to_continue { - return Intl.message('Please login to continue', - name: 'login_text_login_to_continue'); - } - String get unknown_error_login_message { return Intl.message('Unknown error occurred, please try again', name: 'unknown_error_login_message');