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:
@@ -1,5 +1,6 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../../../utils/logger.dart';
|
||||
|
||||
/// 서비스 데이터를 관리하는 저장소 클래스
|
||||
class ServiceDataRepository {
|
||||
@@ -15,9 +16,9 @@ class ServiceDataRepository {
|
||||
await rootBundle.loadString('assets/data/subscription_services.json');
|
||||
_servicesData = json.decode(jsonString);
|
||||
_isInitialized = true;
|
||||
print('ServiceDataRepository: JSON 데이터 로드 완료');
|
||||
Log.i('ServiceDataRepository: JSON 데이터 로드 완료');
|
||||
} catch (e) {
|
||||
print('ServiceDataRepository: JSON 로드 실패 - $e');
|
||||
Log.w('ServiceDataRepository: JSON 로드 실패 - $e');
|
||||
// 로드 실패시 기존 하드코딩 데이터 사용
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
@@ -75,24 +75,33 @@ class CategoryMapperService {
|
||||
String getCategoryForLegacyService(String serviceName) {
|
||||
final lowerName = serviceName.toLowerCase();
|
||||
|
||||
if (LegacyServiceData.ottServices.containsKey(lowerName))
|
||||
if (LegacyServiceData.ottServices.containsKey(lowerName)) {
|
||||
return 'ott_services';
|
||||
if (LegacyServiceData.musicServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.musicServices.containsKey(lowerName)) {
|
||||
return 'music_streaming';
|
||||
if (LegacyServiceData.storageServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.storageServices.containsKey(lowerName)) {
|
||||
return 'cloud_storage';
|
||||
if (LegacyServiceData.aiServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.aiServices.containsKey(lowerName)) {
|
||||
return 'ai_services';
|
||||
if (LegacyServiceData.programmingServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.programmingServices.containsKey(lowerName)) {
|
||||
return 'dev_tools';
|
||||
if (LegacyServiceData.officeTools.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.officeTools.containsKey(lowerName)) {
|
||||
return 'office_tools';
|
||||
if (LegacyServiceData.lifestyleServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.lifestyleServices.containsKey(lowerName)) {
|
||||
return 'lifestyle';
|
||||
if (LegacyServiceData.shoppingServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.shoppingServices.containsKey(lowerName)) {
|
||||
return 'shopping';
|
||||
if (LegacyServiceData.telecomServices.containsKey(lowerName))
|
||||
}
|
||||
if (LegacyServiceData.telecomServices.containsKey(lowerName)) {
|
||||
return 'telecom';
|
||||
}
|
||||
|
||||
return 'other';
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import '../models/service_info.dart';
|
||||
import '../data/service_data_repository.dart';
|
||||
import '../data/legacy_service_data.dart';
|
||||
import 'category_mapper_service.dart';
|
||||
import '../../../utils/logger.dart';
|
||||
|
||||
/// URL 매칭 관련 기능을 제공하는 서비스 클래스
|
||||
class UrlMatcherService {
|
||||
@@ -35,7 +36,7 @@ class UrlMatcherService {
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
print('UrlMatcherService: 도메인 추출 실패 - $e');
|
||||
Log.e('UrlMatcherService: 도메인 추출 실패', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -107,7 +108,7 @@ class UrlMatcherService {
|
||||
/// 서비스명으로 URL 찾기
|
||||
String? suggestUrl(String serviceName) {
|
||||
if (serviceName.isEmpty) {
|
||||
print('UrlMatcherService: 빈 serviceName');
|
||||
Log.w('UrlMatcherService: 빈 serviceName');
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -118,7 +119,7 @@ class UrlMatcherService {
|
||||
// 정확한 매칭을 먼저 시도
|
||||
for (final entry in LegacyServiceData.allServices.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: 정확한 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: 정확한 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -126,7 +127,7 @@ class UrlMatcherService {
|
||||
// OTT 서비스 검사
|
||||
for (final entry in LegacyServiceData.ottServices.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: OTT 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: OTT 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -134,7 +135,7 @@ class UrlMatcherService {
|
||||
// 음악 서비스 검사
|
||||
for (final entry in LegacyServiceData.musicServices.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: 음악 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: 음악 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ class UrlMatcherService {
|
||||
// AI 서비스 검사
|
||||
for (final entry in LegacyServiceData.aiServices.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: AI 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: AI 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +151,7 @@ class UrlMatcherService {
|
||||
// 프로그래밍 서비스 검사
|
||||
for (final entry in LegacyServiceData.programmingServices.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: 프로그래밍 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: 프로그래밍 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -158,7 +159,7 @@ class UrlMatcherService {
|
||||
// 오피스 툴 검사
|
||||
for (final entry in LegacyServiceData.officeTools.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: 오피스 툴 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: 오피스 툴 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -166,7 +167,7 @@ class UrlMatcherService {
|
||||
// 기타 서비스 검사
|
||||
for (final entry in LegacyServiceData.otherServices.entries) {
|
||||
if (lowerName.contains(entry.key.toLowerCase())) {
|
||||
print('UrlMatcherService: 기타 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: 기타 서비스 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
@@ -175,15 +176,15 @@ class UrlMatcherService {
|
||||
for (final entry in LegacyServiceData.allServices.entries) {
|
||||
final key = entry.key.toLowerCase();
|
||||
if (key.contains(lowerName) || lowerName.contains(key)) {
|
||||
print('UrlMatcherService: 부분 매칭 - $lowerName -> ${entry.key}');
|
||||
Log.d('UrlMatcherService: 부분 매칭 - $lowerName -> ${entry.key}');
|
||||
return entry.value;
|
||||
}
|
||||
}
|
||||
|
||||
print('UrlMatcherService: 매칭 실패 - $lowerName');
|
||||
Log.d('UrlMatcherService: 매칭 실패 - $lowerName');
|
||||
return null;
|
||||
} catch (e) {
|
||||
print('UrlMatcherService: suggestUrl 에러 - $e');
|
||||
Log.e('UrlMatcherService: suggestUrl 에러', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user