feat(settings): SMS 읽기 권한 상태/요청 위젯 추가 (Android)
- 설정 화면에 SMS 권한 카드 추가: 상태 표시(허용/미허용/영구 거부), 권한 요청/설정 이동 지원\n- 기존 알림 권한 카드 스타일과 일관성 유지 feat(permissions): 최초 실행 시 SMS 권한 온보딩 화면 추가 및 Splash에서 라우팅 (Android) - 권한 필요 이유/수집 범위 현지화 문구 추가\n- 거부/영구거부 케이스 처리 및 설정 이동 chore(codex): AGENTS.md/체크 스크립트/CI/프롬프트 템플릿 추가 - AGENTS.md, scripts/check.sh, scripts/fix.sh, .github/workflows/flutter_ci.yml, .claude/agents/codex.md, 문서 템플릿 추가 refactor(logging): 경로별 print 제거 후 경량 로거(Log) 도입 - SMS 스캐너/컨트롤러, URL 매처, 데이터 리포지토리, 내비게이션, 메모리/성능 유틸 등 핵심 경로 치환 feat(exchange): 환율 API URL을 --dart-define로 오버라이드 가능 + 폴백 로깅 강화 test: URL 매처/환율 스모크 테스트 추가 chore(android): RECEIVE_SMS 권한 제거 (READ_SMS만 유지) fix(lints): dart fix + 수동 정리로 경고 대폭 감소, 비동기 context(mounted) 보강 fix(deprecations):\n- flutter_local_notifications의 androidAllowWhileIdle → androidScheduleMode 전환\n- WillPopScope → PopScope 교체 i18n: SMS 권한 온보딩/설정 문구 현지화 키 추가
This commit is contained in:
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart' show kIsWeb, kDebugMode;
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../models/subscription_model.dart';
|
||||
import '../providers/subscription_provider.dart';
|
||||
import '../providers/category_provider.dart';
|
||||
import '../services/sms_service.dart';
|
||||
@@ -183,6 +182,7 @@ class AddSubscriptionController {
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
// ignore: avoid_print
|
||||
print('AddSubscriptionController: URL 자동 매칭 중 오류 - $e');
|
||||
}
|
||||
}
|
||||
@@ -320,6 +320,7 @@ class AddSubscriptionController {
|
||||
await SubscriptionUrlMatcher.extractServiceFromSms(smsContent);
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
// ignore: avoid_print
|
||||
print('AddSubscriptionController: SMS 서비스 추출 실패 - $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,7 +401,7 @@ class DetailScreenController extends ChangeNotifier {
|
||||
|
||||
debugPrint('[DetailScreenController] 구독 업데이트 시작: '
|
||||
'${subscription.serviceName} → ${serviceNameController.text}, '
|
||||
'금액: ${subscription.monthlyCost} → $monthlyCost ${_currency}');
|
||||
'금액: $subscription.monthlyCost → $monthlyCost $_currency');
|
||||
|
||||
subscription.serviceName = serviceNameController.text;
|
||||
subscription.monthlyCost = monthlyCost;
|
||||
@@ -460,12 +460,14 @@ class DetailScreenController extends ChangeNotifier {
|
||||
serviceName: subscription.serviceName,
|
||||
locale: locale,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
|
||||
// 삭제 확인 다이얼로그 표시
|
||||
final shouldDelete = await DeleteConfirmationDialog.show(
|
||||
context: context,
|
||||
serviceName: displayName,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (!shouldDelete) return;
|
||||
|
||||
@@ -529,6 +531,7 @@ class DetailScreenController extends ChangeNotifier {
|
||||
}
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
// ignore: avoid_print
|
||||
print('DetailScreenController: 해지 페이지 열기 실패 - $e');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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 '../utils/logger.dart';
|
||||
import '../providers/navigation_provider.dart';
|
||||
import '../providers/category_provider.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
@@ -58,20 +58,20 @@ class SmsScanController extends ChangeNotifier {
|
||||
|
||||
try {
|
||||
// SMS 스캔 실행
|
||||
print('SMS 스캔 시작');
|
||||
Log.i('SMS 스캔 시작');
|
||||
final scannedSubscriptionModels =
|
||||
await _smsScanner.scanForSubscriptions();
|
||||
print('스캔된 구독: ${scannedSubscriptionModels.length}개');
|
||||
Log.d('스캔된 구독: ${scannedSubscriptionModels.length}개');
|
||||
|
||||
if (scannedSubscriptionModels.isNotEmpty) {
|
||||
print(
|
||||
Log.d(
|
||||
'첫 번째 구독: ${scannedSubscriptionModels[0].serviceName}, 반복 횟수: ${scannedSubscriptionModels[0].repeatCount}');
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
if (scannedSubscriptionModels.isEmpty) {
|
||||
print('스캔된 구독이 없음');
|
||||
Log.i('스캔된 구독이 없음');
|
||||
_errorMessage = AppLocalizations.of(context).subscriptionNotFound;
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
@@ -85,15 +85,15 @@ class SmsScanController extends ChangeNotifier {
|
||||
// 2회 이상 반복 결제된 구독만 필터링
|
||||
final repeatSubscriptions =
|
||||
_filter.filterByRepeatCount(scannedSubscriptions, 2);
|
||||
print('반복 결제된 구독: ${repeatSubscriptions.length}개');
|
||||
Log.d('반복 결제된 구독: ${repeatSubscriptions.length}개');
|
||||
|
||||
if (repeatSubscriptions.isNotEmpty) {
|
||||
print(
|
||||
Log.d(
|
||||
'첫 번째 반복 구독: ${repeatSubscriptions[0].serviceName}, 반복 횟수: ${repeatSubscriptions[0].repeatCount}');
|
||||
}
|
||||
|
||||
if (repeatSubscriptions.isEmpty) {
|
||||
print('반복 결제된 구독이 없음');
|
||||
Log.i('반복 결제된 구독이 없음');
|
||||
_errorMessage = AppLocalizations.of(context).repeatSubscriptionNotFound;
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
@@ -104,21 +104,21 @@ class SmsScanController extends ChangeNotifier {
|
||||
final provider =
|
||||
Provider.of<SubscriptionProvider>(context, listen: false);
|
||||
final existingSubscriptions = provider.subscriptions;
|
||||
print('기존 구독: ${existingSubscriptions.length}개');
|
||||
Log.d('기존 구독: ${existingSubscriptions.length}개');
|
||||
|
||||
// 중복 구독 필터링
|
||||
final filteredSubscriptions =
|
||||
_filter.filterDuplicates(repeatSubscriptions, existingSubscriptions);
|
||||
print('중복 제거 후 구독: ${filteredSubscriptions.length}개');
|
||||
Log.d('중복 제거 후 구독: ${filteredSubscriptions.length}개');
|
||||
|
||||
if (filteredSubscriptions.isNotEmpty) {
|
||||
print(
|
||||
Log.d(
|
||||
'첫 번째 필터링된 구독: ${filteredSubscriptions[0].serviceName}, 반복 횟수: ${filteredSubscriptions[0].repeatCount}');
|
||||
}
|
||||
|
||||
// 중복 제거 후 신규 구독이 없는 경우
|
||||
if (filteredSubscriptions.isEmpty) {
|
||||
print('중복 제거 후 신규 구독이 없음');
|
||||
Log.i('중복 제거 후 신규 구독이 없음');
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
return;
|
||||
@@ -129,7 +129,7 @@ class SmsScanController extends ChangeNotifier {
|
||||
websiteUrlController.text = ''; // URL 입력 필드 초기화
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
print('SMS 스캔 중 오류 발생: $e');
|
||||
Log.e('SMS 스캔 중 오류 발생', e);
|
||||
if (context.mounted) {
|
||||
_errorMessage =
|
||||
AppLocalizations.of(context).smsScanErrorWithMessage(e.toString());
|
||||
@@ -159,7 +159,7 @@ class SmsScanController extends ChangeNotifier {
|
||||
? websiteUrlController.text.trim()
|
||||
: subscription.websiteUrl;
|
||||
|
||||
print(
|
||||
Log.d(
|
||||
'구독 추가 시도: ${subscription.serviceName}, 카테고리: $finalCategoryId, URL: $websiteUrl');
|
||||
|
||||
// addSubscription 호출
|
||||
@@ -176,19 +176,20 @@ class SmsScanController extends ChangeNotifier {
|
||||
currency: subscription.currency,
|
||||
);
|
||||
|
||||
print('구독 추가 성공: ${subscription.serviceName}');
|
||||
|
||||
Log.i('구독 추가 성공: ${subscription.serviceName}');
|
||||
if (!context.mounted) return;
|
||||
moveToNextSubscription(context);
|
||||
} catch (e) {
|
||||
print('구독 추가 중 오류 발생: $e');
|
||||
Log.e('구독 추가 중 오류 발생', e);
|
||||
// 오류가 있어도 다음 구독으로 이동
|
||||
if (!context.mounted) return;
|
||||
moveToNextSubscription(context);
|
||||
}
|
||||
}
|
||||
|
||||
void skipCurrentSubscription(BuildContext context) {
|
||||
final subscription = _scannedSubscriptions[_currentIndex];
|
||||
print('구독 건너뛰기: ${subscription.serviceName}');
|
||||
Log.i('구독 건너뛰기: ${subscription.serviceName}');
|
||||
moveToNextSubscription(context);
|
||||
}
|
||||
|
||||
@@ -224,7 +225,7 @@ class SmsScanController extends ChangeNotifier {
|
||||
(cat) => cat.name == 'other',
|
||||
orElse: () => categoryProvider.categories.first,
|
||||
);
|
||||
print('기본 카테고리 설정: ${otherCategory.name} (ID: ${otherCategory.id})');
|
||||
Log.d('기본 카테고리 설정: ${otherCategory.name} (ID: ${otherCategory.id})');
|
||||
return otherCategory.id;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user