Аутентификация во Flutter | Проектирование пользовательского интерфейса

Создайте динамический виджет Input, форму, которая может трансформироваться в форму входа и регистрации в зависимости от режима аутентификации, анимируйте переход между формой регистрации и формой входа, а также проверьте вводимые пользователем данные.

Содержание
  1. Введение
  2. Создание формы аутентификации во Flutter
  3. Создание виджета динамической текстовой формы
  4. Валидация ввода во Flutter
  5. Создание валидатора электронной почты во Flutter
  6. Создание валидатора паролей во Flutter
  7. Создание валидатора подтверждения пароля во Flutter
  8. Домашнее задание по валидаторам
  9. Как создать форму регистрации и входа во Flutter?
  10. Виджет формы авторизации: Временный
  11. Экран аутентификации
  12. Подключение экрана аутентификации к маршрутизатору
  13. Временная навигация к AuthScreen
  14. Полноценный виджет формы аутентификации
  15. Инстанцируйте AuthValidators внутри класса _AuthFormWidgetState.
  16. Создайте контроллеры и узлы фокуса.
  17. Создание неясного текста bool
  18. Режим авторизации
  19. Инстанцирование и утилизация
  20. Обработчик видимости пароля
  21. Виджет закусочной
  22. Подготовка к списку виджетов в колонке
  23. Виджеты ввода
  24. Кнопка регистрации/входа в систему
  25. Переключение режима аутентификации
  26. Анимированный переход регистрации/сигнализации
  27. Резюме
  28. Показать поддержку

Введение

Здравствуйте и добро пожаловать в Khadka’s Coding Lounge. Это Нибеш из Khadka’s Coding Lounge. Прежде всего, отдельное спасибо всем последователям и подписчикам этой серии блогов. Итак, до сих пор в этой серии мы сделали Splash Screen, User Onboarding System, Global Theme и пользовательские виджеты, такие как панель приложений, нижняя навигационная панель и ящик для нашего приложения.

Сегодняшний блог — одна из самых важных частей этой серии, как вы можете понять из названия, потому что теперь, согласно нашему экрану потока пользователей, приведенному ниже, мы должны аутентифицировать пользователя.

Аутентификация — это базовый, но очень важный аспект любого приложения, независимо от платформы. Бессерверные/безголовые приложения в наши дни в тренде. Среди них Firebase от Google является одним из популярных, особенно для мобильных приложений. В этой главе цикла мы создадим пользовательский интерфейс аутентификации. Мы создадим виджет динамической формы ввода для большей эффективности. Мы также напишем несколько валидаций для ввода и анимируем переходы формы регистрации <->.

Вы можете найти код до этого момента здесь.

Создание формы аутентификации во Flutter

Мы создадим простую форму. Для регистрации у нас будет четыре входа: email, имя пользователя, пароль и подтверждение пароля, в то время как форма входа использует только два входа: email и пароль.

Давайте создадим соответствующие файлы и папки в нашем проекте.

# cursor on root folder
# create folders
mkdir lib/screens/auth lib/screens/auth/widgets lib/screens/auth/providers  lib/screens/auth/utils

# Create files
touch lib/screens/auth/auth_screen.dart   lib/screens/auth/providers/auth_provider.dart lib/screens/auth/widgets/text_from_widget.dart  lib/screens/auth/widgets/auth_form_widget.dart lib/screens/auth/utils/auth_validators.dart lib/screens/auth/utils/auth_utils.dart
Вход в полноэкранный режим Выход из полноэкранного режима

Создание виджета динамической текстовой формы

Прежде чем мы создадим наш виджет динамического поля текстовой формы, давайте подробно рассмотрим, насколько динамичным он будет.

Мы создадим наш динамический виджет текстовой формы в файле text_from_widget.dart.

import 'package:flutter/material.dart';

class DynamicInputWidget extends StatelessWidget {
  const DynamicInputWidget(
      {required this.controller,
      required this.obscureText,
      required this.focusNode,
      required this.toggleObscureText,
      required this.validator,
      required this.prefIcon,
      required this.labelText,
      required this.textInputAction,
      required this.isNonPasswordField,
      Key? key})
      : super(key: key);

