feat: 폼 필드 컴포넌트 분리 및 구독 카드 인터랙션 개선
- billing_cycle_selector, category_selector, currency_selector 컴포넌트 분리 - 구독 카드 클릭 이슈 해결을 위한 리팩토링 - SMS 스캔 화면 UI/UX 개선 및 기능 강화 - 상세 화면 컨트롤러 로직 개선 - 알림 서비스 및 구독 URL 매칭 기능 추가 - CLAUDE.md 프로젝트 가이드라인 대폭 확장 - 전반적인 코드 구조 개선 및 타입 안정성 강화 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -36,33 +36,62 @@ class CurrencyInputField extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
late TextEditingController _formattedController;
|
||||
late FocusNode _focusNode;
|
||||
bool _isFormatted = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_formattedController = TextEditingController();
|
||||
_updateFormattedValue();
|
||||
widget.controller.addListener(_onControllerChanged);
|
||||
_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() {
|
||||
widget.controller.removeListener(_onControllerChanged);
|
||||
_formattedController.dispose();
|
||||
if (widget.focusNode == null) {
|
||||
_focusNode.dispose();
|
||||
} else {
|
||||
_focusNode.removeListener(_onFocusChanged);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onControllerChanged() {
|
||||
_updateFormattedValue();
|
||||
}
|
||||
|
||||
void _updateFormattedValue() {
|
||||
final value = double.tryParse(widget.controller.text.replaceAll(',', ''));
|
||||
if (value != null) {
|
||||
_formattedController.text = _formatCurrency(value);
|
||||
} else {
|
||||
_formattedController.text = '';
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,38 +119,42 @@ class _CurrencyInputFieldState extends State<CurrencyInputField> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BaseTextField(
|
||||
controller: _formattedController,
|
||||
focusNode: widget.focusNode,
|
||||
controller: widget.controller,
|
||||
focusNode: _focusNode,
|
||||
label: widget.label,
|
||||
hintText: widget.hintText ?? _defaultHintText,
|
||||
textInputAction: widget.textInputAction,
|
||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.allow(RegExp(r'[\d,.]')),
|
||||
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);
|
||||
if (parsedValue != null) {
|
||||
widget.controller.text = parsedValue.toString();
|
||||
widget.onChanged?.call(parsedValue);
|
||||
} else {
|
||||
widget.controller.text = '';
|
||||
widget.onChanged?.call(null);
|
||||
}
|
||||
|
||||
// 포맷팅 업데이트
|
||||
if (parsedValue != null) {
|
||||
final formattedText = _formatCurrency(parsedValue);
|
||||
if (formattedText != value) {
|
||||
_formattedController.value = TextEditingValue(
|
||||
text: formattedText,
|
||||
selection: TextSelection.collapsed(offset: formattedText.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
widget.onChanged?.call(parsedValue);
|
||||
},
|
||||
validator: widget.validator ?? (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
|
||||
Reference in New Issue
Block a user