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

@@ -1,6 +1,5 @@
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
/// 환율 정보 서비스 클래스

View File

@@ -152,13 +152,6 @@ class NotificationService {
}
}
// 알림 서비스가 초기화되었는지 확인하는 메서드
static bool _isInitialized() {
// 웹 플랫폼인 경우 항상 false 반환
if (_isWeb) return false;
// 초기화 플래그 확인
return _initialized;
}
static Future<bool> requestPermission() async {
final result = await _notifications
@@ -182,7 +175,7 @@ class NotificationService {
}
try {
final androidDetails = AndroidNotificationDetails(
const androidDetails = AndroidNotificationDetails(
'subscription_channel',
'구독 알림',
channelDescription: '구독 관련 알림을 보여줍니다.',
@@ -257,7 +250,7 @@ class NotificationService {
try {
final notificationId = subscription.id.hashCode;
final androidDetails = AndroidNotificationDetails(
const androidDetails = AndroidNotificationDetails(
'subscription_channel',
'구독 알림',
channelDescription: '구독 만료 알림을 보내는 채널입니다.',
@@ -265,13 +258,13 @@ class NotificationService {
priority: Priority.high,
);
final iosDetails = DarwinNotificationDetails(
const iosDetails = DarwinNotificationDetails(
presentAlert: true,
presentBadge: true,
presentSound: true,
);
final notificationDetails = NotificationDetails(
const notificationDetails = NotificationDetails(
android: androidDetails,
iOS: iosDetails,
);

View File

@@ -80,7 +80,6 @@ class SmsScanner {
final nextBillingDateStr = sms['nextBillingDate'] as String?;
// 실제 반복 횟수 사용 (테스트 데이터에서는 이미 제공됨)
final actualRepeatCount = repeatCount > 0 ? repeatCount : 1;
final isRecurring = (sms['isRecurring'] as bool?) ?? (repeatCount >= 2);
final message = sms['message'] as String? ?? '';
// 통화 단위 감지 - 메시지 내용과 서비스명 모두 검사
@@ -205,79 +204,7 @@ class SmsScanner {
return serviceUrls[serviceName];
}
bool _containsSubscriptionKeywords(String text) {
final keywords = [
'구독',
'결제',
'청구',
'정기',
'자동',
'subscription',
'payment',
'bill',
'invoice'
];
return keywords
.any((keyword) => text.toLowerCase().contains(keyword.toLowerCase()));
}
double? _extractAmount(String text) {
final RegExp amountRegex = RegExp(r'(\d{1,3}(?:,\d{3})*(?:\.\d{2})?)');
final match = amountRegex.firstMatch(text);
if (match != null) {
final amountStr = match.group(1)?.replaceAll(',', '');
return double.tryParse(amountStr ?? '');
}
return null;
}
String? _extractServiceName(String text) {
final serviceNames = [
'Netflix',
'Spotify',
'Disney+',
'Apple Music',
'YouTube Premium',
'Amazon Prime',
'Microsoft 365',
'Google One',
'iCloud',
'Dropbox'
];
for (final name in serviceNames) {
if (text.contains(name)) {
return name;
}
}
return null;
}
String _extractBillingCycle(String text) {
if (text.contains('') || text.contains('month')) {
return 'monthly';
} else if (text.contains('') || text.contains('year')) {
return 'yearly';
} else if (text.contains('') || text.contains('week')) {
return 'weekly';
}
return 'monthly'; // 기본값
}
DateTime _extractNextBillingDate(String text) {
final RegExp dateRegex = RegExp(r'(\d{4}[-/]\d{2}[-/]\d{2})');
final match = dateRegex.firstMatch(text);
if (match != null) {
final dateStr = match.group(1);
if (dateStr != null) {
final date = DateTime.tryParse(dateStr);
if (date != null) {
return date;
}
}
}
return DateTime.now().add(const Duration(days: 30)); // 기본값: 30일 후
}
// 메시지에서 통화 단위를 감지하는 함수
String _detectCurrency(String message) {

View File

@@ -1,4 +1,3 @@
import 'package:flutter/material.dart';
/// 구독 서비스와 웹사이트 URL 매칭을 처리하는 서비스 클래스
class SubscriptionUrlMatcher {