  // bool to check if the text field is for password or not
  final bool isNonPasswordField;
  // Controller for the text field
  final TextEditingController controller;
  // Functio to toggle Text obscuractio on password text field
  final VoidCallback? toggleObscureText;
  // to obscure text or not bool
  final bool obscureText;
  // FocusNode for input
  final FocusNode focusNode;
  // Validator function
  final String? Function(String?)? validator;
  // Prefix icon for input form
  final Icon prefIcon;
  // label for input form
  final String labelText;
  // The keyword action to display
  final TextInputAction textInputAction;

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      controller: controller,
      decoration: InputDecoration(
        // Input with border outlined
        border: const OutlineInputBorder(
          // Make border edge circular
          borderRadius: BorderRadius.all(Radius.circular(10.0)),
        ),
        label: Text(labelText),
        prefixIcon: prefIcon,
        suffixIcon: IconButton(
          onPressed: toggleObscureText,
          // If is non-password filed like emal the suffix icon will be null
          icon: isNonPasswordField
              ? const Icon(null)
              : obscureText
                  ? const Icon(Icons.visibility)
                  : const Icon(Icons.visibility_off),
        ),
      ),
      focusNode: focusNode,
      textInputAction: textInputAction,
      obscureText: obscureText,
      validator: validator,
      // onSaved: passwordVlidator,
    );
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Давайте пройдемся по нескольким важным полям нашего класса динамического виджета ввода.


// bool to check if the text field is for password or not
  final bool isNonPasswordField; //# 1
   // Function to toggle Text obscuraction on password text field
  final VoidCallback? toggleObscureText; //# 2
  // to obscure text or not bool
  final bool obscureText;
   // Validator function 
  final String? Function(String?)? validator; //# 3

Вход в полноэкранный режим Выход из полноэкранного режима
  1. Мы проверяем, является ли тип ввода паролем или нет. Это нам понадобится, чтобы определить, нужно ли отображать значок суффикса, который переключает видимость пароля.
  2. Булево-непонятный текст изменит видимость пароля со звездочек на строки и наоборот.
  3. Validator — это функция валидатора свойств в TextFormField. Она возвращает null в случае валидности и пользовательскую строку в случае невалидности.

Валидация ввода во Flutter

Теперь, когда наш динамический ввод готов. Давайте напишем несколько валидаторов, прежде чем перейдем к созданию нашей формы. Мы создаем отдельный файл auth_validators.dart только для этой цели. Мы напишем три функции, которые будут отдельно проверять Email, пароль и подтверждение пароля для ввода пользователем.

Создание валидатора электронной почты во Flutter

class AuthValidators {
  // Create error messages to send.
 // #1
  static const String emailErrMsg =  "Invalid Email Address, Please provide a valid email.";
  static const String passwordErrMsg = "Password must have at least 6 characters.";
  static const String confirmPasswordErrMsg = "Two passwords don't match.";

// A simple email validator that  checks presence and position of @
// #2
  String? emailValidator(String? val) {
    final String email = val as String;

    // If length of email is <=3 then its invlaid
   // #3
    if (email.length <= 3) return emailErrMsg;

    // Check if it has @
  // # 4
    final hasAtSymbol = email.contains('@');

    // find position of @
    // # 5
    final indexOfAt = email.indexOf('@');

    // Check numbers of @
    // # 6
    final numbersOfAt = "@".allMatches(email).length;

    // Valid if has @
  // # 7
    if (!hasAtSymbol) return emailErrMsg;

    // and  if number of @ is only 1
  // # 8
    if (numbersOfAt != 1) return emailErrMsg;

    //and if  '@' is not the first or last character
  // # 9
    if (indexOfAt == 0 || indexOfAt == email.length - 1) return emailErrMsg;

    // Else its valid
    return null;
  }
}



Вход в полноэкранный режим Выйти из полноэкранного режима

Внутри файла auth_validators.dart мы создаем класс AuthValidators.

  1. Затем мы создали три сообщения об ошибках, которые будут отправлены в качестве вывода соответствующей ошибки.
  2. Email Validator Функция, которая принимает входное значение из формы ввода.
  3. Email считается недействительным, если его длина <=3.
  4. (4&7) Проверяет наличие символа @ и возвращает сообщение об ошибке при его отсутствии.
  5. (5&9) Проверяет положение символа @. Если позиция находится в начале или в конце, это означает, что письмо недействительно.
  6. (6&8) Если количество символов @ больше или меньше единицы, значит, письмо недействительно.

Это был очень простой валидатор, в котором мы проверяли четыре вещи: если вход для email пустой, длина входа email меньше 4, позиция и количество @ во входе.

Напоминание: Не меняйте порядок операторов возврата. Это приведет к ошибке.

Создание валидатора паролей во Flutter

