Refactor screens to MVC architecture with modular widgets

- Extract business logic from screens into dedicated controllers
- Split large screen files into smaller, reusable widget components
- Add controllers for AddSubscriptionScreen and DetailScreen
- Create modular widgets for subscription and detail features
- Improve code organization and maintainability
- Remove duplicated code and improve reusability

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-11 00:21:18 +09:00
parent 4731288622
commit 83c5e3d64e
56 changed files with 9092 additions and 4579 deletions

View File

@@ -0,0 +1,138 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'base_text_field.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 TextEditingController _formattedController;
@override
void initState() {
super.initState();
_formattedController = TextEditingController();
_updateFormattedValue();
widget.controller.addListener(_onControllerChanged);
}
@override
void dispose() {
widget.controller.removeListener(_onControllerChanged);
_formattedController.dispose();
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 = '';
}
}
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 get _defaultHintText {
return widget.currency == 'KRW' ? '금액을 입력하세요' : 'Enter amount';
}
@override
Widget build(BuildContext context) {
return BaseTextField(
controller: _formattedController,
focusNode: widget.focusNode,
label: widget.label,
hintText: widget.hintText ?? _defaultHintText,
textInputAction: widget.textInputAction,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[\d,.]')),
],
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),
);
}
}
},
validator: widget.validator ?? (value) {
if (value == null || value.isEmpty) {
return '금액을 입력해주세요';
}
final parsedValue = _parseValue(value);
if (parsedValue == null || parsedValue <= 0) {
return '올바른 금액을 입력해주세요';
}
return null;
},
);
}
}