Доходы, ребалансировка, налоги, импорт, цели, здоровье данных, правила, транзакции, категории, cashflow, pending: новые виджеты и токены темы, dart format. Тесты подстроены под новые модели.
40 lines
1.5 KiB
Dart
40 lines
1.5 KiB
Dart
import 'package:decimal/decimal.dart';
|
|
|
|
/// Weights travel as shares (`"0.60"` = 60 %) and are edited as percents. The conversion is
|
|
/// exact on both sides: `Decimal`, never `double`, so 33,33 % round-trips unchanged and a
|
|
/// set that sums to exactly 1 does not fail validation because of binary floating point.
|
|
|
|
final _hundred = Decimal.fromInt(100);
|
|
|
|
/// `"0.605"` → `"60,5"`; empty for anything unparseable.
|
|
String shareToPercentText(String? share) {
|
|
final d = share == null ? null : Decimal.tryParse(share);
|
|
if (d == null) return '';
|
|
final p = d * _hundred;
|
|
final text = p == p.truncate() ? p.truncate().toString() : p.toString();
|
|
return text.replaceAll('.', ',');
|
|
}
|
|
|
|
/// `"60,5"` → `"0.605"`; null when the text is not a number.
|
|
String? percentTextToShare(String text) {
|
|
final normalized = text.trim().replaceAll(',', '.').replaceAll(' ', '');
|
|
if (normalized.isEmpty) return null;
|
|
final d = Decimal.tryParse(normalized);
|
|
if (d == null) return null;
|
|
return (d / _hundred).toDecimal(scaleOnInfinitePrecision: 10).toString();
|
|
}
|
|
|
|
/// `"0.032"` → `"3,20 %"`, signed. Kept local to the rebalance screen because drift is a
|
|
/// share, not a return, and the portfolio helper would sign it the same way by accident.
|
|
String formatShareAsPercent(
|
|
String? share, {
|
|
bool signed = false,
|
|
int digits = 2,
|
|
}) {
|
|
final d = share == null ? null : Decimal.tryParse(share);
|
|
if (d == null) return '—';
|
|
final p = (d * _hundred).toDouble();
|
|
final sign = signed && p > 0 ? '+' : '';
|
|
return '$sign${p.toStringAsFixed(digits).replaceAll('.', ',')} %';
|
|
}
|