Мы продолжим со вторым валидатором для пароля. Мы будем проверять пароль, только если он не пустой и его длина больше 5. Итак, снова внутри класса AuthValidator создадим новую функцию passwordValidator.

// Password validator
  String? passwordVlidator(String? val) {
    final String password = val as String;

    if (password.isEmpty || password.length <= 5) return passwordErrMsg;

    return null;
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Создание валидатора подтверждения пароля во Flutter

Во время регистрации будет два поля ввода пароля, второе из которых необходимо для подтверждения заданного пароля. Наш валидатор confirmPassword будет принимать два ввода: оригинальный пароль и второй пароль.

// Confirm password
  String? confirmPasswordValidator(String? val, firstPasswordInpTxt) {
    final String firstPassword = firstPasswordInpTxt;
    final String secondPassword = val as String;
    // If either of the password field is empty
    // Or if thier length do not match then we don't need to compare their content
// #1
    if (firstPassword.isEmpty || 
        secondPassword.isEmpty ||
        firstPassword.length != secondPassword.length) {
      return confirmPasswordErrMsg;
    }

    //  If two passwords do not match then send error message
// #2
    if (firstPassword != secondPassword) return confirmPasswordErrMsg;

    return null;
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Для подтверждения пароля мы проверяем:

  1. Если один из двух паролей пуст или если их длина не совпадает друг с другом. Если да, то возвращаем сообщение об ошибке.
  2. Второе условие сравнивает содержимое двух паролей, если они не совпадают, то возвращает сообщение об ошибке.

Вот так наши три валидатора готовы к работе.

В целом класс AuthValidators выглядит следующим образом.

class AuthValidators {
  // Create error messages to send.
  static const String emailErrMsg =
      "Invalid Email Address, Please provide a valid email.";
  static const String passwordErrMsg =
      "Password must have at least 6 characters.";
  static const String confirmPasswordErrMsg = "Two passwords don't match.";

// A simple email validator that  checks presence and position of @
  String? emailValidator(String? val) {
    final String email = val as String;

    // If length of email is <=3 then its invlaid
    if (email.length <= 3) return emailErrMsg;

    // Check if it has @
    final hasAtSymbol = email.contains('@');
    // find position of @
    final indexOfAt = email.indexOf('@');
    // Check numbers of @
    final numbersOfAt = "@".allMatches(email).length;

    // Valid if has @
    if (!hasAtSymbol) return emailErrMsg;

    // and  if number of @ is only 1
    if (numbersOfAt != 1) return emailErrMsg;

    //and if  '@' is not first or last character
    if (indexOfAt == 0 || indexOfAt == email.length - 1) return emailErrMsg;

    // Else its valid
    return null;
  }

  // Password validator
  String? passwordVlidator(String? val) {
    final String password = val as String;

    if (password.isEmpty || password.length <= 5) return passwordErrMsg;

    return null;
  }

  // Confirm password
  String? confirmPasswordValidator(String? val, firstPasswordInpTxt) {
    final String firstPassword = firstPasswordInpTxt;
    final String secondPassword = val as String;
    // If either of the password field is empty
    // Or if thier length do not match then we don't need to compare their content
    if (firstPassword.isEmpty ||
        secondPassword.isEmpty ||
        firstPassword.length != secondPassword.length) {
      return confirmPasswordErrMsg;
    }

    //  If two passwords do not match then send error message
    if (firstPassword != secondPassword) return confirmPasswordErrMsg;

    return null;
  }
}

Вход в полноэкранный режим Выход из полноэкранного режима

Домашнее задание по валидаторам

Вот вам кое-что для практики.

  1. xyz..abc@mail.com — это недопустимый адрес электронной почты, поскольку за символами (_, ., -) всегда должны следовать буквы или цифры. Можете ли вы написать валидатор для этой ошибки?
  2. На валидаторе пароля проверьте, что он должен содержать как цифры, так и буквы, чтобы быть действительным.
  3. Добавьте валидатор имени пользователя, проверьте, что оно не должно совпадать с адресом электронной почты. Кстати, имя пользователя является необязательным параметром.

Как создать форму регистрации и входа во Flutter?

Виджет формы авторизации: Временный

Теперь, когда мы сделали динамический ввод и валидатор, пришло время собрать их вместе, чтобы создать визуальный вид формы аутентификации. Мы создадим наш виджет формы в файле auth_form_widget.dart, который будет отображаться в файле auth_screen.dart.

auth_form_widget.dart

import 'package:flutter/material.dart';

class AuthFormWidget extends StatefulWidget {
  const AuthFormWidget({Key? key}) : super(key: key);

  @override
  State<AuthFormWidget> createState() => _AuthFormWidgetState();
}

class _AuthFormWidgetState extends State<AuthFormWidget> {
  // Define Form key
  final _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Form(
        key: _formKey,
        child: TextFormField(),
      ),
    );
  }
}

