formatEditUpdate method
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;
}