formatEditUpdate method

  1. @override
TextEditingValue formatEditUpdate(
  1. TextEditingValue oldValue,
  2. TextEditingValue newValue
)
override

Called when text is being typed or cut/copy/pasted in the EditableText.

You can override the resulting text based on the previous text value and the incoming new text value.

When formatters are chained, oldValue reflects the initial value of TextEditingValue at the beginning of the chain.

Implementation

@override
TextEditingValue formatEditUpdate(
  TextEditingValue oldValue,
  TextEditingValue newValue,
) {
  if (oldValue.text == newValue.text) {
    return newValue;
  }

  /// since IOS keyboard doesn't have . but ,
  /// we have to overwrite the comma into period
  final oldOffset = oldValue.selection.baseOffset;
  final newOffset = newValue.selection.baseOffset;

  if (newOffset - oldOffset == 1 &&
      newValue.text.substring(oldOffset, newOffset) == ",") {
    newValue = newValue.copyWith(
      text:
          "${newValue.text.substring(0, oldOffset)}.${newValue.text.substring(newOffset, newValue.text.length)}",
    );
  }

  /// if something pasted to here and starts with 0, we will basically reduce the position by 1
  /// because
  /// 01 will become 1
  /// 02 will become 2
  if (newValue.text.startsWith("0") && newValue.selection.baseOffset > 0) {
    newValue = newValue.copyWith(
      selection: newValue.selection.copyWith(
        baseOffset: newValue.selection.baseOffset - 1,
        extentOffset: newValue.selection.extentOffset - 1,
      ),
    );
  }

  /// because the zero position for textfield is 0.00
  /// this script below will ignore the 0 when user type
  /// |0.00 => user type 1 => 1|0.00
  /// with this script, it will become 1|.00

  final oldPeriodIndex = oldValue.text.indexOf(".");
  final newPeriodIndex = newValue.text.indexOf(".");

  if (oldPeriodIndex >0 && newPeriodIndex >0) {
    if (oldValue.text
                .substring(oldPeriodIndex - 1, oldPeriodIndex + 1) ==
            "0." &&
        newValue.text.substring(newPeriodIndex - 1, newPeriodIndex + 1) ==
            "0." &&
        oldValue.selection.baseOffset == 0) {
      newValue = newValue.copyWith(
        text:
            "${newValue.text.substring(0, newValue.text.length - 4)}.${newValue.text.substring(newValue.text.length - 2)}",
        selection: newValue.selection.copyWith(
          baseOffset: newValue.selection.baseOffset,
          extentOffset: newValue.selection.extentOffset,
        ),
      );
    }
  }

  var newNormalizedValue = newValue;

  final dotIndex = newNormalizedValue.text.indexOf(".") + 1;

  /// if the decimal being deleted, will replace the deleted digit with 0
  if (oldValue.text.length > newValue.text.length &&
      newNormalizedValue.selection.baseOffset >= dotIndex &&
      dotIndex != 0) {
    final text = newNormalizedValue.text;

    newNormalizedValue = newNormalizedValue.copyWith(
      text:
          "${text.substring(0, newNormalizedValue.selection.baseOffset)}0${newNormalizedValue.selection.baseOffset > text.length ? "" : text.substring(newNormalizedValue.selection.baseOffset)}",
    );
  }

  /// remove comma from the old value, and update base & extent offset accordingly
  final cleanOldValueText = oldValue.text.replaceAll(",", "");
  final cleanOldValueBase = oldValue.selection.baseOffset -
      ","
          .allMatches(
              oldValue.text.substring(0, oldValue.selection.baseOffset))
          .length;
  final cleanOldValueExtent = oldValue.selection.extentOffset -
      ","
          .allMatches(
              oldValue.text.substring(0, oldValue.selection.extentOffset))
          .length;
  final cleanOldValue = oldValue.copyWith(
    text: cleanOldValueText,
    selection: oldValue.selection.copyWith(
      baseOffset: cleanOldValueBase,
      extentOffset: cleanOldValueExtent,
    ),
  );

  /// remove comma from the new value, and update base & extent offset accordingly
  final oldValueDotIndex = oldValue.text.indexOf(".");
  var cleanNewValueText = newNormalizedValue.text.replaceAll(",", "");

  /// if the decimal separator is deleted, will revert to the old value (the one with dot)
  if (oldValueDotIndex >= 0 &&
      oldValue.text.length > newValue.text.length &&
      newNormalizedValue.selection.baseOffset == oldValueDotIndex) {
    cleanNewValueText = cleanOldValueText;
  }

  var cleanNewValueBase = newNormalizedValue.selection.baseOffset -
      ","
          .allMatches(newNormalizedValue.text
              .substring(0, newNormalizedValue.selection.baseOffset))
          .length;
  var cleanNewValueExtent = newNormalizedValue.selection.extentOffset -
      ","
          .allMatches(newNormalizedValue.text
              .substring(0, newNormalizedValue.selection.extentOffset))
          .length;

  /// to make any typing in decimal replace the next digit instead appending one more
  cleanNewValueText = normalizeDecimal(
    cleanNewValueText,
    newNormalizedValue.copyWith(
      selection: newNormalizedValue.selection.copyWith(
        baseOffset: cleanNewValueBase,
        extentOffset: cleanNewValueExtent,
      ),
    ),
    oldValue,
    precision,
  );

  cleanNewValueBase = cleanNewValueBase.clamp(0, cleanNewValueText.length);
  cleanNewValueExtent =
      cleanNewValueExtent.clamp(0, cleanNewValueText.length);

  final cleanNewValue = newNormalizedValue.copyWith(
    text: cleanNewValueText,
    selection: newNormalizedValue.selection.copyWith(
      baseOffset: cleanNewValueBase,
      extentOffset: cleanNewValueExtent,
    ),
  );

  /// call super to filter all excluded characters
  var x = super.formatEditUpdate(cleanOldValue, cleanNewValue);

  /// after calling super, the decimal separator will be deleted
  /// normalize is to divide the parsed normalized text with factor
  /// initial value: 1234.99
  /// after calling super: 123499
  /// after normalized: 1234.99
  x = normalize(x, oldValue, cleanNewValue, newNormalizedValue);

  /// if the length of text is exceeded
  if (x.text.length > (digitLimit + 1 + precision)) {
    /// will call super again but with oldvalue
    x = super.formatEditUpdate(cleanOldValue, cleanOldValue);

    /// after that it will be normalized
    x = normalize(x, oldValue, cleanOldValue, newNormalizedValue);

    /// if the result is empty, will trim the text instead
    if (x.text.isEmpty) {
      x = super.formatEditUpdate(cleanOldValue, cleanNewValue);

      x = x.copyWith(text: x.text.substring(0, digitLimit));
    }
  }

  /// format text with NumberFormat
  final finalResult = _format(cleanOldValue, x);

  return finalResult;
}