Вход в полноэкранный режим Выйти из полноэкранного режима

В flutter класс Form может выступать в качестве контейнера для отображения полей TextFormFields (если их несколько). Он также требует FormState, который можно получить с помощью глобального ключа типа FormState, как мы только что сделали. Прежде чем виджет формы разрастется, давайте подключим его к auth_screen и отобразим на экране приложения. Мы изменим его позже, после подключения экрана аутентификации к маршрутизатору.

Экран аутентификации

auth_screen.dart

import 'package:flutter/material.dart';
import 'package:temple/globals/settings/router/utils/router_utils.dart';
import 'package:temple/screens/auth/widgets/auth_form_widget.dart';

class AuthScreen extends StatelessWidget {
  const AuthScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(APP_PAGE.auth.routePageTitle)),
      body:
      // Safe area prevents safe gards widgets to go beyond device edges
       SafeArea(
        //===========//
        // to dismiss keyword on tap outside use listener
        child: Listener(
          onPointerDown: (PointerDownEvent event) =>
              FocusManager.instance.primaryFocus?.unfocus(),
          //===========//
          child: SingleChildScrollView(
            child: SizedBox(
              width: double.infinity,
              child: Column(children: [
                // Display a welcome user image
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Image.asset(
                    'assets/AuthScreen/WelcomeScreenImage_landscape_2.png',
                    fit: BoxFit.fill,
                  ),
                ),
                const AuthFormWidget()
              ]),
            ),
          ),
        ),
      ),
    );
  }
}
Вход в полноэкранный режим Выход из полноэкранного режима

Вот auth_screen.dart, это все, в дальнейшем мы не будем вносить никаких изменений.

Поскольку приложение доступно только зарегистрированным пользователям, экран аутентификации будет первым экраном, на который попадет наш пользователь после входа в приложение. Поэтому нам не нужно отображать user_drawer или нижнюю панель навигации.

Отключение активной клавиатуры при нажатии на нее снаружи является важной характеристикой. Класс Listener реагирует на события жестов, такие как щелчок мыши, касание и т.д. Вместе с FocusManager мы можем отслеживать дерево узлов фокуса и расфокусировать активную клавиатуру. Вы можете задаться вопросом, почему я использую его на auth_screen в целом, а не на auth_form_widget. Это потому, что события детектора жестов должны охватывать всю видимую область, которой в данном случае является SingleChildScrollView.

Далее — секция изображения, если вы используете исходный код из репозитория GitHub, изображение уже должно быть в папке AuthScreen внутри assets. Давайте добавим путь в файл pubspec.

assets:
    # Splash Screens
    - assets/splash/om_splash.png
    - assets/splash/om_lotus_splash.png
    - assets/splash/om_lotus_splash_1152x1152.png
    # Onboard Screens
    - assets/onboard/FindTemples.png
    - assets/onboard/FindVenues.png
// New line
    # Auth Screens
    - assets/AuthScreen/WelcomeScreenImage_landscape_2.png 
Вход в полноэкранный режим Выход из полноэкранного режима

Пришло время добавить auth_screen в app_router.dart

Подключение экрана аутентификации к маршрутизатору

app_router.dart

 routes: [
        GoRoute(
          path: APP_PAGE.home.routePath,
          name: APP_PAGE.home.routeName,
          builder: (context, state) => const Home(),
        ),
        // Add the onboard Screen
        GoRoute(
            path: APP_PAGE.onboard.routePath,
            name: APP_PAGE.onboard.routeName,
            builder: (context, state) => const OnBoardScreen()),
        // New line from here
        // Add Auth Screen on Go Router
        GoRoute(
            path: APP_PAGE.auth.routePath,
            name: APP_PAGE.auth.routeName,
            builder: (context, state) => const AuthScreen()),
      ],

Войти в полноэкранный режим Выход из полноэкранного режима

Временная навигация к AuthScreen

Нам еще предстоит написать бэкенд/бизнес-логику в этом блоге. Но нам нужно протестировать пользовательский интерфейс. Поэтому давайте создадим временную ссылку в нашем файле user_drawer.dart, которая приведет нас к экрану auth_screen.
user_drawer.dart

