feat: SMS 스캔 전면광고 및 Isolate 버그 수정
## 전면 광고 (AdService) - AdService 클래스 신규 생성 (lunchpick 패턴 참조) - Completer 패턴으로 광고 완료 대기 구현 - 로딩 오버레이로 앱 foreground 상태 유지 - 몰입형 모드 (immersiveSticky) 적용 - iOS 테스트 광고 ID 설정 ## SMS 스캔 버그 수정 - Isolate 내 Flutter 바인딩 접근 오류 해결 - _isoExtractServiceNameFromSender()에서 하드코딩 사용 - 로딩 위젯 화면 정중앙 배치 수정 ## 문서 및 설정 - CLAUDE.md 최적화 (글로벌 규칙 중복 제거) - Claude Code Skills 5개 추가 - flutter-build: 빌드/분석 - hive-model: Hive 모델 관리 - release-deploy: 릴리즈 배포 - sms-scanner: SMS 스캔 디버깅 - admob: 광고 구현 ## 버전 - 1.0.1+2 → 1.0.1+3
This commit is contained in:
@@ -1,21 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:permission_handler/permission_handler.dart' as permission;
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import '../services/sms_scanner.dart';
|
||||
import '../models/subscription.dart';
|
||||
import '../models/payment_card_suggestion.dart';
|
||||
import '../services/ad_service.dart';
|
||||
import '../services/sms_scan/subscription_converter.dart';
|
||||
import '../services/sms_scan/subscription_filter.dart';
|
||||
import '../services/sms_scan/sms_scan_result.dart';
|
||||
import '../models/subscription.dart';
|
||||
import '../models/payment_card_suggestion.dart';
|
||||
import '../providers/subscription_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:permission_handler/permission_handler.dart' as permission;
|
||||
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';
|
||||
import 'package:google_mobile_ads/google_mobile_ads.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../l10n/app_localizations.dart';
|
||||
import '../utils/logger.dart';
|
||||
|
||||
class SmsScanController extends ChangeNotifier {
|
||||
// 상태 관리
|
||||
@@ -47,10 +48,9 @@ class SmsScanController extends ChangeNotifier {
|
||||
final SmsScanner _smsScanner = SmsScanner();
|
||||
final SubscriptionConverter _converter = SubscriptionConverter();
|
||||
final SubscriptionFilter _filter = SubscriptionFilter();
|
||||
final AdService _adService = AdService();
|
||||
bool _forceServiceNameEditing = false;
|
||||
bool get isServiceNameEditable => _forceServiceNameEditing;
|
||||
bool _isAdInProgress = false;
|
||||
bool get isAdInProgress => _isAdInProgress;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -87,69 +87,26 @@ class SmsScanController extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// SMS 스캔 시작 (전면 광고 표시 후 스캔 진행)
|
||||
Future<void> startScan(BuildContext context) async {
|
||||
if (_isLoading) return;
|
||||
_isAdInProgress = true;
|
||||
notifyListeners();
|
||||
|
||||
// 웹/비지원 플랫폼은 바로 스캔
|
||||
if (kIsWeb || !(Platform.isAndroid || Platform.isIOS)) {
|
||||
_isAdInProgress = false;
|
||||
notifyListeners();
|
||||
await scanSms(context);
|
||||
return;
|
||||
}
|
||||
|
||||
// 전면 광고 로드 및 노출 후 스캔 진행
|
||||
try {
|
||||
await InterstitialAd.load(
|
||||
adUnitId: _interstitialAdUnitId(),
|
||||
request: const AdRequest(),
|
||||
adLoadCallback: InterstitialAdLoadCallback(
|
||||
onAdLoaded: (ad) {
|
||||
ad.fullScreenContentCallback = FullScreenContentCallback(
|
||||
onAdDismissedFullScreenContent: (ad) {
|
||||
ad.dispose();
|
||||
_startSmsScanIfMounted(context);
|
||||
},
|
||||
onAdFailedToShowFullScreenContent: (ad, error) {
|
||||
ad.dispose();
|
||||
_fallbackAfterDelay(context);
|
||||
},
|
||||
);
|
||||
ad.show();
|
||||
},
|
||||
onAdFailedToLoad: (error) {
|
||||
_fallbackAfterDelay(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
Log.e('전면 광고 로드 중 오류, 바로 스캔 진행', e);
|
||||
if (!context.mounted) return;
|
||||
_fallbackAfterDelay(context);
|
||||
}
|
||||
}
|
||||
// 광고 표시 (완료까지 대기)
|
||||
// 광고 실패해도 스캔 진행 (사용자 경험 우선)
|
||||
await _adService.showInterstitialAd(context);
|
||||
|
||||
String _interstitialAdUnitId() {
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
return 'ca-app-pub-6691216385521068~6638409932';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
Future<void> _startSmsScanIfMounted(BuildContext context) async {
|
||||
if (!context.mounted) return;
|
||||
_isAdInProgress = false;
|
||||
notifyListeners();
|
||||
|
||||
// 광고 완료 후 SMS 스캔 실행
|
||||
await scanSms(context);
|
||||
}
|
||||
|
||||
Future<void> _fallbackAfterDelay(BuildContext context) async {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
if (!context.mounted) return;
|
||||
await _startSmsScanIfMounted(context);
|
||||
}
|
||||
|
||||
Future<void> scanSms(BuildContext context) async {
|
||||
_isLoading = true;
|
||||
_errorMessage = null;
|
||||
@@ -157,6 +114,11 @@ class SmsScanController extends ChangeNotifier {
|
||||
_currentIndex = 0;
|
||||
notifyListeners();
|
||||
|
||||
await _performSmsScan(context);
|
||||
}
|
||||
|
||||
/// 실제 SMS 스캔 수행 (로딩 상태는 이미 설정되어 있음)
|
||||
Future<void> _performSmsScan(BuildContext context) async {
|
||||
try {
|
||||
// Android에서 SMS 권한 확인 및 요청
|
||||
final ctx = context;
|
||||
@@ -399,13 +361,14 @@ class SmsScanController extends ChangeNotifier {
|
||||
return otherCategory.id;
|
||||
}
|
||||
|
||||
void initializeWebsiteUrl() {
|
||||
void initializeWebsiteUrl(BuildContext context) {
|
||||
if (_currentIndex < _scannedSubscriptions.length) {
|
||||
final currentSub = _scannedSubscriptions[_currentIndex];
|
||||
if (websiteUrlController.text.isEmpty && currentSub.websiteUrl != null) {
|
||||
websiteUrlController.text = currentSub.websiteUrl!;
|
||||
}
|
||||
if (_shouldEnableServiceNameEditing(currentSub)) {
|
||||
final unknownLabel = _unknownServiceLabel(context);
|
||||
if (_shouldEnableServiceNameEditing(currentSub, unknownLabel)) {
|
||||
if (serviceNameController.text != currentSub.serviceName) {
|
||||
serviceNameController.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user