204 lines
5.7 KiB
Dart
204 lines
5.7 KiB
Dart
import 'package:flutter/services.dart';
|
|
import 'package:intl/intl.dart';
|
|
|
|
/// 한국식 숫자 포맷팅 유틸리티
|
|
class KoreanNumberFormatter {
|
|
static final _currencyFormat = NumberFormat.currency(
|
|
locale: 'ko_KR',
|
|
symbol: '₩',
|
|
decimalDigits: 0,
|
|
);
|
|
|
|
static final _decimalFormat = NumberFormat.decimalPattern('ko_KR');
|
|
|
|
/// 통화 포맷팅 (₩1,234,567)
|
|
static String formatCurrency(num amount) {
|
|
return _currencyFormat.format(amount);
|
|
}
|
|
|
|
/// 통화 포맷팅 - 심플 (1,234,567원)
|
|
static String formatCurrencySimple(num amount) {
|
|
return '${_decimalFormat.format(amount)}원';
|
|
}
|
|
|
|
/// 수량 포맷팅 (1,234개)
|
|
static String formatQuantity(int quantity, {String unit = '개'}) {
|
|
return '${_decimalFormat.format(quantity)}$unit';
|
|
}
|
|
|
|
/// 일반 숫자 포맷팅 (1,234,567)
|
|
static String formatNumber(num number) {
|
|
return _decimalFormat.format(number);
|
|
}
|
|
|
|
/// 퍼센트 포맷팅 (85.2%)
|
|
static String formatPercent(double value, {int decimalDigits = 1}) {
|
|
final formatter = NumberFormat.percentPattern('ko_KR')
|
|
..minimumFractionDigits = decimalDigits
|
|
..maximumFractionDigits = decimalDigits;
|
|
return formatter.format(value);
|
|
}
|
|
|
|
/// 파일 크기 포맷팅 (1.5MB, 256KB 등)
|
|
static String formatFileSize(int bytes) {
|
|
if (bytes < 1024) {
|
|
return '${bytes}B';
|
|
} else if (bytes < 1024 * 1024) {
|
|
return '${(bytes / 1024).toStringAsFixed(1)}KB';
|
|
} else if (bytes < 1024 * 1024 * 1024) {
|
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
|
|
} else {
|
|
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)}GB';
|
|
}
|
|
}
|
|
|
|
/// 한국식 단위 포맷팅 (만, 억, 조)
|
|
static String formatKoreanUnit(num number) {
|
|
final abs = number.abs();
|
|
final isNegative = number < 0;
|
|
String result;
|
|
|
|
if (abs >= 1000000000000) {
|
|
// 조 단위
|
|
result = '${(abs / 1000000000000).toStringAsFixed(1)}조';
|
|
} else if (abs >= 100000000) {
|
|
// 억 단위
|
|
result = '${(abs / 100000000).toStringAsFixed(1)}억';
|
|
} else if (abs >= 10000) {
|
|
// 만 단위
|
|
result = '${(abs / 10000).toStringAsFixed(1)}만';
|
|
} else {
|
|
result = _decimalFormat.format(abs);
|
|
}
|
|
|
|
return isNegative ? '-$result' : result;
|
|
}
|
|
|
|
/// 기간 포맷팅 (3개월, 1년 2개월 등)
|
|
static String formatPeriod(int months) {
|
|
if (months == 0) return '0개월';
|
|
|
|
final years = months ~/ 12;
|
|
final remainingMonths = months % 12;
|
|
|
|
if (years == 0) {
|
|
return '$remainingMonths개월';
|
|
} else if (remainingMonths == 0) {
|
|
return '$years년';
|
|
} else {
|
|
return '$years년 $remainingMonths개월';
|
|
}
|
|
}
|
|
|
|
/// 남은 일수 포맷팅
|
|
static String formatRemainingDays(int days) {
|
|
if (days < 0) {
|
|
return '${-days}일 지남';
|
|
} else if (days == 0) {
|
|
return '오늘';
|
|
} else if (days == 1) {
|
|
return '내일';
|
|
} else if (days <= 7) {
|
|
return '$days일 남음';
|
|
} else if (days <= 30) {
|
|
final weeks = days ~/ 7;
|
|
return '$weeks주 남음';
|
|
} else if (days <= 365) {
|
|
final months = days ~/ 30;
|
|
return '$months개월 남음';
|
|
} else {
|
|
final years = days ~/ 365;
|
|
return '$years년 남음';
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 숫자 입력 필드용 TextInputFormatter
|
|
class ThousandsSeparatorInputFormatter extends TextInputFormatter {
|
|
final String separator;
|
|
|
|
ThousandsSeparatorInputFormatter({this.separator = ','});
|
|
|
|
@override
|
|
TextEditingValue formatEditUpdate(
|
|
TextEditingValue oldValue,
|
|
TextEditingValue newValue,
|
|
) {
|
|
// 숫자만 추출
|
|
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^\d]'), '');
|
|
|
|
if (digitsOnly.isEmpty) {
|
|
return const TextEditingValue();
|
|
}
|
|
|
|
// 천 단위 구분자 추가
|
|
final formatted = _addThousandsSeparator(digitsOnly);
|
|
|
|
// 커서 위치 계산
|
|
int cursorPosition = formatted.length;
|
|
|
|
// 입력 중 커서 위치 조정
|
|
if (newValue.selection.baseOffset < newValue.text.length) {
|
|
final beforeCursor = newValue.text.substring(0, newValue.selection.baseOffset);
|
|
final digitsBeforeCursor = beforeCursor.replaceAll(RegExp(r'[^\d]'), '').length;
|
|
|
|
cursorPosition = 0;
|
|
int digitCount = 0;
|
|
for (int i = 0; i < formatted.length; i++) {
|
|
if (formatted[i] != separator) {
|
|
digitCount++;
|
|
}
|
|
cursorPosition++;
|
|
if (digitCount == digitsBeforeCursor) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return TextEditingValue(
|
|
text: formatted,
|
|
selection: TextSelection.collapsed(offset: cursorPosition),
|
|
);
|
|
}
|
|
|
|
String _addThousandsSeparator(String digitsOnly) {
|
|
final buffer = StringBuffer();
|
|
final length = digitsOnly.length;
|
|
|
|
for (int i = 0; i < length; i++) {
|
|
buffer.write(digitsOnly[i]);
|
|
|
|
final remaining = length - i - 1;
|
|
if (remaining > 0 && remaining % 3 == 0) {
|
|
buffer.write(separator);
|
|
}
|
|
}
|
|
|
|
return buffer.toString();
|
|
}
|
|
}
|
|
|
|
/// 통화 입력 필드용 TextInputFormatter
|
|
class CurrencyInputFormatter extends TextInputFormatter {
|
|
@override
|
|
TextEditingValue formatEditUpdate(
|
|
TextEditingValue oldValue,
|
|
TextEditingValue newValue,
|
|
) {
|
|
// 숫자만 추출
|
|
final digitsOnly = newValue.text.replaceAll(RegExp(r'[^\d]'), '');
|
|
|
|
if (digitsOnly.isEmpty) {
|
|
return const TextEditingValue();
|
|
}
|
|
|
|
// 천 단위 구분자 추가
|
|
final number = int.tryParse(digitsOnly) ?? 0;
|
|
final formatted = '₩${KoreanNumberFormatter.formatNumber(number)}';
|
|
|
|
return TextEditingValue(
|
|
text: formatted,
|
|
selection: TextSelection.collapsed(offset: formatted.length),
|
|
);
|
|
}
|
|
} |