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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

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

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

Задача

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

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

  • при вводе 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 тут

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

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

Источник: https://habr.com/ru/articles/729216/


Интересные статьи

Интересные статьи

Иногда при внедрении интерфейса недостаточно тех возможностей кастомизации, которые предоставляет Flutter. Подтверждением этому является большое количество вопросов на Stackoverflow, типа, как добав...
Привет, Хабр!На связи разработчики из Mad Brains. Мы специализируемся на разработке сервисов для мобильных устройств. Имеем опыт в реализации интеграционных решений, со...
Всем привет! Меня зовут Дмитрий Андриянов, я Flutter-разработчик в Surf. В предыдущей статье про RenderObject я рассказал, как немного копнул в слой рендеринга и смог получать располож...
Это заключительная часть лаконичной интерпретации документации по Flutter, которая будет полезна Xamarin.Forms-разработчикам. Учитывая текущую ситуацию, сейчас самое время изучать что-то новое! П...
Эта публикация написана после неоднократных обращений как клиентов, так и (к горести моей) партнеров. Темы обращений были разные, но причиной в итоге оказывался один и тот же сценарий, реализу...