feat: adopt material 3 theme and billing adjustments
This commit is contained in:
@@ -5,10 +5,10 @@ import 'base_text_field.dart';
|
||||
import '../../../l10n/app_localizations.dart';
|
||||
|
||||
/// 통화 입력 필드 위젯
|
||||
/// 원화(KRW)와 달러(USD)를 지원하며 자동 포맷팅을 제공합니다.
|
||||
/// KRW/JPY(정수), USD/CNY(소수점 2자리)를 지원하며 자동 포맷팅을 제공합니다.
|
||||
class CurrencyInputField extends StatefulWidget {
|
||||
final TextEditingController controller;
|
||||
final String currency; // 'KRW' or 'USD'
|
||||
final String currency; // 'KRW' | 'USD' | 'JPY' | 'CNY'
|
||||
final String? label;
|
||||
final String? hintText;
|
||||
final Function(double?)? onChanged;
|
||||
@@ -39,6 +39,7 @@ class CurrencyInputField extends StatefulWidget {
|
||||
class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
late FocusNode _focusNode;
|
||||
bool _isFormatted = false;
|
||||
bool _isPostFrameUpdating = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -66,6 +67,29 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CurrencyInputField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.currency != widget.currency) {
|
||||
// 통화 변경 시 빌드 이후에 안전하게 재포맷 적용
|
||||
if (_focusNode.hasFocus) return;
|
||||
final value = _parseValue(widget.controller.text);
|
||||
if (value == null) return;
|
||||
final formatted = _formatCurrency(value);
|
||||
if (widget.controller.text == formatted || _isPostFrameUpdating) return;
|
||||
_isPostFrameUpdating = true;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!mounted) return;
|
||||
widget.controller.value = TextEditingValue(
|
||||
text: formatted,
|
||||
selection: TextSelection.collapsed(offset: formatted.length),
|
||||
);
|
||||
_isFormatted = true;
|
||||
_isPostFrameUpdating = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void _onFocusChanged() {
|
||||
if (!_focusNode.hasFocus && widget.controller.text.isNotEmpty) {
|
||||
// 포커스를 잃었을 때 포맷팅 적용
|
||||
@@ -81,7 +105,7 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
final value = _parseValue(widget.controller.text);
|
||||
if (value != null) {
|
||||
setState(() {
|
||||
if (widget.currency == 'KRW') {
|
||||
if (_isIntegerCurrency(widget.currency)) {
|
||||
widget.controller.text = value.toInt().toString();
|
||||
} else {
|
||||
widget.controller.text = value.toString();
|
||||
@@ -97,7 +121,7 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
}
|
||||
|
||||
String _formatCurrency(double value) {
|
||||
if (widget.currency == 'KRW') {
|
||||
if (_isIntegerCurrency(widget.currency)) {
|
||||
return NumberFormat.decimalPattern().format(value.toInt());
|
||||
} else {
|
||||
return NumberFormat('#,##0.00').format(value);
|
||||
@@ -108,13 +132,26 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
final cleanText = text
|
||||
.replaceAll(',', '')
|
||||
.replaceAll('₩', '')
|
||||
.replaceAll('¥', '')
|
||||
.replaceAll('¥', '')
|
||||
.replaceAll('\$', '')
|
||||
.trim();
|
||||
return double.tryParse(cleanText);
|
||||
}
|
||||
|
||||
// ignore: unused_element
|
||||
String get _prefixText {
|
||||
return widget.currency == 'KRW' ? '₩ ' : '\$ ';
|
||||
switch (widget.currency) {
|
||||
case 'KRW':
|
||||
return '₩ ';
|
||||
case 'JPY':
|
||||
return '¥ ';
|
||||
case 'CNY':
|
||||
return '¥ ';
|
||||
case 'USD':
|
||||
default:
|
||||
return '4 ';
|
||||
}
|
||||
}
|
||||
|
||||
String _getDefaultHintText(BuildContext context) {
|
||||
@@ -132,26 +169,27 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(
|
||||
widget.currency == 'KRW' ? RegExp(r'[0-9]') : RegExp(r'[0-9.]')),
|
||||
if (widget.currency == 'USD')
|
||||
// USD의 경우 소수점 이하 2자리까지만 허용
|
||||
_isIntegerCurrency(widget.currency)
|
||||
? RegExp(r'[0-9]')
|
||||
: RegExp(r'[0-9.]'),
|
||||
),
|
||||
if (!_isIntegerCurrency(widget.currency))
|
||||
// 소수 통화(USD/CNY): 소수점 이하 2자리 제한
|
||||
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||
final text = newValue.text;
|
||||
if (text.isEmpty) return newValue;
|
||||
|
||||
final parts = text.split('.');
|
||||
if (parts.length > 2) {
|
||||
// 소수점이 2개 이상인 경우 거부
|
||||
return oldValue;
|
||||
return oldValue; // 소수점이 2개 이상인 경우 거부
|
||||
}
|
||||
if (parts.length == 2 && parts[1].length > 2) {
|
||||
// 소수점 이하가 2자리를 초과하는 경우 거부
|
||||
return oldValue;
|
||||
return oldValue; // 소수점 이하 2자 초과 거부
|
||||
}
|
||||
return newValue;
|
||||
}),
|
||||
],
|
||||
prefixText: _prefixText,
|
||||
prefixText: _getPrefixText(),
|
||||
onEditingComplete: widget.onEditingComplete,
|
||||
enabled: widget.enabled,
|
||||
onChanged: (value) {
|
||||
@@ -172,3 +210,23 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool _isIntegerCurrency(String code) => code == 'KRW' || code == 'JPY';
|
||||
|
||||
// 안전한 프리픽스 계산 함수(모든 통화 지원)
|
||||
String _currencySymbol(String code) {
|
||||
switch (code) {
|
||||
case 'KRW':
|
||||
return '₩';
|
||||
case 'JPY':
|
||||
case 'CNY':
|
||||
return '¥';
|
||||
case 'USD':
|
||||
default:
|
||||
return '\$';
|
||||
}
|
||||
}
|
||||
|
||||
extension on _CurrencyInputFieldState {
|
||||
String _getPrefixText() => '${_currencySymbol(widget.currency)} ';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user