...............
 actions: [

        ListTile(
            leading: Icon(
              Icons.person_outline_rounded,
              color: Theme.of(context).colorScheme.secondary,
            ),
            title: const Text('User Profile'),
            onTap: () {
              print("User Profile Button Pressed");
            }),
 // ============================//
    // A temporarry link to auth screen
        ListTile(
            leading: Icon(
              Icons.login,
              color: Theme.of(context).colorScheme.secondary,
            ),
            title: const Text('Register/Login'),
            onTap: () => GoRouter.of(context).goNamed(APP_PAGE.auth.routeName)),
 // ============================//
        ListTile(
            leading: Icon(
              Icons.logout,
              color: Theme.of(context).colorScheme.secondary,
            ),
            title: const Text('Logout'),
            onTap: () {
              print("Log Out Button Pressed");
            }),
      ],
.....
Вход в полноэкранный режим Выйти из полноэкранного режима

Сохраните все это и запустите приложение, перейдите к auth_screen из ящика пользователя (нажмите иконку человека для ящика), и вы увидите auth screen. Теперь, когда мы видим экран аутентификации, давайте создадим полноценную форму аутентификации.

Полноценный виджет формы аутентификации

Коды для файла auth_form_widget.dart очень длинные. Поэтому давайте сначала рассмотрим его по частям.

Инстанцируйте AuthValidators внутри класса _AuthFormWidgetState.

  // Instantiate validator
  final AuthValidators authValidator = AuthValidators();

Вход в полноэкранный режим Выход из полноэкранного режима

Создайте контроллеры и узлы фокуса.

// controllers 
  late TextEditingController emailController;
  late TextEditingController usernameController;
  late TextEditingController passwordController;
  late TextEditingController confirmPasswordController;

// create focus nodes
  late FocusNode emailFocusNode;
  late FocusNode usernameFocusNode;
  late FocusNode passwordFocusNode;
  late FocusNode confirmPasswordFocusNode;

Вход в полноэкранный режим Выход из полноэкранного режима

Создание неясного текста bool

// to obscure text default value is false
  bool obscureText = true;
Войти в полноэкранный режим Выйти из полноэкранного режима

Режим авторизации

Вместо того чтобы создавать два отдельных экрана для регистрации и входа, мы просто будем переключать несколько отображаемых входов, включая и выключая их. Для этого создадим булево значение для управления authMode.

 // This will require toggling between register and signin mode
  bool registerAuthMode = false;
Войти в полноэкранный режим Выйти из полноэкранного режима

Инстанцирование и утилизация

Инстанцируйте все контроллеры редактирования текста и узлы фокуса в функции initState. Аналогично, все они также должны быть утилизированы, поэтому давайте сделаем это с помощью метода dispose.

@override
  void initState() {
    super.initState();
    emailController = TextEditingController();
    usernameController = TextEditingController();
    passwordController = TextEditingController();
    confirmPasswordController = TextEditingController();

    emailFocusNode = FocusNode();
    usernameFocusNode = FocusNode();
    passwordFocusNode = FocusNode();
    confirmPasswordFocusNode = FocusNode();
  }

