feat: SMS 스캔 화면 리팩토링 및 MVC 패턴 적용
- SMS 스캔 화면을 컨트롤러/서비스/위젯으로 분리 - 코드 가독성 및 유지보수성 향상 - 새로운 다국어 지원 키 추가 - Git 커밋 가이드라인 문서화
This commit is contained in:
224
lib/controllers/sms_scan_controller.dart
Normal file
224
lib/controllers/sms_scan_controller.dart
Normal file
@@ -0,0 +1,224 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/sms_scanner.dart';
|
||||
import '../models/subscription.dart';
|
||||
import '../models/subscription_model.dart';
|
||||
import '../services/sms_scan/subscription_converter.dart';
|
||||
import '../services/sms_scan/subscription_filter.dart';
|
||||
import '../providers/subscription_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../providers/navigation_provider.dart';
|
||||
import '../providers/category_provider.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
class SmsScanController extends ChangeNotifier {
|
||||
// 상태 관리
|
||||
bool _isLoading = false;
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
String? _errorMessage;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
List<Subscription> _scannedSubscriptions = [];
|
||||
List<Subscription> get scannedSubscriptions => _scannedSubscriptions;
|
||||
|
||||
int _currentIndex = 0;
|
||||
int get currentIndex => _currentIndex;
|
||||
|
||||
String? _selectedCategoryId;
|
||||
String? get selectedCategoryId => _selectedCategoryId;
|
||||
|
||||
final TextEditingController websiteUrlController = TextEditingController();
|
||||
|
||||
// 의존성
|
||||
final SmsScanner _smsScanner = SmsScanner();
|
||||
final SubscriptionConverter _converter = SubscriptionConverter();
|
||||
final SubscriptionFilter _filter = SubscriptionFilter();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
websiteUrlController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void setSelectedCategoryId(String? categoryId) {
|
||||
_selectedCategoryId = categoryId;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetWebsiteUrl() {
|
||||
websiteUrlController.text = '';
|
||||
}
|
||||
|
||||
Future<void> scanSms(BuildContext context) async {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
_scannedSubscriptions = [];
|
||||
_currentIndex = 0;
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// SMS 스캔 실행
|
||||
print('SMS 스캔 시작');
|
||||
final scannedSubscriptionModels = await _smsScanner.scanForSubscriptions();
|
||||
print('스캔된 구독: ${scannedSubscriptionModels.length}개');
|
||||
|
||||
if (scannedSubscriptionModels.isNotEmpty) {
|
||||
print('첫 번째 구독: ${scannedSubscriptionModels[0].serviceName}, 반복 횟수: ${scannedSubscriptionModels[0].repeatCount}');
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (scannedSubscriptionModels.isEmpty) {
|
||||
print('스캔된 구독이 없음');
|
||||
_errorMessage = AppLocalizations.of(context).subscriptionNotFound;
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
// SubscriptionModel을 Subscription으로 변환
|
||||
final scannedSubscriptions = _converter.convertModelsToSubscriptions(scannedSubscriptionModels);
|
||||
|
||||
// 2회 이상 반복 결제된 구독만 필터링
|
||||
final repeatSubscriptions = _filter.filterByRepeatCount(scannedSubscriptions, 2);
|
||||
print('반복 결제된 구독: ${repeatSubscriptions.length}개');
|
||||
|
||||
if (repeatSubscriptions.isNotEmpty) {
|
||||
print('첫 번째 반복 구독: ${repeatSubscriptions[0].serviceName}, 반복 횟수: ${repeatSubscriptions[0].repeatCount}');
|
||||
}
|
||||
|
||||
if (repeatSubscriptions.isEmpty) {
|
||||
print('반복 결제된 구독이 없음');
|
||||
_errorMessage = AppLocalizations.of(context).repeatSubscriptionNotFound;
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
// 구독 목록 가져오기
|
||||
final provider = Provider.of<SubscriptionProvider>(context, listen: false);
|
||||
final existingSubscriptions = provider.subscriptions;
|
||||
print('기존 구독: ${existingSubscriptions.length}개');
|
||||
|
||||
// 중복 구독 필터링
|
||||
final filteredSubscriptions = _filter.filterDuplicates(repeatSubscriptions, existingSubscriptions);
|
||||
print('중복 제거 후 구독: ${filteredSubscriptions.length}개');
|
||||
|
||||
if (filteredSubscriptions.isNotEmpty) {
|
||||
print('첫 번째 필터링된 구독: ${filteredSubscriptions[0].serviceName}, 반복 횟수: ${filteredSubscriptions[0].repeatCount}');
|
||||
}
|
||||
|
||||
// 중복 제거 후 신규 구독이 없는 경우
|
||||
if (filteredSubscriptions.isEmpty) {
|
||||
print('중복 제거 후 신규 구독이 없음');
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
_scannedSubscriptions = filteredSubscriptions;
|
||||
_isLoading = false;
|
||||
websiteUrlController.text = ''; // URL 입력 필드 초기화
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
print('SMS 스캔 중 오류 발생: $e');
|
||||
if (context.mounted) {
|
||||
_errorMessage = AppLocalizations.of(context).smsScanErrorWithMessage(e.toString());
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addCurrentSubscription(BuildContext context) async {
|
||||
if (_currentIndex >= _scannedSubscriptions.length) return;
|
||||
|
||||
final subscription = _scannedSubscriptions[_currentIndex];
|
||||
|
||||
try {
|
||||
final provider = Provider.of<SubscriptionProvider>(context, listen: false);
|
||||
final categoryProvider = Provider.of<CategoryProvider>(context, listen: false);
|
||||
|
||||
final finalCategoryId = _selectedCategoryId ?? subscription.category ?? getDefaultCategoryId(categoryProvider);
|
||||
|
||||
// websiteUrl 처리
|
||||
final websiteUrl = websiteUrlController.text.trim().isNotEmpty
|
||||
? websiteUrlController.text.trim()
|
||||
: subscription.websiteUrl;
|
||||
|
||||
print('구독 추가 시도: ${subscription.serviceName}, 카테고리: $finalCategoryId, URL: $websiteUrl');
|
||||
|
||||
// addSubscription 호출
|
||||
await provider.addSubscription(
|
||||
serviceName: subscription.serviceName,
|
||||
monthlyCost: subscription.monthlyCost,
|
||||
billingCycle: subscription.billingCycle,
|
||||
nextBillingDate: subscription.nextBillingDate,
|
||||
websiteUrl: websiteUrl,
|
||||
isAutoDetected: true,
|
||||
repeatCount: subscription.repeatCount,
|
||||
lastPaymentDate: subscription.lastPaymentDate,
|
||||
categoryId: finalCategoryId,
|
||||
currency: subscription.currency,
|
||||
);
|
||||
|
||||
print('구독 추가 성공: ${subscription.serviceName}');
|
||||
|
||||
moveToNextSubscription(context);
|
||||
} catch (e) {
|
||||
print('구독 추가 중 오류 발생: $e');
|
||||
// 오류가 있어도 다음 구독으로 이동
|
||||
moveToNextSubscription(context);
|
||||
}
|
||||
}
|
||||
|
||||
void skipCurrentSubscription(BuildContext context) {
|
||||
final subscription = _scannedSubscriptions[_currentIndex];
|
||||
print('구독 건너뛰기: ${subscription.serviceName}');
|
||||
moveToNextSubscription(context);
|
||||
}
|
||||
|
||||
void moveToNextSubscription(BuildContext context) {
|
||||
_currentIndex++;
|
||||
websiteUrlController.text = ''; // URL 입력 필드 초기화
|
||||
_selectedCategoryId = null; // 카테고리 선택 초기화
|
||||
|
||||
// 모든 구독을 처리했으면 홈 화면으로 이동
|
||||
if (_currentIndex >= _scannedSubscriptions.length) {
|
||||
navigateToHome(context);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void navigateToHome(BuildContext context) {
|
||||
// NavigationProvider를 사용하여 홈 화면으로 이동
|
||||
final navigationProvider = Provider.of<NavigationProvider>(context, listen: false);
|
||||
navigationProvider.updateCurrentIndex(0);
|
||||
}
|
||||
|
||||
void resetState() {
|
||||
_scannedSubscriptions = [];
|
||||
_currentIndex = 0;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
String getDefaultCategoryId(CategoryProvider categoryProvider) {
|
||||
final otherCategory = categoryProvider.categories.firstWhere(
|
||||
(cat) => cat.name == 'other',
|
||||
orElse: () => categoryProvider.categories.first,
|
||||
);
|
||||
print('기본 카테고리 설정: ${otherCategory.name} (ID: ${otherCategory.id})');
|
||||
return otherCategory.id;
|
||||
}
|
||||
|
||||
void initializeWebsiteUrl() {
|
||||
if (_currentIndex < _scannedSubscriptions.length) {
|
||||
final currentSub = _scannedSubscriptions[_currentIndex];
|
||||
if (websiteUrlController.text.isEmpty && currentSub.websiteUrl != null) {
|
||||
websiteUrlController.text = currentSub.websiteUrl!;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user