style: apply dart format across project
This commit is contained in:
@@ -336,22 +336,22 @@ class LegacyServiceData {
|
||||
|
||||
// 모든 서비스 매핑을 합친 맵
|
||||
static Map<String, String> get allServices => {
|
||||
...ottServices,
|
||||
...musicServices,
|
||||
...storageServices,
|
||||
...aiServices,
|
||||
...programmingServices,
|
||||
...officeTools,
|
||||
...lifestyleServices,
|
||||
...shoppingServices,
|
||||
...telecomServices,
|
||||
...otherServices,
|
||||
};
|
||||
...ottServices,
|
||||
...musicServices,
|
||||
...storageServices,
|
||||
...aiServices,
|
||||
...programmingServices,
|
||||
...officeTools,
|
||||
...lifestyleServices,
|
||||
...shoppingServices,
|
||||
...telecomServices,
|
||||
...otherServices,
|
||||
};
|
||||
|
||||
/// 서비스 카테고리 찾기
|
||||
static String? getCategoryForService(String serviceName) {
|
||||
final lowerName = serviceName.toLowerCase();
|
||||
|
||||
|
||||
if (ottServices.containsKey(lowerName)) return 'ott';
|
||||
if (musicServices.containsKey(lowerName)) return 'music';
|
||||
if (storageServices.containsKey(lowerName)) return 'storage';
|
||||
@@ -362,7 +362,7 @@ class LegacyServiceData {
|
||||
if (shoppingServices.containsKey(lowerName)) return 'shopping';
|
||||
if (telecomServices.containsKey(lowerName)) return 'telecom';
|
||||
if (otherServices.containsKey(lowerName)) return 'other';
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,13 +5,14 @@ import 'package:flutter/services.dart';
|
||||
class ServiceDataRepository {
|
||||
Map<String, dynamic>? _servicesData;
|
||||
bool _isInitialized = false;
|
||||
|
||||
|
||||
/// JSON 데이터 초기화
|
||||
Future<void> initialize() async {
|
||||
if (_isInitialized) return;
|
||||
|
||||
|
||||
try {
|
||||
final jsonString = await rootBundle.loadString('assets/data/subscription_services.json');
|
||||
final jsonString =
|
||||
await rootBundle.loadString('assets/data/subscription_services.json');
|
||||
_servicesData = json.decode(jsonString);
|
||||
_isInitialized = true;
|
||||
print('ServiceDataRepository: JSON 데이터 로드 완료');
|
||||
@@ -21,10 +22,10 @@ class ServiceDataRepository {
|
||||
_isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 서비스 데이터 가져오기
|
||||
Map<String, dynamic>? getServicesData() => _servicesData;
|
||||
|
||||
|
||||
/// 초기화 여부 확인
|
||||
bool get isInitialized => _isInitialized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ class ServiceInfo {
|
||||
final String categoryId;
|
||||
final String categoryNameKr;
|
||||
final String categoryNameEn;
|
||||
|
||||
|
||||
ServiceInfo({
|
||||
required this.serviceId,
|
||||
required this.serviceName,
|
||||
@@ -17,4 +17,4 @@ class ServiceInfo {
|
||||
required this.categoryNameKr,
|
||||
required this.categoryNameEn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import 'url_matcher_service.dart';
|
||||
class CancellationUrlService {
|
||||
final ServiceDataRepository _dataRepository;
|
||||
final UrlMatcherService _urlMatcher;
|
||||
|
||||
|
||||
CancellationUrlService(this._dataRepository, this._urlMatcher);
|
||||
|
||||
|
||||
/// 서비스명 또는 URL로 해지 안내 페이지 URL 찾기
|
||||
Future<String?> findCancellationUrl({
|
||||
String? serviceName,
|
||||
@@ -19,47 +19,55 @@ class CancellationUrlService {
|
||||
final servicesData = _dataRepository.getServicesData();
|
||||
if (servicesData != null) {
|
||||
final categories = servicesData['categories'] as Map<String, dynamic>;
|
||||
|
||||
|
||||
// 1. 서비스명으로 찾기
|
||||
if (serviceName != null && serviceName.isNotEmpty) {
|
||||
final lowerName = serviceName.toLowerCase().trim();
|
||||
|
||||
|
||||
for (final categoryData in categories.values) {
|
||||
final services = (categoryData as Map<String, dynamic>)['services'] as Map<String, dynamic>;
|
||||
|
||||
final services = (categoryData as Map<String, dynamic>)['services']
|
||||
as Map<String, dynamic>;
|
||||
|
||||
for (final serviceData in services.values) {
|
||||
final names = List<String>.from((serviceData as Map<String, dynamic>)['names'] ?? []);
|
||||
|
||||
final names = List<String>.from(
|
||||
(serviceData as Map<String, dynamic>)['names'] ?? []);
|
||||
|
||||
for (final name in names) {
|
||||
if (lowerName.contains(name.toLowerCase()) || name.toLowerCase().contains(lowerName)) {
|
||||
final cancellationUrls = serviceData['cancellationUrls'] as Map<String, dynamic>?;
|
||||
if (lowerName.contains(name.toLowerCase()) ||
|
||||
name.toLowerCase().contains(lowerName)) {
|
||||
final cancellationUrls =
|
||||
serviceData['cancellationUrls'] as Map<String, dynamic>?;
|
||||
if (cancellationUrls != null) {
|
||||
// 요청한 언어의 URL이 있으면 반환, 없으면 다른 언어 URL 반환
|
||||
return cancellationUrls[locale] ??
|
||||
cancellationUrls[locale == 'kr' ? 'en' : 'kr'];
|
||||
return cancellationUrls[locale] ??
|
||||
cancellationUrls[locale == 'kr' ? 'en' : 'kr'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 2. URL로 찾기
|
||||
if (websiteUrl != null && websiteUrl.isNotEmpty) {
|
||||
final domain = _urlMatcher.extractDomain(websiteUrl);
|
||||
if (domain != null) {
|
||||
for (final categoryData in categories.values) {
|
||||
final services = (categoryData as Map<String, dynamic>)['services'] as Map<String, dynamic>;
|
||||
|
||||
final services = (categoryData as Map<String, dynamic>)['services']
|
||||
as Map<String, dynamic>;
|
||||
|
||||
for (final serviceData in services.values) {
|
||||
final domains = List<String>.from((serviceData as Map<String, dynamic>)['domains'] ?? []);
|
||||
|
||||
final domains = List<String>.from(
|
||||
(serviceData as Map<String, dynamic>)['domains'] ?? []);
|
||||
|
||||
for (final serviceDomain in domains) {
|
||||
if (domain.contains(serviceDomain) || serviceDomain.contains(domain)) {
|
||||
final cancellationUrls = serviceData['cancellationUrls'] as Map<String, dynamic>?;
|
||||
if (domain.contains(serviceDomain) ||
|
||||
serviceDomain.contains(domain)) {
|
||||
final cancellationUrls =
|
||||
serviceData['cancellationUrls'] as Map<String, dynamic>?;
|
||||
if (cancellationUrls != null) {
|
||||
return cancellationUrls[locale] ??
|
||||
cancellationUrls[locale == 'kr' ? 'en' : 'kr'];
|
||||
return cancellationUrls[locale] ??
|
||||
cancellationUrls[locale == 'kr' ? 'en' : 'kr'];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,7 +76,7 @@ class CancellationUrlService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// JSON에서 못 찾았으면 레거시 방식으로 찾기
|
||||
return _findCancellationUrlLegacy(serviceName ?? websiteUrl ?? '');
|
||||
}
|
||||
@@ -126,4 +134,4 @@ class CancellationUrlService {
|
||||
);
|
||||
return cancellationUrl != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,41 +4,43 @@ import '../data/legacy_service_data.dart';
|
||||
/// 카테고리 매핑 관련 기능을 제공하는 서비스 클래스
|
||||
class CategoryMapperService {
|
||||
final ServiceDataRepository _dataRepository;
|
||||
|
||||
|
||||
CategoryMapperService(this._dataRepository);
|
||||
|
||||
|
||||
/// 서비스명으로 카테고리 찾기
|
||||
Future<String?> findCategoryByServiceName(String serviceName) async {
|
||||
if (serviceName.isEmpty) return null;
|
||||
|
||||
|
||||
final lowerName = serviceName.toLowerCase().trim();
|
||||
|
||||
|
||||
// JSON 데이터가 있으면 JSON에서 찾기
|
||||
final servicesData = _dataRepository.getServicesData();
|
||||
if (servicesData != null) {
|
||||
final categories = servicesData['categories'] as Map<String, dynamic>;
|
||||
|
||||
|
||||
for (final categoryEntry in categories.entries) {
|
||||
final categoryId = categoryEntry.key;
|
||||
final categoryData = categoryEntry.value as Map<String, dynamic>;
|
||||
final services = categoryData['services'] as Map<String, dynamic>;
|
||||
|
||||
|
||||
for (final serviceData in services.values) {
|
||||
final names = List<String>.from((serviceData as Map<String, dynamic>)['names'] ?? []);
|
||||
|
||||
final names = List<String>.from(
|
||||
(serviceData as Map<String, dynamic>)['names'] ?? []);
|
||||
|
||||
for (final name in names) {
|
||||
if (lowerName.contains(name.toLowerCase()) || name.toLowerCase().contains(lowerName)) {
|
||||
if (lowerName.contains(name.toLowerCase()) ||
|
||||
name.toLowerCase().contains(lowerName)) {
|
||||
return getCategoryIdByKey(categoryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// JSON에서 못 찾았으면 레거시 방식으로 카테고리 추측
|
||||
return getCategoryForLegacyService(serviceName);
|
||||
}
|
||||
|
||||
|
||||
/// 카테고리 키를 실제 카테고리 ID로 매핑
|
||||
String getCategoryIdByKey(String key) {
|
||||
// 여기에 실제 앱의 카테고리 ID 매핑을 추가
|
||||
@@ -68,21 +70,30 @@ class CategoryMapperService {
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 레거시 서비스명으로 카테고리 추측
|
||||
String getCategoryForLegacyService(String serviceName) {
|
||||
final lowerName = serviceName.toLowerCase();
|
||||
|
||||
if (LegacyServiceData.ottServices.containsKey(lowerName)) return 'ott_services';
|
||||
if (LegacyServiceData.musicServices.containsKey(lowerName)) return 'music_streaming';
|
||||
if (LegacyServiceData.storageServices.containsKey(lowerName)) return 'cloud_storage';
|
||||
if (LegacyServiceData.aiServices.containsKey(lowerName)) return 'ai_services';
|
||||
if (LegacyServiceData.programmingServices.containsKey(lowerName)) return 'dev_tools';
|
||||
if (LegacyServiceData.officeTools.containsKey(lowerName)) return 'office_tools';
|
||||
if (LegacyServiceData.lifestyleServices.containsKey(lowerName)) return 'lifestyle';
|
||||
if (LegacyServiceData.shoppingServices.containsKey(lowerName)) return 'shopping';
|
||||
if (LegacyServiceData.telecomServices.containsKey(lowerName)) return 'telecom';
|
||||
|
||||
|
||||
if (LegacyServiceData.ottServices.containsKey(lowerName))
|
||||
return 'ott_services';
|
||||
if (LegacyServiceData.musicServices.containsKey(lowerName))
|
||||
return 'music_streaming';
|
||||
if (LegacyServiceData.storageServices.containsKey(lowerName))
|
||||
return 'cloud_storage';
|
||||
if (LegacyServiceData.aiServices.containsKey(lowerName))
|
||||
return 'ai_services';
|
||||
if (LegacyServiceData.programmingServices.containsKey(lowerName))
|
||||
return 'dev_tools';
|
||||
if (LegacyServiceData.officeTools.containsKey(lowerName))
|
||||
return 'office_tools';
|
||||
if (LegacyServiceData.lifestyleServices.containsKey(lowerName))
|
||||
return 'lifestyle';
|
||||
if (LegacyServiceData.shoppingServices.containsKey(lowerName))
|
||||
return 'shopping';
|
||||
if (LegacyServiceData.telecomServices.containsKey(lowerName))
|
||||
return 'telecom';
|
||||
|
||||
return 'other';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@ import '../data/service_data_repository.dart';
|
||||
/// 서비스명 관련 기능을 제공하는 서비스 클래스
|
||||
class ServiceNameResolver {
|
||||
final ServiceDataRepository _dataRepository;
|
||||
|
||||
|
||||
ServiceNameResolver(this._dataRepository);
|
||||
|
||||
|
||||
/// 현재 로케일에 따라 서비스 표시명 가져오기
|
||||
Future<String> getServiceDisplayName({
|
||||
required String serviceName,
|
||||
@@ -15,22 +15,23 @@ class ServiceNameResolver {
|
||||
if (servicesData == null) {
|
||||
return serviceName;
|
||||
}
|
||||
|
||||
|
||||
final lowerName = serviceName.toLowerCase().trim();
|
||||
final categories = servicesData['categories'] as Map<String, dynamic>;
|
||||
|
||||
|
||||
// JSON에서 서비스 찾기
|
||||
for (final categoryData in categories.values) {
|
||||
final services = (categoryData as Map<String, dynamic>)['services'] as Map<String, dynamic>;
|
||||
|
||||
final services = (categoryData as Map<String, dynamic>)['services']
|
||||
as Map<String, dynamic>;
|
||||
|
||||
for (final serviceData in services.values) {
|
||||
final data = serviceData as Map<String, dynamic>;
|
||||
final names = List<String>.from(data['names'] ?? []);
|
||||
|
||||
|
||||
// names 배열에 있는지 확인
|
||||
for (final name in names) {
|
||||
if (lowerName == name.toLowerCase() ||
|
||||
lowerName.contains(name.toLowerCase()) ||
|
||||
if (lowerName == name.toLowerCase() ||
|
||||
lowerName.contains(name.toLowerCase()) ||
|
||||
name.toLowerCase().contains(lowerName)) {
|
||||
// 로케일에 따라 적절한 이름 반환
|
||||
if (locale == 'ko' || locale == 'kr') {
|
||||
@@ -40,11 +41,11 @@ class ServiceNameResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// nameKr/nameEn에 직접 매칭 확인
|
||||
final nameKr = (data['nameKr'] ?? '').toString().toLowerCase();
|
||||
final nameEn = (data['nameEn'] ?? '').toString().toLowerCase();
|
||||
|
||||
|
||||
if (lowerName == nameKr || lowerName == nameEn) {
|
||||
if (locale == 'ko' || locale == 'kr') {
|
||||
return data['nameKr'] ?? serviceName;
|
||||
@@ -54,8 +55,8 @@ class ServiceNameResolver {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 찾지 못한 경우 원래 이름 반환
|
||||
return serviceName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import 'category_mapper_service.dart';
|
||||
class SmsExtractorService {
|
||||
final UrlMatcherService _urlMatcher;
|
||||
final CategoryMapperService _categoryMapper;
|
||||
|
||||
|
||||
SmsExtractorService(this._urlMatcher, this._categoryMapper);
|
||||
|
||||
|
||||
/// SMS에서 URL과 서비스 정보 추출
|
||||
Future<ServiceInfo?> extractServiceFromSms(String smsText) async {
|
||||
// URL 패턴 찾기
|
||||
@@ -17,9 +17,9 @@ class SmsExtractorService {
|
||||
r'https?://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)',
|
||||
caseSensitive: false,
|
||||
);
|
||||
|
||||
|
||||
final matches = urlPattern.allMatches(smsText);
|
||||
|
||||
|
||||
for (final match in matches) {
|
||||
final url = match.group(0);
|
||||
if (url != null) {
|
||||
@@ -29,15 +29,17 @@ class SmsExtractorService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// URL로 못 찾았으면 서비스명으로 시도
|
||||
final lowerSms = smsText.toLowerCase();
|
||||
|
||||
|
||||
// 모든 서비스명 검사
|
||||
for (final entry in LegacyServiceData.allServices.entries) {
|
||||
if (lowerSms.contains(entry.key.toLowerCase())) {
|
||||
final categoryId = await _categoryMapper.findCategoryByServiceName(entry.key) ?? 'other';
|
||||
|
||||
final categoryId =
|
||||
await _categoryMapper.findCategoryByServiceName(entry.key) ??
|
||||
'other';
|
||||
|
||||
return ServiceInfo(
|
||||
serviceId: entry.key,
|
||||
serviceName: entry.key,
|
||||
@@ -49,7 +51,7 @@ class SmsExtractorService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,23 +7,23 @@ import 'category_mapper_service.dart';
|
||||
class UrlMatcherService {
|
||||
final ServiceDataRepository _dataRepository;
|
||||
final CategoryMapperService _categoryMapper;
|
||||
|
||||
|
||||
UrlMatcherService(this._dataRepository, this._categoryMapper);
|
||||
|
||||
|
||||
/// 도메인 추출 (www와 TLD 제외)
|
||||
String? extractDomain(String url) {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
final host = uri.host.toLowerCase();
|
||||
|
||||
|
||||
// 도메인 부분 추출
|
||||
var parts = host.split('.');
|
||||
|
||||
|
||||
// www 제거
|
||||
if (parts.isNotEmpty && parts[0] == 'www') {
|
||||
parts = parts.sublist(1);
|
||||
}
|
||||
|
||||
|
||||
// 서브도메인 처리 (예: music.youtube.com)
|
||||
if (parts.length >= 3) {
|
||||
// 서브도메인 포함 전체 도메인 반환
|
||||
@@ -32,40 +32,41 @@ class UrlMatcherService {
|
||||
// 메인 도메인만 반환
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
} catch (e) {
|
||||
print('UrlMatcherService: 도메인 추출 실패 - $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// URL로 서비스 찾기
|
||||
Future<ServiceInfo?> findServiceByUrl(String url) async {
|
||||
final domain = extractDomain(url);
|
||||
if (domain == null) return null;
|
||||
|
||||
|
||||
// JSON 데이터가 있으면 JSON에서 찾기
|
||||
final servicesData = _dataRepository.getServicesData();
|
||||
if (servicesData != null) {
|
||||
final categories = servicesData['categories'] as Map<String, dynamic>;
|
||||
|
||||
|
||||
for (final categoryEntry in categories.entries) {
|
||||
final categoryId = categoryEntry.key;
|
||||
final categoryData = categoryEntry.value as Map<String, dynamic>;
|
||||
final services = categoryData['services'] as Map<String, dynamic>;
|
||||
|
||||
|
||||
for (final serviceEntry in services.entries) {
|
||||
final serviceId = serviceEntry.key;
|
||||
final serviceData = serviceEntry.value as Map<String, dynamic>;
|
||||
final domains = List<String>.from(serviceData['domains'] ?? []);
|
||||
|
||||
|
||||
// 도메인이 일치하는지 확인
|
||||
for (final serviceDomain in domains) {
|
||||
if (domain.contains(serviceDomain) || serviceDomain.contains(domain)) {
|
||||
if (domain.contains(serviceDomain) ||
|
||||
serviceDomain.contains(domain)) {
|
||||
final names = List<String>.from(serviceData['names'] ?? []);
|
||||
final urls = serviceData['urls'] as Map<String, dynamic>?;
|
||||
|
||||
|
||||
return ServiceInfo(
|
||||
serviceId: serviceId,
|
||||
serviceName: names.isNotEmpty ? names[0] : serviceId,
|
||||
@@ -80,13 +81,13 @@ class UrlMatcherService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// JSON에서 못 찾았으면 레거시 방식으로 찾기
|
||||
for (final entry in LegacyServiceData.allServices.entries) {
|
||||
final serviceUrl = entry.value;
|
||||
final serviceDomain = extractDomain(serviceUrl);
|
||||
|
||||
if (serviceDomain != null &&
|
||||
|
||||
if (serviceDomain != null &&
|
||||
(domain.contains(serviceDomain) || serviceDomain.contains(domain))) {
|
||||
return ServiceInfo(
|
||||
serviceId: entry.key,
|
||||
@@ -99,10 +100,10 @@ class UrlMatcherService {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// 서비스명으로 URL 찾기
|
||||
String? suggestUrl(String serviceName) {
|
||||
if (serviceName.isEmpty) {
|
||||
@@ -186,7 +187,7 @@ class UrlMatcherService {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// URL이 알려진 서비스 URL인지 확인
|
||||
Future<bool> isKnownServiceUrl(String url) async {
|
||||
final serviceInfo = await findServiceByUrl(url);
|
||||
@@ -232,4 +233,4 @@ class UrlMatcherService {
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
/// URL Matcher 패키지의 export 파일
|
||||
export 'models/service_info.dart';
|
||||
export 'models/service_info.dart';
|
||||
|
||||
Reference in New Issue
Block a user