@override
  void dispose() {
    super.dispose();

    emailController.dispose();
    usernameController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();

    emailFocusNode.dispose();
    usernameFocusNode.dispose();
    passwordFocusNode.dispose();
    confirmPasswordFocusNode.dispose();
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Обработчик видимости пароля

Создайте функцию, которая будет переключать видимость пароля при нажатии на соответствующую иконку.

 void toggleObscureText() {
    setState(() {
      obscureText = !obscureText;
    });
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Виджет закусочной

Давайте создадим закусочную, которая будет выводить информацию о различных обстоятельствах.

// Create a scaffold messanger
  SnackBar msgPopUp(msg) {
    return SnackBar(
        content: Text(
      msg,
      textAlign: TextAlign.center,
    ));
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Подготовка к списку виджетов в колонке

Давайте изменим дочернюю форму Form, которую мы возвращаем, на колонку.

@override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Form(
        key: _formKey,
        child: Column(children: [],)
      ),
    );
  }
Войти в полноэкранный режим Выйти из полноэкранного режима

Виджеты ввода

Пришло время создать наши формы ввода email, имени пользователя, пароля и подтверждения пароля. Внутри дочерних элементов колонки для виджета from мы будем добавлять вводимые данные по одному.

Ввод электронной почты
    // Email
            DynamicInputWidget(
              controller: emailController,
              obscureText: false,
              focusNode: emailFocusNode,
              toggleObscureText: null,
              validator: authValidator.emailValidator,
              prefIcon: const Icon(Icons.mail),
              labelText: "Enter Email Address",
              textInputAction: TextInputAction.next,
              isNonPasswordField: true,
            ),
            const SizedBox(
              height: 20,
            ),

Вход в полноэкранный режим Выход из полноэкранного режима

Убедитесь, что импортировали виджет DynamicInput

Ввод имени пользователя
    // Username
              DynamicInputWidget(
                controller: usernameController,
                obscureText: false,
                focusNode: usernameFocusNode,
                toggleObscureText: null,
                validator: null,
                prefIcon: const Icon(Icons.person),
                labelText: "Enter Username(Optional)",
                textInputAction: TextInputAction.next,
                isNonPasswordField: true,
              ),
               const SizedBox(
                height: 20,
              ),

Войти в полноэкранный режим Выйти из полноэкранного режима
Ввод пароля
// password
  DynamicInputWidget(
              controller: passwordController,
              labelText: "Enter Password",
              obscureText: obscureText,
              focusNode: passwordFocusNode,
              toggleObscureText: toggleObscureText,
              validator: authValidator.passwordVlidator,
              prefIcon: const Icon(Icons.password),
              textInputAction: registerAuthMode
                  ? TextInputAction.next
                  : TextInputAction.done,
              isNonPasswordField: false,
            ),

            const SizedBox(
              height: 20,
            ),

Ввести в полноэкранном режиме Выход из полноэкранного режима
Подтверждение ввода пароля
// confirm password
DynamicInputWidget(
                controller: confirmPasswordController,
                focusNode: confirmPasswordFocusNode,
                isNonPasswordField: false,
                labelText: "Confirm Password",
                obscureText: obscureText,
                prefIcon: const Icon(Icons.password),
                textInputAction: TextInputAction.done,
                toggleObscureText: toggleObscureText,
                validator: (val) => authValidator.confirmPasswordValidator(
                    val, passwordController.text),
              ),

              const SizedBox(
                height: 20,
              ),
Вход в полноэкранный режим Выход из полноэкранного режима

Кнопка регистрации/входа в систему

Позже нам также понадобится создать и переключить функции для регистрации и входа в систему, когда мы настроим бэкенд firebase. Пока что мы просто переключим тексты регистрации/входа на поднятой кнопке.

// Toggle register/singin button text. Later on we'll also need to toggle register or signin function
Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton(
                  onPressed: () {},
                  child: const Text('Cancel'),
                ),
                const SizedBox(
                  width: 20,
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: Text(registerAuthMode ? 'Register' : 'Sign In'),
                  style: ButtonStyle(
                    elevation: MaterialStateProperty.all(8.0),
                  ),
                ),
              ],
            ),

Вход в полноэкранный режим Выйти из полноэкранного режима

Переключение режима аутентификации

Управляйте режимом аутентификации с помощью функции setState().

 Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(registerAuthMode
                    ? "Already Have an account?"
                    : "Don't have an account yet?"),
                TextButton(
                  onPressed: () =>
                      setState(() => registerAuthMode = !registerAuthMode),
                  child: Text(registerAuthMode ? "Sign In" : "Regsiter"),
                )
              ],
            )
Войти в полноэкранный режим Выйти из полноэкранного режима

Анимированный переход регистрации/сигнализации

Если вы сохраните файл и запустите его. Вы, вероятно, заметите, что переключение не работает. Это потому, что мы еще не реализовали анимированный переход. Два виджета ввода: виджеты имени пользователя и подтверждения пароля должны быть скрыты во время процесса регистрации, но видны при регистрации. Поэтому мы будем использовать переключение видимости на основе значения переменной registerAuthMode, которую мы создали ранее. Для плавного перехода во время переключения мы будем использовать анимацию.

Класс AnimatedContainer может быть использован для анимации, а класс AnimatedOpacity — для плавного включения/выключения виджетов. С помощью их комбинации мы переключим ввод с анимированной непрозрачностью, в то время как анимированный контейнер будет плавно сжимать/заполнять пространство, занимаемое вводом.

