Маски ввода номера телефона на Flutter

Всем привет. Нужно поговорить немного про такую стандартную и обыденную часть любого приложения, как ввод номера телефона пользователя. Речь только о российских телефонах. Казалось бы, что может быть проще? Ставим в начало строки + или даже +7 и пользователь указывает телефон начиная с 9. Для удобства можно накинуть и маску и казалось бы все хорошо. Но нет иногда пользователи длоб… смотрят не туда и пишут с 8 или с 7 и все. Без авторизации пользователь не может использовать сервис и дать нам денег на дошик на смузи. Посмотрим несколько примеров из других сервисов:

Как это работает в wildberries

Логика проста: ставим + а дальше пользователь сам поймет что вводить.

Логика проста: ставим +, а дальше пользователь сам поймет что вводить.

Как это работает в Тинькофф

2f2dbaa00659c97f2d5fc858fd2a4139.gif

Задача

Мы делаем пакет для пользователей из России и пары случайных иностранцев. Наши требования:

  • Поле по умолчанию пустое. Пользователь сам решает что и как вводить

  • при вводе 7,8,9 должна работать маска российских номеров (Х (ХХХ) ХХХ-ХХ-ХХ),

  • в маску можно будет ввести и иностранный номер. Например +1

Время стучать по Клавиатуре

Для работы будем использовать наследника TextInputFormatter. Его можно добавить в любое текстовое поле в inputFormatters.А потом вытащить всю нужную нам информацию.

class RuPhoneInputFormatter extends TextInputFormatter{
  @override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    
  }
}

Для работы нам нужны

  1. переменная для форматированного значения

  2. флаг российски ли это номер

  3. метод форматирующий номер

  4. геттер чистого номера (номер без форматирования. Для российских начинается с 9, для иностранных c первой цифры в текстовом поле)

Добавляем нужные переменные и дефолтные значения (вдруг мы номер уже знаем)

class RuPhoneInputFormatter extends TextInputFormatter{
  //форматированный телефон
  String _formattedPhone = "";
  //Российский ли номер
  bool _isRu=false;

  //Добавляем возможность указать номер по дефолту
  RuPhoneInputFormatter({
    String? initialText,
  }) {
    if (initialText != null) {
      formatEditUpdate(
          TextEditingValue.empty, TextEditingValue(text: initialText));
    }
  }

    ///Иетод возвращает форматированнный телефон
  String getMaskedPhone() {
    return _formattedPhone;
  }
  ///возвращает чистый телефон. для России начинается с 9
  String getClearPhone() {
    if(_formattedPhone.isEmpty){
      return '';
    }
    if(!_isRu){
      return _formattedPhone.replaceAll(RegExp(r'\D'), '');
    }
    return _formattedPhone.replaceAll(RegExp(r'\D'), '').substring(
        1,
        (_formattedPhone.replaceAll(RegExp(r'\D'), '').length >= 11)
            ? 11
            : _formattedPhone.replaceAll(RegExp(r'\D'), '').length);
  }
  ///Проверяет заполнил ли пользователь телефон. Актуально только для Российских телефонов
  bool isDone(){
    if(!_isRu){
      return true;
    }
    return (_formattedPhone.replaceAll(RegExp(r'\D'), '').length>10);
  }
  ///возвращает флаг Российски ли номер
  get isRussian=>_isRu;
}

Пишем метод для форматирования

метод должен проверять первые цифры и форматировать только если это 7,8,9

String _formattingPhone(String text){
  //регулярка протиа букв. в телефоне только цифры
    text=text.replaceAll(RegExp(r'\D'), '');
    if(text.isNotEmpty){
      String phone='';
      //проверяем российски ли номер
      if(['7','8','9'].contains(text[0])){
        _isRu=true;
        //если пользователь начал с 9, то добавим 7
        if(text[0]=='9'){
          text='7$text';
        }
        //Проверяем нужен ли +
        String firstSymbols=(text[0]=='8') ? '8':'+7';
        //само форматирование
        phone='$firstSymbols ';
        if(text.length>1){
          phone+='(${text.substring(1,(text.length<4)?text.length:4)}';
        }if(text.length>=5){
          phone+=') ${text.substring(4,(text.length<7)?text.length:7)}';
        }
        if(text.length>=8){
          phone+='-${text.substring(7,(text.length<9)?text.length:9)}';
        }
        if(text.length>=10){
          phone+='-${text.substring(9,(text.length<11)?text.length:11)}';
        }
        return phone;
      }else{
        _isRu=false;
        return '+$text';
      }
    }
    return '';
  }

Собираем все в кучу

Добавим код для удобства редактирования телефона

@override
  TextEditingValue formatEditUpdate(TextEditingValue oldValue, TextEditingValue newValue) {
    String text=newValue.text.replaceAll(RegExp(r'\D'), '');
    int selectionStart=oldValue.selection.end;

    //проверяем стерает ли пользователь все символы?
    if(oldValue.text=='${newValue.text} '){
      _formattedPhone='';
      return TextEditingValue(
          text: _formattedPhone,
          selection: TextSelection(
              baseOffset: _formattedPhone.length,
              extentOffset: _formattedPhone.length,
              affinity: newValue.selection.affinity,
              isDirectional: newValue.selection.isDirectional
          )
      );
    }

    //проверяем редактирует ли пользователь телефон где то по середине?
    if(selectionStart!=_formattedPhone.length){
      _formattedPhone= _formattingPhone(text);
      //если да, то не перекидываем курсов в самый конец

      return TextEditingValue(
          text: _formattedPhone,
          selection: TextSelection(
              baseOffset: newValue.selection.baseOffset,
              extentOffset: newValue.selection.extentOffset,
              affinity: newValue.selection.affinity,
              isDirectional: newValue.selection.isDirectional
          )
      );
    }

    _formattedPhone= _formattingPhone(text);

    //если пользователь просто вводит телефон, 
    //то переносим курсор в конец форматированной строки
    return TextEditingValue(
        text: _formattedPhone,
        selection: TextSelection(
            baseOffset: _formattedPhone.length,
            extentOffset: _formattedPhone.length,
            affinity: newValue.selection.affinity,
            isDirectional: newValue.selection.isDirectional
        )
    );
  }

Итоги

пример работы виджета. пользователь полностью свободен при вводе телефона. Так же сохранилась поддержка и иностранных телефонов.

пример работы виджета. пользователь полностью свободен при вводе телефона. Так же сохранилась поддержка и иностранных телефонов.

А что дальше?

в планах добавить маски всех телефонов стран СНГ.

Пакет

Для удобства все исходники тут

А еще есть пакет на pub.dev тут

Поддержать автора тут

Вдохновение черпал в видосике на Ютубе

© Habrahabr.ru