feat: add payment card grouping and analysis
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../services/sms_scanner.dart';
|
||||
import '../models/subscription.dart';
|
||||
import '../models/payment_card_suggestion.dart';
|
||||
import '../services/sms_scan/subscription_converter.dart';
|
||||
import '../services/sms_scan/subscription_filter.dart';
|
||||
import '../services/sms_scan/sms_scan_result.dart';
|
||||
import '../providers/subscription_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
@@ -11,6 +13,7 @@ import '../utils/logger.dart';
|
||||
import '../providers/navigation_provider.dart';
|
||||
import '../providers/category_provider.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../providers/payment_card_provider.dart';
|
||||
|
||||
class SmsScanController extends ChangeNotifier {
|
||||
// 상태 관리
|
||||
@@ -22,12 +25,18 @@ class SmsScanController extends ChangeNotifier {
|
||||
|
||||
List<Subscription> _scannedSubscriptions = [];
|
||||
List<Subscription> get scannedSubscriptions => _scannedSubscriptions;
|
||||
PaymentCardSuggestion? _currentSuggestion;
|
||||
PaymentCardSuggestion? get currentSuggestion => _currentSuggestion;
|
||||
bool _shouldSuggestCardCreation = false;
|
||||
bool get shouldSuggestCardCreation => _shouldSuggestCardCreation;
|
||||
|
||||
int _currentIndex = 0;
|
||||
int get currentIndex => _currentIndex;
|
||||
|
||||
String? _selectedCategoryId;
|
||||
String? get selectedCategoryId => _selectedCategoryId;
|
||||
String? _selectedPaymentCardId;
|
||||
String? get selectedPaymentCardId => _selectedPaymentCardId;
|
||||
|
||||
final TextEditingController websiteUrlController = TextEditingController();
|
||||
|
||||
@@ -47,6 +56,14 @@ class SmsScanController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setSelectedPaymentCardId(String? paymentCardId) {
|
||||
_selectedPaymentCardId = paymentCardId;
|
||||
if (paymentCardId != null) {
|
||||
_shouldSuggestCardCreation = false;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void resetWebsiteUrl() {
|
||||
websiteUrlController.text = '';
|
||||
}
|
||||
@@ -88,18 +105,18 @@ class SmsScanController extends ChangeNotifier {
|
||||
|
||||
// SMS 스캔 실행
|
||||
Log.i('SMS 스캔 시작');
|
||||
final scannedSubscriptionModels =
|
||||
final List<SmsScanResult> scanResults =
|
||||
await _smsScanner.scanForSubscriptions();
|
||||
Log.d('스캔된 구독: ${scannedSubscriptionModels.length}개');
|
||||
Log.d('스캔된 구독: ${scanResults.length}개');
|
||||
|
||||
if (scannedSubscriptionModels.isNotEmpty) {
|
||||
if (scanResults.isNotEmpty) {
|
||||
Log.d(
|
||||
'첫 번째 구독: ${scannedSubscriptionModels[0].serviceName}, 반복 횟수: ${scannedSubscriptionModels[0].repeatCount}');
|
||||
'첫 번째 구독: ${scanResults[0].model.serviceName}, 반복 횟수: ${scanResults[0].model.repeatCount}');
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (scannedSubscriptionModels.isEmpty) {
|
||||
if (scanResults.isEmpty) {
|
||||
Log.i('스캔된 구독이 없음');
|
||||
_errorMessage = AppLocalizations.of(context).subscriptionNotFound;
|
||||
_isLoading = false;
|
||||
@@ -109,7 +126,7 @@ class SmsScanController extends ChangeNotifier {
|
||||
|
||||
// SubscriptionModel을 Subscription으로 변환
|
||||
final scannedSubscriptions =
|
||||
_converter.convertModelsToSubscriptions(scannedSubscriptionModels);
|
||||
_converter.convertResultsToSubscriptions(scanResults);
|
||||
|
||||
// 2회 이상 반복 결제된 구독만 필터링
|
||||
final repeatSubscriptions =
|
||||
@@ -155,7 +172,9 @@ class SmsScanController extends ChangeNotifier {
|
||||
|
||||
_scannedSubscriptions = filteredSubscriptions;
|
||||
_isLoading = false;
|
||||
websiteUrlController.text = ''; // URL 입력 필드 초기화
|
||||
websiteUrlController.text = '';
|
||||
_currentSuggestion = null;
|
||||
_prepareCurrentSelection(context);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
Log.e('SMS 스캔 중 오류 발생', e);
|
||||
@@ -202,10 +221,14 @@ class SmsScanController extends ChangeNotifier {
|
||||
Provider.of<SubscriptionProvider>(context, listen: false);
|
||||
final categoryProvider =
|
||||
Provider.of<CategoryProvider>(context, listen: false);
|
||||
final paymentCardProvider =
|
||||
Provider.of<PaymentCardProvider>(context, listen: false);
|
||||
|
||||
final finalCategoryId = _selectedCategoryId ??
|
||||
subscription.category ??
|
||||
getDefaultCategoryId(categoryProvider);
|
||||
final finalPaymentCardId =
|
||||
_selectedPaymentCardId ?? paymentCardProvider.defaultCard?.id;
|
||||
|
||||
// websiteUrl 처리
|
||||
final websiteUrl = websiteUrlController.text.trim().isNotEmpty
|
||||
@@ -226,6 +249,7 @@ class SmsScanController extends ChangeNotifier {
|
||||
repeatCount: subscription.repeatCount,
|
||||
lastPaymentDate: subscription.lastPaymentDate,
|
||||
categoryId: finalCategoryId,
|
||||
paymentCardId: finalPaymentCardId,
|
||||
currency: subscription.currency,
|
||||
);
|
||||
|
||||
@@ -248,8 +272,9 @@ class SmsScanController extends ChangeNotifier {
|
||||
|
||||
void moveToNextSubscription(BuildContext context) {
|
||||
_currentIndex++;
|
||||
websiteUrlController.text = ''; // URL 입력 필드 초기화
|
||||
_selectedCategoryId = null; // 카테고리 선택 초기화
|
||||
websiteUrlController.text = '';
|
||||
_selectedCategoryId = null;
|
||||
_prepareCurrentSelection(context);
|
||||
|
||||
// 모든 구독을 처리했으면 홈 화면으로 이동
|
||||
if (_currentIndex >= _scannedSubscriptions.length) {
|
||||
@@ -270,6 +295,9 @@ class SmsScanController extends ChangeNotifier {
|
||||
_scannedSubscriptions = [];
|
||||
_currentIndex = 0;
|
||||
_errorMessage = null;
|
||||
_selectedPaymentCardId = null;
|
||||
_currentSuggestion = null;
|
||||
_shouldSuggestCardCreation = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@@ -290,4 +318,78 @@ class SmsScanController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String? _getDefaultPaymentCardId(BuildContext context) {
|
||||
try {
|
||||
final provider = Provider.of<PaymentCardProvider>(context, listen: false);
|
||||
return provider.defaultCard?.id;
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _prepareCurrentSelection(BuildContext context) {
|
||||
if (_currentIndex >= _scannedSubscriptions.length) {
|
||||
_selectedPaymentCardId = null;
|
||||
_currentSuggestion = null;
|
||||
return;
|
||||
}
|
||||
|
||||
final current = _scannedSubscriptions[_currentIndex];
|
||||
|
||||
// URL 기본값
|
||||
if (current.websiteUrl != null && current.websiteUrl!.isNotEmpty) {
|
||||
websiteUrlController.text = current.websiteUrl!;
|
||||
} else {
|
||||
websiteUrlController.clear();
|
||||
}
|
||||
|
||||
_currentSuggestion = current.paymentCardSuggestion;
|
||||
|
||||
final matchedCardId = _matchCardWithSuggestion(context, _currentSuggestion);
|
||||
_shouldSuggestCardCreation =
|
||||
_currentSuggestion != null && matchedCardId == null;
|
||||
if (matchedCardId != null) {
|
||||
_selectedPaymentCardId = matchedCardId;
|
||||
return;
|
||||
}
|
||||
|
||||
// 모델에 직접 카드 정보가 존재하면 우선 사용
|
||||
if (current.paymentCardId != null) {
|
||||
_selectedPaymentCardId = current.paymentCardId;
|
||||
return;
|
||||
}
|
||||
|
||||
_selectedPaymentCardId = _getDefaultPaymentCardId(context);
|
||||
}
|
||||
|
||||
String? _matchCardWithSuggestion(
|
||||
BuildContext context, PaymentCardSuggestion? suggestion) {
|
||||
if (suggestion == null) return null;
|
||||
try {
|
||||
final provider = Provider.of<PaymentCardProvider>(context, listen: false);
|
||||
final cards = provider.cards;
|
||||
if (cards.isEmpty) return null;
|
||||
|
||||
if (suggestion.hasLast4) {
|
||||
for (final card in cards) {
|
||||
if (card.last4 == suggestion.last4) {
|
||||
return card.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final normalizedIssuer = suggestion.issuerName.toLowerCase();
|
||||
for (final card in cards) {
|
||||
final issuer = card.issuerName.toLowerCase();
|
||||
if (issuer.contains(normalizedIssuer) ||
|
||||
normalizedIssuer.contains(issuer)) {
|
||||
return card.id;
|
||||
}
|
||||
}
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user