Итак, давайте анимируем имя пользователя, следующий за ним виджет sized-box и виджет ввода confirm-password.

    // Username
              AnimatedContainer(
                duration: const Duration(milliseconds: 500),
                height: registerAuthMode ? 65 : 0,
                child: AnimatedOpacity(
                  duration: const Duration(milliseconds: 500),
                  opacity: registerAuthMode ? 1 : 0,
                  child: DynamicInputWidget(
                    controller: usernameController,
                    obscureText: false,
                    focusNode: usernameFocusNode,
                    toggleObscureText: null,
                    validator: null,
                    prefIcon: const Icon(Icons.person),
                    labelText: "Enter Username(Optional)",
                    textInputAction: TextInputAction.next,
                    isNonPasswordField: true,
                  ),
                ),
              ),
// We'll also need to fade in/out sizedbox
              AnimatedOpacity(
              duration: const Duration(milliseconds: 500),
              opacity: registerAuthMode ? 1 : 0,
              child: const SizedBox(
                height: 20,
              ),
            ),

// Confirm password 
 AnimatedContainer(
                duration: const Duration(milliseconds: 500),
                height: registerAuthMode ? 65 : 0,
                child: AnimatedOpacity(
                  duration: const Duration(milliseconds: 500),
                  opacity: registerAuthMode ? 1 : 0,
                  child: DynamicInputWidget(
                    controller: confirmPasswordController,
                    focusNode: confirmPasswordFocusNode,
                    isNonPasswordField: false,
                    labelText: "Confirm Password",
                    obscureText: obscureText,
                    prefIcon: const Icon(Icons.password),
                    textInputAction: TextInputAction.done,
                    toggleObscureText: toggleObscureText,
                    validator: (val) => authValidator.confirmPasswordValidator(
                        val, passwordController.text),
                  ),
                ),
              ),


Вход в полноэкранный режим Выход из полноэкранного режима

После этого вы сможете увидеть два ввода на экране аутентификации, поскольку режим входа в систему является нашим режимом по умолчанию. Теперь наш пользовательский интерфейс виджета формы готов. Не путайтесь, ниже вы можете найти весь auth_form_widget.dart целиком.

import 'package:flutter/material.dart';
import 'package:temple/screens/auth/utils/auth_validators.dart';
import 'package:temple/screens/auth/widgets/text_from_widget.dart';

class AuthFormWidget extends StatefulWidget {
  const AuthFormWidget({Key? key}) : super(key: key);

  @override
  State<AuthFormWidget> createState() => _AuthFormWidgetState();
}

class _AuthFormWidgetState extends State<AuthFormWidget> {
  // Define Form key
  final _formKey = GlobalKey<FormState>();

  // Instantiate validator
  final AuthValidators authValidator = AuthValidators();

// controllers
  late TextEditingController emailController;
  late TextEditingController usernameController;
  late TextEditingController passwordController;
  late TextEditingController confirmPasswordController;

// create focus nodes
  late FocusNode emailFocusNode;
  late FocusNode usernameFocusNode;
  late FocusNode passwordFocusNode;
  late FocusNode confirmPasswordFocusNode;

  // to obscure text default value is false
  bool obscureText = true;
  // This will require to toggle between register and sigin in mode
  bool registerAuthMode = false;

// Instantiate all the *text editing controllers* and focus nodes on *initState* function
  @override
  void initState() {
    super.initState();
    emailController = TextEditingController();
    usernameController = TextEditingController();
    passwordController = TextEditingController();
    confirmPasswordController = TextEditingController();

    emailFocusNode = FocusNode();
    usernameFocusNode = FocusNode();
    passwordFocusNode = FocusNode();
    confirmPasswordFocusNode = FocusNode();
  }

// These all need to be disposed of once done so let's do that as well.
  @override
  void dispose() {
    super.dispose();

    emailController.dispose();
    usernameController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();

    emailFocusNode.dispose();
    usernameFocusNode.dispose();
    passwordFocusNode.dispose();
    confirmPasswordFocusNode.dispose();
  }

// Create a function that'll toggle the password's visibility on the relevant icon tap.
  void toggleObscureText() {
    setState(() {
      obscureText = !obscureText;
    });
  }

// Let's create a snack bar to pop info on various circumstances.
// Create a scaffold messanger
  SnackBar msgPopUp(msg) {
    return SnackBar(
        content: Text(
      msg,
      textAlign: TextAlign.center,
    ));
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8),
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            // Email
            DynamicInputWidget(
              controller: emailController,
              obscureText: false,
              focusNode: emailFocusNode,
              toggleObscureText: null,
              validator: authValidator.emailValidator,
              prefIcon: const Icon(Icons.mail),
              labelText: "Enter Email Address",
              textInputAction: TextInputAction.next,
              isNonPasswordField: true,
            ),
            const SizedBox(
              height: 20,
            ),
            // Username
            AnimatedContainer(
              duration: const Duration(milliseconds: 500),
              height: registerAuthMode ? 65 : 0,
              child: AnimatedOpacity(
                duration: const Duration(milliseconds: 500),
                opacity: registerAuthMode ? 1 : 0,
                child: DynamicInputWidget(
                  controller: usernameController,
                  obscureText: false,
                  focusNode: usernameFocusNode,
                  toggleObscureText: null,
                  validator: null,
                  prefIcon: const Icon(Icons.person),
                  labelText: "Enter Username(Optional)",
                  textInputAction: TextInputAction.next,
                  isNonPasswordField: true,
                ),
              ),
            ),

