import 'package:flutter/services.dart'; import '../models/subscription_model.dart'; import '../temp/test_sms_data.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import '../services/subscription_url_matcher.dart'; class SmsScanner { Future> scanForSubscriptions() async { try { List smsList; print('SmsScanner: 스캔 시작'); // 디버그 모드에서는 테스트 데이터 사용 if (kDebugMode) { print('SmsScanner: 디버그 모드에서 테스트 데이터 사용'); smsList = TestSmsData.getTestData(); print('SmsScanner: 테스트 데이터 개수: ${smsList.length}'); } else { print('SmsScanner: 실제 SMS 데이터 스캔'); // 실제 환경에서는 네이티브 코드 호출 const platform = MethodChannel('com.submanager/sms'); try { smsList = await platform.invokeMethod('scanSubscriptions'); print('SmsScanner: 네이티브 호출 성공, SMS 데이터 개수: ${smsList.length}'); } catch (e) { print('SmsScanner: 네이티브 호출 실패: $e'); // 오류 발생 시 빈 목록 반환 return []; } } // SMS 데이터를 분석하여 반복 결제되는 구독 식별 final List subscriptions = []; final Map>> serviceGroups = {}; // 서비스명별로 SMS 메시지 그룹화 for (final sms in smsList) { final serviceName = sms['serviceName'] as String? ?? '알 수 없는 서비스'; if (!serviceGroups.containsKey(serviceName)) { serviceGroups[serviceName] = []; } serviceGroups[serviceName]!.add(sms); } print('SmsScanner: 그룹화된 서비스 수: ${serviceGroups.length}'); // 그룹화된 데이터로 구독 분석 for (final entry in serviceGroups.entries) { print('SmsScanner: 서비스 "${entry.key}" - 메시지 개수: ${entry.value.length}'); // 2회 이상 반복된 서비스만 구독으로 간주 if (entry.value.length >= 2) { final serviceSms = entry.value[0]; // 가장 최근 SMS 사용 final subscription = _parseSms(serviceSms, entry.value.length); if (subscription != null) { print( 'SmsScanner: 구독 추가: ${subscription.serviceName}, 반복 횟수: ${subscription.repeatCount}'); subscriptions.add(subscription); } else { print('SmsScanner: 구독 파싱 실패: ${entry.key}'); } } else { print('SmsScanner: 반복 횟수 부족, 구독으로 간주하지 않음: ${entry.key}'); } } print('SmsScanner: 최종 구독 개수: ${subscriptions.length}'); return subscriptions; } catch (e) { print('SmsScanner: 예외 발생: $e'); throw Exception('SMS 스캔 중 오류 발생: $e'); } } SubscriptionModel? _parseSms(Map sms, int repeatCount) { try { final serviceName = sms['serviceName'] as String? ?? '알 수 없는 서비스'; final monthlyCost = (sms['monthlyCost'] as num?)?.toDouble() ?? 0.0; final billingCycle = sms['billingCycle'] as String? ?? '월간'; final nextBillingDateStr = sms['nextBillingDate'] as String?; // 실제 반복 횟수 사용 (테스트 데이터에서는 이미 제공됨) final actualRepeatCount = repeatCount > 0 ? repeatCount : 1; final message = sms['message'] as String? ?? ''; // 통화 단위 감지 - 메시지 내용과 서비스명 모두 검사 String currency = _detectCurrency(message); // 서비스명에 따라 통화 단위 재확인 final dollarServices = [ 'GitHub', 'GitHub Pro', 'Netflix US', 'Spotify', 'Spotify Premium' ]; if (dollarServices.any((service) => serviceName.contains(service))) { print('서비스명 $serviceName으로 USD 통화 단위 확정'); currency = 'USD'; } DateTime? nextBillingDate; if (nextBillingDateStr != null) { nextBillingDate = DateTime.tryParse(nextBillingDateStr); } DateTime? lastPaymentDate; final previousPaymentDateStr = sms['previousPaymentDate'] as String?; if (previousPaymentDateStr != null && previousPaymentDateStr.isNotEmpty) { lastPaymentDate = DateTime.tryParse(previousPaymentDateStr); } // 결제일 계산 로직 추가 - 미래 날짜가 아니면 조정 DateTime adjustedNextBillingDate = _calculateNextBillingDate( nextBillingDate ?? DateTime.now().add(const Duration(days: 30)), billingCycle); return SubscriptionModel( id: DateTime.now().millisecondsSinceEpoch.toString(), serviceName: serviceName, monthlyCost: monthlyCost, billingCycle: billingCycle, nextBillingDate: adjustedNextBillingDate, isAutoDetected: true, repeatCount: actualRepeatCount, lastPaymentDate: lastPaymentDate, websiteUrl: _extractWebsiteUrl(serviceName), currency: currency, // 통화 단위 설정 ); } catch (e) { return null; } } // 다음 결제일 계산 (현재 날짜 기준으로 조정) DateTime _calculateNextBillingDate( DateTime billingDate, String billingCycle) { final now = DateTime.now(); // 결제일이 이미 미래인 경우 그대로 반환 if (billingDate.isAfter(now)) { return billingDate; } // 결제 주기별 다음 결제일 계산 if (billingCycle == '월간') { int month = now.month; int year = now.year; // 현재 달의 결제일이 이미 지났으면 다음 달로 이동 if (now.day >= billingDate.day) { month = month + 1; if (month > 12) { month = 1; year = year + 1; } } return DateTime(year, month, billingDate.day); } else if (billingCycle == '연간') { // 올해의 결제일이 지났는지 확인 final thisYearBilling = DateTime(now.year, billingDate.month, billingDate.day); if (thisYearBilling.isBefore(now)) { return DateTime(now.year + 1, billingDate.month, billingDate.day); } else { return thisYearBilling; } } else if (billingCycle == '주간') { // 가장 가까운 다음 주 같은 요일 계산 final dayDifference = billingDate.weekday - now.weekday; final daysToAdd = dayDifference > 0 ? dayDifference : 7 + dayDifference; return now.add(Duration(days: daysToAdd)); } // 기본 처리: 30일 후 return now.add(const Duration(days: 30)); } String? _extractWebsiteUrl(String serviceName) { // SubscriptionUrlMatcher 서비스를 사용하여 URL 매칭 시도 final suggestedUrl = SubscriptionUrlMatcher.findMatchingUrl(serviceName); // 매칭된 URL이 있으면 반환, 없으면 기존 매핑 사용 if (suggestedUrl != null && suggestedUrl.isNotEmpty) { return suggestedUrl; } // 기존 하드코딩된 매핑 (필요한 경우 폴백으로 사용) final Map serviceUrls = { '넷플릭스': 'https://www.netflix.com', '디즈니플러스': 'https://www.disneyplus.com', '유튜브프리미엄': 'https://www.youtube.com', 'YouTube Premium': 'https://www.youtube.com', '애플 iCloud': 'https://www.icloud.com', 'Microsoft 365': 'https://www.microsoft.com/microsoft-365', '멜론': 'https://www.melon.com', '웨이브': 'https://www.wavve.com', 'Apple Music': 'https://www.apple.com/apple-music', 'Netflix': 'https://www.netflix.com', 'Disney+': 'https://www.disneyplus.com', 'Spotify': 'https://www.spotify.com', }; return serviceUrls[serviceName]; } // 메시지에서 통화 단위를 감지하는 함수 String _detectCurrency(String message) { final dollarKeywords = [ '\$', 'USD', 'dollar', '달러', 'dollars', 'US\$', // 해외 서비스 이름 'Netflix US', 'Spotify Premium', 'Apple US', 'Amazon US', 'GitHub' ]; // 특정 서비스명으로 통화 단위 결정 final Map serviceCurrencyMap = { 'Netflix US': 'USD', 'Spotify Premium': 'USD', 'Spotify': 'USD', 'GitHub': 'USD', 'GitHub Pro': 'USD', }; // 서비스명 기반 통화 단위 확인 for (final service in serviceCurrencyMap.keys) { if (message.contains(service)) { print('_detectCurrency: ${service}는 USD 서비스로 판별됨'); return 'USD'; } } // 메시지에 달러 관련 키워드가 있는지 확인 for (final keyword in dollarKeywords) { if (message.toLowerCase().contains(keyword.toLowerCase())) { print('_detectCurrency: USD 키워드 발견: $keyword'); return 'USD'; } } // 기본값은 원화 return 'KRW'; } }