Files
submanager/lib/widgets/common/form_fields/currency_input_field.dart
JiWoong Sul 0f0b02bf08 feat: 다국어 지원 및 다중 통화 환율 변환 기능 확대
- ExchangeRateService에 JPY, CNY 환율 지원 추가
- 구독 서비스별 다국어 표시 이름 지원
- 분석 화면 차트 및 UI/UX 개선
- 설정 화면 전면 리팩토링
- SMS 스캔 기능 사용성 개선
- 전체 앱 다국어 번역 확대

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 17:34:32 +09:00

172 lines
5.2 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'base_text_field.dart';
import '../../../l10n/app_localizations.dart';
/// 통화 입력 필드 위젯
/// 원화(KRW)와 달러(USD)를 지원하며 자동 포맷팅을 제공합니다.
class CurrencyInputField extends StatefulWidget {
final TextEditingController controller;
final String currency; // 'KRW' or 'USD'
final String? label;
final String? hintText;
final Function(double?)? onChanged;
final String? Function(String?)? validator;
final FocusNode? focusNode;
final TextInputAction? textInputAction;
final Function()? onEditingComplete;
final bool enabled;
const CurrencyInputField({
super.key,
required this.controller,
required this.currency,
this.label,
this.hintText,
this.onChanged,
this.validator,
this.focusNode,
this.textInputAction,
this.onEditingComplete,
this.enabled = true,
});
@override
State<CurrencyInputField> createState() => _CurrencyInputFieldState();
}
class _CurrencyInputFieldState extends State<CurrencyInputField> {
late FocusNode _focusNode;
bool _isFormatted = false;
@override
void initState() {
super.initState();
_focusNode = widget.focusNode ?? FocusNode();
_focusNode.addListener(_onFocusChanged);
// 초기값이 있으면 포맷팅 적용
if (widget.controller.text.isNotEmpty) {
final value = double.tryParse(widget.controller.text.replaceAll(',', ''));
if (value != null) {
widget.controller.text = _formatCurrency(value);
_isFormatted = true;
}
}
}
@override
void dispose() {
if (widget.focusNode == null) {
_focusNode.dispose();
} else {
_focusNode.removeListener(_onFocusChanged);
}
super.dispose();
}
void _onFocusChanged() {
if (!_focusNode.hasFocus && widget.controller.text.isNotEmpty) {
// 포커스를 잃었을 때 포맷팅 적용
final value = _parseValue(widget.controller.text);
if (value != null) {
setState(() {
widget.controller.text = _formatCurrency(value);
_isFormatted = true;
});
}
} else if (_focusNode.hasFocus && _isFormatted) {
// 포커스를 받았을 때 포맷팅 제거
final value = _parseValue(widget.controller.text);
if (value != null) {
setState(() {
if (widget.currency == 'KRW') {
widget.controller.text = value.toInt().toString();
} else {
widget.controller.text = value.toString();
}
_isFormatted = false;
});
// 커서를 끝으로 이동
widget.controller.selection = TextSelection.fromPosition(
TextPosition(offset: widget.controller.text.length),
);
}
}
}
String _formatCurrency(double value) {
if (widget.currency == 'KRW') {
return NumberFormat.decimalPattern().format(value.toInt());
} else {
return NumberFormat('#,##0.00').format(value);
}
}
double? _parseValue(String text) {
final cleanText = text.replaceAll(',', '').replaceAll('', '').replaceAll('\$', '').trim();
return double.tryParse(cleanText);
}
String get _prefixText {
return widget.currency == 'KRW' ? '' : '\$ ';
}
String _getDefaultHintText(BuildContext context) {
return AppLocalizations.of(context).enterAmount;
}
@override
Widget build(BuildContext context) {
return BaseTextField(
controller: widget.controller,
focusNode: _focusNode,
label: widget.label,
hintText: widget.hintText ?? _getDefaultHintText(context),
textInputAction: widget.textInputAction,
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자리까지만 허용
TextInputFormatter.withFunction((oldValue, newValue) {
final text = newValue.text;
if (text.isEmpty) return newValue;
final parts = text.split('.');
if (parts.length > 2) {
// 소수점이 2개 이상인 경우 거부
return oldValue;
}
if (parts.length == 2 && parts[1].length > 2) {
// 소수점 이하가 2자리를 초과하는 경우 거부
return oldValue;
}
return newValue;
}),
],
prefixText: _prefixText,
onEditingComplete: widget.onEditingComplete,
enabled: widget.enabled,
onChanged: (value) {
final parsedValue = _parseValue(value);
widget.onChanged?.call(parsedValue);
},
validator: widget.validator ?? (value) {
if (value == null || value.isEmpty) {
return AppLocalizations.of(context).amountRequired;
}
final parsedValue = _parseValue(value);
if (parsedValue == null || parsedValue <= 0) {
return AppLocalizations.of(context).invalidAmount;
}
return null;
},
);
}
}