toCurrencyString function

String toCurrencyString (
  1. String value,
  2. {int mantissaLength: 2,
  3. ThousandSeparator thousandSeparator: ThousandSeparator.Comma,
  4. ShorteningPolicy shorteningPolicy: ShorteningPolicy.NoShortening,
  5. String leadingSymbol: '',
  6. String trailingSymbol: '',
  7. bool useSymbolPadding: false}
)

thousandSeparator specifies what symbol will be used to separate each block of 3 digits, e.g. ThousandSeparator.Comma will format a million as 1,000,000 shorteningPolicy is used to round values using K for thousands, M for millions and B for billions ShorteningPolicy.NoShortening displays a value of 1234456789.34 as 1,234,456,789.34 but ShorteningPolicy.RoundToThousands displays the same value as 1,234,456K mantissaLength specifies how many digits will be added after a period sign leadingSymbol any symbol (except for the ones that contain digits) the will be added in front of the resulting string. E.g. $ or € some of the signs are available via constants like MoneyInputFormatter.EURO_SIGN but you can basically add any string instead of it. The main rule is that the string must not contain digits, preiods, commas and dashes trailingSymbol is the same as leading but this symbol will be added at the end of your resulting string like 1,250€ instead of €1,250 useSymbolPadding adds a space between the number and trailing / leading symbols like 1,250€ -> 1,250 € or €1,250€ -> € 1,250

Implementation

String toCurrencyString(String value, {
    int mantissaLength = 2,
    ThousandSeparator thousandSeparator = ThousandSeparator.Comma,
    ShorteningPolicy shorteningPolicy = ShorteningPolicy.NoShortening,
    String leadingSymbol = '',
    String trailingSymbol = '',
    bool useSymbolPadding = false
  }) {

  assert(value != null);
  assert(leadingSymbol != null);
  assert(trailingSymbol != null);
  assert(useSymbolPadding != null);
  assert(shorteningPolicy != null);
  assert(thousandSeparator != null);
  assert(mantissaLength != null);

  /// если нужно, чтобы точки заменились на запятые и наоборот
  var swapCommasAndPreriods = false;
  String tSeparator;
  switch (thousandSeparator) {
    case ThousandSeparator.Comma:
      tSeparator = ',';
      break;
    case ThousandSeparator.Period:
      /// вначале все равно запятая, а потом делается своп, если нужен
      /// разделитель тысяч в виде точек
      tSeparator = ',';
      swapCommasAndPreriods = true;
      break;
    case ThousandSeparator.None:
      tSeparator = '';
      break;
    case ThousandSeparator.SpaceAndPeriodMantissa:
      tSeparator = ' ';
      break;
    case ThousandSeparator.SpaceAndCommaMantissa:
      tSeparator = ' ';
      swapCommasAndPreriods = true;
      break;
  }

  value = value.replaceAll(_multiPeriodRegExp, '.');
  value = toNumericString(value, allowPeriod: mantissaLength > 0);
  var isNegative = value.contains('-');
  // парсинг нужен, чтобы избежать лишних
  // символов внутри числа типа -- или множества точек
  var parsed = (double.tryParse(value) ?? 0.0);
  if (parsed == 0.0) {
    if (isNegative) {
      var containsMinus = parsed.toString().contains('-');
      // print('CONTAINS MINUS $containsMinus');
      // parsed = parsed.abs();
      if (!containsMinus) {
        value = '-${parsed.toStringAsFixed(mantissaLength).replaceFirst('0.', '.')}';
      } else {
        value = '${parsed.toStringAsFixed(mantissaLength)}';
      }

    } else {
      value = parsed.toStringAsFixed(mantissaLength);
    }
  }
  var noShortening = shorteningPolicy == ShorteningPolicy.NoShortening;

  var minShorteningLength = 0;
  switch (shorteningPolicy) {
    case ShorteningPolicy.NoShortening:
      break;
    case ShorteningPolicy.RoundToThousands:
      minShorteningLength = 4;
      value = '${_getRoundedValue(value, 1000)}K';
      break;
    case ShorteningPolicy.RoundToMillions:
      minShorteningLength = 7;
      value = '${_getRoundedValue(value, 1000000)}M';
      break;
    case ShorteningPolicy.RoundToBillions:
      minShorteningLength = 10;
      value = '${_getRoundedValue(value, 1000000000)}B';
      break;
    case ShorteningPolicy.RoundToTrillions:
      minShorteningLength = 13;
      value = '${_getRoundedValue(value, 1000000000000)}T';
      break;
    case ShorteningPolicy.Automatic:
      // тут просто по длине строки определяет какое сокращение использовать
      var intValStr = (int.tryParse(value) ?? 0).toString();
      if (intValStr.length < 7) {
        minShorteningLength = 4;
        value = '${_getRoundedValue(value, 1000)}K';
      }
      else if (intValStr.length < 10) {
        minShorteningLength = 7;
        value = '${_getRoundedValue(value, 1000000)}M';
      }
      else if (intValStr.length < 13) {
        minShorteningLength = 10;
        value = '${_getRoundedValue(value, 1000000000)}B';
      }
      else {
        minShorteningLength = 13;
        value = '${_getRoundedValue(value, 1000000000000)}T';
      }
      break;
  }
  var list = <String>[];
  var mantissa = '';
  var split = value.split('');
  var mantissaList = <String>[];
  var mantissaSeparatorIndex = value.indexOf('.');
  if (mantissaSeparatorIndex > -1) {
    var start = mantissaSeparatorIndex + 1;
    var end = start + mantissaLength;
    for (var i = start; i < end; i++) {
      if (i < split.length) {
        mantissaList.add(split[i]);
      } else {
        mantissaList.add('0');
      }
    }
  }

  mantissa = noShortening ? _postProcessMantissa(
    mantissaList.join(''), mantissaLength
  ) : '';
  var maxIndex = split.length - 1;
  if (mantissaSeparatorIndex > 0 && noShortening) {
    maxIndex = mantissaSeparatorIndex - 1;
  }
  var digitCounter = 0;
  if (maxIndex > -1) {
    for (var i = maxIndex; i >= 0; i--) {
      digitCounter++;
      list.add(split[i]);
      if (noShortening) {
        // в случае с отрицательным числом, запятая перед минусом не нужна
        if (digitCounter % 3 == 0 && i > (isNegative ? 1 : 0)) {
          list.add(tSeparator);
        }
      } else {
        if (value.length >= minShorteningLength) {
          if (!isDigit(split[i])) digitCounter = 1;
          if (digitCounter % 3 == 1 && digitCounter > 1 && i > (isNegative ? 1 : 0)) {
            list.add(tSeparator);
          }
        }
      }
    }
  } else {
    list.add('0');
  }

  if (leadingSymbol.isNotEmpty) {
    if (useSymbolPadding) {
      list.add('$leadingSymbol ');
    } else {
      list.add(leadingSymbol);
    }
  }
  var reversed = list.reversed.join('');
  String result;

  if (trailingSymbol.isNotEmpty) {
    if (useSymbolPadding) {
       result = '$reversed$mantissa $trailingSymbol';
    } else {
      result = '$reversed$mantissa$trailingSymbol';
    }
  } else {
    result = '$reversed$mantissa';
  }

  if (swapCommasAndPreriods) {
    return _swapCommasAndPeriods(result);
  }
  return result;
}