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,6 +1,7 @@
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:convert';
|
||||
import 'package:intl/intl.dart';
|
||||
import '../utils/logger.dart';
|
||||
|
||||
/// 환율 정보 서비스 클래스
|
||||
class ExchangeRateService {
|
||||
@@ -21,12 +22,20 @@ class ExchangeRateService {
|
||||
double? _usdToCnyRate;
|
||||
DateTime? _lastUpdated;
|
||||
|
||||
// API 요청 URL (ExchangeRate-API 사용)
|
||||
final String _apiUrl = 'https://api.exchangerate-api.com/v4/latest/USD';
|
||||
// API 요청 URL (ExchangeRate-API 등) - 빌드 타임 오버라이드 가능
|
||||
static const String _defaultApiUrl =
|
||||
'https://api.exchangerate-api.com/v4/latest/USD';
|
||||
final String _apiUrl = const String.fromEnvironment(
|
||||
'EXCHANGE_RATE_API_URL',
|
||||
defaultValue: _defaultApiUrl,
|
||||
);
|
||||
|
||||
// 기본 환율 상수
|
||||
// ignore: constant_identifier_names
|
||||
static const double DEFAULT_USD_TO_KRW_RATE = 1350.0;
|
||||
// ignore: constant_identifier_names
|
||||
static const double DEFAULT_USD_TO_JPY_RATE = 150.0;
|
||||
// ignore: constant_identifier_names
|
||||
static const double DEFAULT_USD_TO_CNY_RATE = 7.2;
|
||||
|
||||
// 캐싱된 환율 반환 (동기적)
|
||||
@@ -44,18 +53,26 @@ class ExchangeRateService {
|
||||
}
|
||||
|
||||
try {
|
||||
// API 요청
|
||||
// API 요청 (네트워크 불가 환경에서는 예외 발생 가능)
|
||||
final response = await http.get(Uri.parse(_apiUrl));
|
||||
|
||||
if (response.statusCode == 200) {
|
||||
final data = json.decode(response.body);
|
||||
_usdToKrwRate = data['rates']['KRW']?.toDouble();
|
||||
_usdToJpyRate = data['rates']['JPY']?.toDouble();
|
||||
_usdToCnyRate = data['rates']['CNY']?.toDouble();
|
||||
_usdToKrwRate = (data['rates']['KRW'] as num?)?.toDouble();
|
||||
_usdToJpyRate = (data['rates']['JPY'] as num?)?.toDouble();
|
||||
_usdToCnyRate = (data['rates']['CNY'] as num?)?.toDouble();
|
||||
_lastUpdated = DateTime.now();
|
||||
Log.d(
|
||||
'환율 갱신 완료: USD→KRW=$_usdToKrwRate, JPY=$_usdToJpyRate, CNY=$_usdToCnyRate');
|
||||
return;
|
||||
} else {
|
||||
Log.w(
|
||||
'환율 API 응답 코드: ${response.statusCode} (${response.reasonPhrase})');
|
||||
}
|
||||
} catch (e) {
|
||||
// 오류 발생 시 기본값 사용
|
||||
} catch (e, st) {
|
||||
// 네트워크 실패 시 캐시/기본값 폴백
|
||||
Log.w('환율 API 요청 실패. 캐시/기본값 사용');
|
||||
Log.e('환율 API 에러', e, st);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
import 'package:timezone/data/latest_all.dart' as tz;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'dart:io' show Platform;
|
||||
import '../models/subscription_model.dart';
|
||||
@@ -10,7 +9,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
class NotificationService {
|
||||
static final FlutterLocalNotificationsPlugin _notifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
static final _secureStorage = const FlutterSecureStorage();
|
||||
static const _secureStorage = FlutterSecureStorage();
|
||||
|
||||
static const _notificationEnabledKey = 'notification_enabled';
|
||||
static const _paymentNotificationEnabledKey = 'payment_notification_enabled';
|
||||
@@ -241,7 +240,7 @@ class NotificationService {
|
||||
priority: Priority.high,
|
||||
);
|
||||
|
||||
final iosDetails = const DarwinNotificationDetails();
|
||||
const iosDetails = DarwinNotificationDetails();
|
||||
|
||||
// tz.local 초기화 확인 및 재시도
|
||||
tz.Location location;
|
||||
@@ -266,10 +265,10 @@ class NotificationService {
|
||||
title,
|
||||
body,
|
||||
tz.TZDateTime.from(scheduledDate, location),
|
||||
NotificationDetails(android: androidDetails, iOS: iosDetails),
|
||||
androidAllowWhileIdle: true,
|
||||
const NotificationDetails(android: androidDetails, iOS: iosDetails),
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('알림 예약 중 오류 발생: $e');
|
||||
@@ -351,9 +350,9 @@ class NotificationService {
|
||||
'${subscription.serviceName} 구독이 ${subscription.nextBillingDate.day}일 만료됩니다.',
|
||||
tz.TZDateTime.from(subscription.nextBillingDate, location),
|
||||
notificationDetails,
|
||||
androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('구독 알림 예약 중 오류 발생: $e');
|
||||
@@ -416,9 +415,9 @@ class NotificationService {
|
||||
priority: Priority.high,
|
||||
),
|
||||
),
|
||||
androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('결제 알림 예약 중 오류 발생: $e');
|
||||
@@ -456,7 +455,7 @@ class NotificationService {
|
||||
}
|
||||
|
||||
await _notifications.zonedSchedule(
|
||||
(subscription.id + '_expiration').hashCode,
|
||||
('${subscription.id}_expiration').hashCode,
|
||||
'구독 만료 예정 알림',
|
||||
'${subscription.serviceName} 구독이 7일 후 만료됩니다.',
|
||||
tz.TZDateTime.from(reminderDate, location),
|
||||
@@ -469,9 +468,9 @@ class NotificationService {
|
||||
priority: Priority.high,
|
||||
),
|
||||
),
|
||||
androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('만료 알림 예약 중 오류 발생: $e');
|
||||
|
||||
@@ -12,9 +12,12 @@ class SubscriptionConverter {
|
||||
final subscription = _convertSingle(model);
|
||||
result.add(subscription);
|
||||
|
||||
// 개발 편의를 위한 디버그 로그
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'모델 변환 성공: ${model.serviceName}, 카테고리ID: ${model.categoryId}, URL: ${model.websiteUrl}, 통화: ${model.currency}');
|
||||
} catch (e) {
|
||||
// ignore: avoid_print
|
||||
print('모델 변환 중 오류 발생: $e');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import '../../models/subscription.dart';
|
||||
import '../../models/subscription_model.dart';
|
||||
import '../../utils/logger.dart';
|
||||
|
||||
class SubscriptionFilter {
|
||||
// 중복 구독 필터링 (서비스명과 금액이 같으면 중복으로 간주)
|
||||
List<Subscription> filterDuplicates(
|
||||
List<Subscription> scanned, List<SubscriptionModel> existing) {
|
||||
print(
|
||||
Log.d(
|
||||
'_filterDuplicates: 스캔된 구독 ${scanned.length}개, 기존 구독 ${existing.length}개');
|
||||
|
||||
// 중복되지 않은 구독만 필터링
|
||||
@@ -17,7 +18,7 @@ class SubscriptionFilter {
|
||||
final isSameCost = existingSub.monthlyCost == scannedSub.monthlyCost;
|
||||
|
||||
if (isSameName && isSameCost) {
|
||||
print(
|
||||
Log.d(
|
||||
'중복 발견: ${scannedSub.serviceName} (${scannedSub.monthlyCost}원)');
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||
import 'package:flutter_sms_inbox/flutter_sms_inbox.dart';
|
||||
import '../models/subscription_model.dart';
|
||||
import '../utils/logger.dart';
|
||||
import '../temp/test_sms_data.dart';
|
||||
import '../services/subscription_url_matcher.dart';
|
||||
import '../utils/platform_helper.dart';
|
||||
@@ -11,26 +12,26 @@ class SmsScanner {
|
||||
Future<List<SubscriptionModel>> scanForSubscriptions() async {
|
||||
try {
|
||||
List<dynamic> smsList;
|
||||
print('SmsScanner: 스캔 시작');
|
||||
Log.d('SmsScanner: 스캔 시작');
|
||||
|
||||
// 플랫폼별 분기 처리
|
||||
if (kIsWeb) {
|
||||
// 웹 환경: 테스트 데이터 사용
|
||||
print('SmsScanner: 웹 환경에서 테스트 데이터 사용');
|
||||
Log.i('SmsScanner: 웹 환경에서 테스트 데이터 사용');
|
||||
smsList = TestSmsData.getTestData();
|
||||
print('SmsScanner: 테스트 데이터 개수: ${smsList.length}');
|
||||
Log.d('SmsScanner: 테스트 데이터 개수: ${smsList.length}');
|
||||
} else if (PlatformHelper.isIOS) {
|
||||
// iOS: SMS 접근 불가, 빈 리스트 반환
|
||||
print('SmsScanner: iOS에서는 SMS 스캔 불가');
|
||||
Log.w('SmsScanner: iOS에서는 SMS 스캔 불가');
|
||||
return [];
|
||||
} else if (PlatformHelper.isAndroid) {
|
||||
// Android: flutter_sms_inbox 사용
|
||||
print('SmsScanner: Android에서 실제 SMS 스캔');
|
||||
Log.i('SmsScanner: Android에서 실제 SMS 스캔');
|
||||
smsList = await _scanAndroidSms();
|
||||
print('SmsScanner: 스캔된 SMS 개수: ${smsList.length}');
|
||||
Log.d('SmsScanner: 스캔된 SMS 개수: ${smsList.length}');
|
||||
} else {
|
||||
// 기타 플랫폼
|
||||
print('SmsScanner: 지원하지 않는 플랫폼');
|
||||
Log.w('SmsScanner: 지원하지 않는 플랫폼');
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -47,32 +48,32 @@ class SmsScanner {
|
||||
serviceGroups[serviceName]!.add(sms);
|
||||
}
|
||||
|
||||
print('SmsScanner: 그룹화된 서비스 수: ${serviceGroups.length}');
|
||||
Log.d('SmsScanner: 그룹화된 서비스 수: ${serviceGroups.length}');
|
||||
|
||||
// 그룹화된 데이터로 구독 분석
|
||||
for (final entry in serviceGroups.entries) {
|
||||
print('SmsScanner: 서비스 "${entry.key}" - 메시지 개수: ${entry.value.length}');
|
||||
Log.d('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(
|
||||
Log.i(
|
||||
'SmsScanner: 구독 추가: ${subscription.serviceName}, 반복 횟수: ${subscription.repeatCount}');
|
||||
subscriptions.add(subscription);
|
||||
} else {
|
||||
print('SmsScanner: 구독 파싱 실패: ${entry.key}');
|
||||
Log.w('SmsScanner: 구독 파싱 실패: ${entry.key}');
|
||||
}
|
||||
} else {
|
||||
print('SmsScanner: 반복 횟수 부족, 구독으로 간주하지 않음: ${entry.key}');
|
||||
Log.d('SmsScanner: 반복 횟수 부족, 구독으로 간주하지 않음: ${entry.key}');
|
||||
}
|
||||
}
|
||||
|
||||
print('SmsScanner: 최종 구독 개수: ${subscriptions.length}');
|
||||
Log.d('SmsScanner: 최종 구독 개수: ${subscriptions.length}');
|
||||
return subscriptions;
|
||||
} catch (e) {
|
||||
print('SmsScanner: 예외 발생: $e');
|
||||
Log.e('SmsScanner: 예외 발생', e);
|
||||
throw Exception('SMS 스캔 중 오류 발생: $e');
|
||||
}
|
||||
}
|
||||
@@ -93,7 +94,7 @@ class SmsScanner {
|
||||
|
||||
return smsList;
|
||||
} catch (e) {
|
||||
print('SmsScanner: Android SMS 스캔 실패: $e');
|
||||
Log.e('SmsScanner: Android SMS 스캔 실패', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -160,7 +161,7 @@ class SmsScanner {
|
||||
'previousPaymentDate': date.toIso8601String(),
|
||||
};
|
||||
} catch (e) {
|
||||
print('SmsScanner: SMS 파싱 실패: $e');
|
||||
Log.e('SmsScanner: SMS 파싱 실패', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -281,7 +282,7 @@ class SmsScanner {
|
||||
'Spotify Premium'
|
||||
];
|
||||
if (dollarServices.any((service) => serviceName.contains(service))) {
|
||||
print('서비스명 $serviceName으로 USD 통화 단위 확정');
|
||||
Log.d('서비스명 $serviceName으로 USD 통화 단위 확정');
|
||||
currency = 'USD';
|
||||
}
|
||||
|
||||
@@ -411,7 +412,7 @@ class SmsScanner {
|
||||
// 서비스명 기반 통화 단위 확인
|
||||
for (final service in serviceCurrencyMap.keys) {
|
||||
if (message.contains(service)) {
|
||||
print('_detectCurrency: ${service}는 USD 서비스로 판별됨');
|
||||
Log.d('_detectCurrency: $service는 USD 서비스로 판별됨');
|
||||
return 'USD';
|
||||
}
|
||||
}
|
||||
@@ -419,7 +420,7 @@ class SmsScanner {
|
||||
// 메시지에 달러 관련 키워드가 있는지 확인
|
||||
for (final keyword in dollarKeywords) {
|
||||
if (message.toLowerCase().contains(keyword.toLowerCase())) {
|
||||
print('_detectCurrency: USD 키워드 발견: $keyword');
|
||||
Log.d('_detectCurrency: USD 키워드 발견: $keyword');
|
||||
return 'USD';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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