            AnimatedOpacity(
              duration: const Duration(milliseconds: 500),
              opacity: registerAuthMode ? 1 : 0,
              child: const SizedBox(
                height: 20,
              ),
            ),

            DynamicInputWidget(
              controller: passwordController,
              labelText: "Enter Password",
              obscureText: obscureText,
              focusNode: passwordFocusNode,
              toggleObscureText: toggleObscureText,
              validator: authValidator.passwordVlidator,
              prefIcon: const Icon(Icons.password),
              textInputAction: registerAuthMode
                  ? TextInputAction.next
                  : TextInputAction.done,
              isNonPasswordField: false,
            ),

            const SizedBox(
              height: 20,
            ),

            AnimatedContainer(
              duration: const Duration(milliseconds: 500),
              height: registerAuthMode ? 65 : 0,
              child: AnimatedOpacity(
                duration: const Duration(milliseconds: 500),
                opacity: registerAuthMode ? 1 : 0,
                child: DynamicInputWidget(
                  controller: confirmPasswordController,
                  focusNode: confirmPasswordFocusNode,
                  isNonPasswordField: false,
                  labelText: "Confirm Password",
                  obscureText: obscureText,
                  prefIcon: const Icon(Icons.password),
                  textInputAction: TextInputAction.done,
                  toggleObscureText: toggleObscureText,
                  validator: (val) => authValidator.confirmPasswordValidator(
                      val, passwordController.text),
                ),
              ),
            ),
            const SizedBox(
              height: 20,
            ),

            Row(
              mainAxisAlignment: MainAxisAlignment.end,
              children: [
                TextButton(
                  onPressed: () {},
                  child: const Text('Cancel'),
                ),
                const SizedBox(
                  width: 20,
                ),
                ElevatedButton(
                  onPressed: () {},
                  child: Text(registerAuthMode ? 'Register' : 'Sign In'),
                  style: ButtonStyle(
                    elevation: MaterialStateProperty.all(8.0),
                  ),
                ),
              ],
            ),

            const SizedBox(
              height: 20,
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(registerAuthMode
                    ? "Already Have an account?"
                    : "Don't have an account yet?"),
                TextButton(
                  onPressed: () =>
                      setState(() => registerAuthMode = !registerAuthMode),
                  child: Text(registerAuthMode ? "Sign In" : "Regsiter"),
                )
              ],
            )
          ],
        ),
      ),
    );
  }
}

Вход в полноэкранный режим Выход из полноэкранного режима

Наш валидатор все еще не работает, потому что мы все еще не разобрались с тем, что делать при отправке формы. Но давайте проверим результат нашей тяжелой работы.

Резюме

Давайте подведем итоги того, что мы сделали на данный момент.

  1. Создали виджет динамического ввода.
  2. Написали свои личные валидаторы для вводимых данных, которые мы будем принимать.
  3. Создали формы входа и регистрации на одном экране.
  4. Анимировали переход между регистрацией и аутентификацией Sign In.

Показать поддержку

На сегодня это все. Мы поработаем над настройкой Firebase Flutter в следующем разделе. Если у вас есть вопросы, то оставляйте их в разделе комментариев.

Спасибо за ваше время. Не забудьте поставить лайк и поделиться статьей. Надеюсь, вы также подпишитесь на публикацию, чтобы получать уведомления о следующих публикациях. Это Нибеш Кхадка из Khadka’s Coding Lounge. Мы являемся агентством фрилансеров, которое делает высококлассные веб-сайты и мобильные приложения.

Оцените статью
Procodings.ru
Добавить комментарий