# SubManager - 향상된 제품 요구사항 정의서(PRD)

## 1. 제품 개요

### 1.1 앱 소개
**SubManager**는 사용자의 정기 구독 서비스를 효율적으로 관리하고, 지출을 추적하며 절약을 돕기 위한 모바일 앱입니다. 사용자들이 자신의 구독 서비스를 손쉽게 추가하고, 결제일을 기억하며, 총 지출을 한눈에 파악할 수 있도록 설계되었습니다.

### 1.2 핵심 가치
- **서버 없는 구조**: 모든 데이터는 로컬에 저장되어 프라이버시 보호
- **자동 탐지**: SMS 메시지 분석을 통한 구독 서비스 자동 탐지 기능
- **직관적 UI/UX**: Material 3 디자인의 카드 기반 사용자 인터페이스
- **지출 분석**: 다양한 차트와 통계를 통한 구독 지출 최적화 지원

### 1.3 타겟 사용자
- 다수의 구독 서비스를 이용하는 디지털 소비자
- 월간 지출 관리에 관심 있는 사용자
- 결제일과 예산을 체계적으로 관리하고자 하는 사용자

## 2. 기술 스택

### 2.1 프레임워크 및 언어
- **Flutter 3.x**: 크로스 플랫폼 모바일 애플리케이션 개발
- **Dart**: 프로그래밍 언어, 널 안전성(null-safety) 활용

### 2.2 데이터 저장
- **Hive**: 로컬 데이터베이스 (NoSQL 키-값 저장소)
  - 모든 데이터를 타입별 Box에 저장 (`subscriptions`, `categories` 등)
  - 데이터 모델에 `@HiveType` 및 `@HiveField` 애노테이션 적용
- **flutter_secure_storage**: 민감한 정보를 위한 암호화 저장소
  - 생체 인증 설정, 알림 설정 등 저장

### 2.3 상태 관리
- **Provider**: 애플리케이션 상태 관리 패턴
  - `ChangeNotifier`를 상속한 Provider 클래스로 상태 관리
  - `Consumer` 및 `Provider.of<T>` 방식으로 상태 구독
  - 위젯 트리 최상단에 `MultiProvider`로 모든 Provider 등록

### 2.4 주요 패키지
- **flutter_local_notifications**: 로컬 알림 관리
  - 구독 결제일 알림 스케줄링
  - 알림 채널 구성 및 권한 관리
- **fl_chart**: 차트 및 그래프 시각화
  - `BarChart`, `PieChart`로 지출 데이터 시각화
- **intl**: 국제화 및 날짜/숫자 포맷팅
  - 통화 형식, 날짜 포맷을 현지화하여 표시
- **local_auth**: 생체 인증
  - 지문, 얼굴 인식을 통한 앱 잠금 관리
- **telephony/flutter_sms**: SMS 메시지 접근
  - 구독 서비스 자동 탐지용 SMS 분석
- **http**: API 요청
  - 환율 정보 가져오기 (ExchangeRate-API 사용)
- **url_launcher**: 외부 URL 열기
  - 구독 서비스 웹사이트로 바로 이동 기능

## 3. 핵심 기능 명세

### 3.1 구독 관리
- **구독 서비스 수동 추가/편집/삭제**
  - 사용자가 직접 구독 정보 입력 및 관리
  - 모든 필드에 대한 유효성 검사 구현
- **서비스명, 월 비용, 결제 주기, 결제일 관리**
  - 주기 옵션: 월간, 연간, 주간 선택 가능
  - 결제일: 달력 UI로 날짜 선택
- **카테고리별 구독 서비스 분류**
  - 기본 제공 카테고리: OTT, 음악, AI, 프로그래밍/개발, 오피스/협업 툴, 기타
  - 사용자 정의 카테고리 추가/편집/삭제 가능
- **중복 구독 확인 및 관리**
  - 동일 서비스 중복 등록 시 알림
  - 서비스명 유사도 검사 알고리즘 구현

### 3.2 SMS 자동 탐지
- **SMS 메시지 기반 구독 서비스 자동 인식**
  - 구독 관련 키워드 필터링 (`구독`, `결제`, `청구`, `subscription` 등)
  - 발신자 및 내용 패턴 분석
- **반복 결제 패턴 분석**
  - 동일 발신자, 유사 금액으로 2회 이상 메시지가 발견된 경우 구독으로 판단
  - 결제 주기 자동 감지 (월 단위, 연 단위)
- **일회성 결제와 정기 구독 구분**
  - 반복 결제 횟수(`repeatCount`) 추적
  - 구독 서비스 이름 매칭 로직으로 서비스 식별

### 3.3 결제일 알림
- **결제 예정일 3일 전 알림**
  - 알림 제목: "구독 결제 예정 알림"
  - 알림 내용: "[서비스명] 결제가 3일 후 예정되어 있습니다"
- **미사용 서비스 알림 (2개월 이상)**
  - 마지막 이용일 트래킹
  - 장기 미사용 감지 시 알림 발송
- **맞춤형 알림 설정**
  - 알림 활성화/비활성화 옵션
  - 알림 타입별 개별 설정 가능

### 3.4 분석 및 리포트
- **월별 총 지출 차트**
  - 막대 그래프로 월별 지출 추이 시각화
  - 최근 6개월 데이터 표시
- **서비스별 지출 비율 차트**
  - 파이 차트로 구독 서비스별 지출 비중 시각화
  - 차트 항목 터치 시 상세 정보 표시
- **카테고리별 지출 분석**
  - 카테고리별 총 지출 집계
  - 이전 달 대비 증감률 표시

### 3.5 다국어 및 환율 지원
- **앱 다국어 지원 (한국어/영어)**
  - `app_ko.arb`, `app_en.arb` 리소스 파일로 현지화
  - 시스템 언어 기반 자동 언어 설정 및 수동 변경 옵션
- **USD/KRW 환율 자동 변환 기능**
  - 실시간 환율 정보 API 호출 (6시간 주기 캐시)
  - 달러 금액 입력 시 원화 환산 금액 동시 표시
- **통화 단위 선택 옵션**
  - 각 구독별 통화 단위(KRW, USD) 설정 가능
  - 통합 보기 시 단일 통화로 합산 표시

### 3.6 보안 기능
- **생체 인증을 통한 앱 잠금**
  - 지문 인식 또는 얼굴 인식으로 앱 접근 제어
  - 인증 실패 시 재시도 옵션 제공
- **앱 백그라운드 전환 시 자동 잠금**
  - 앱 라이프사이클 상태 감지 (paused → resumed)
  - 설정에 따라 자동 잠금 활성화/비활성화

## 4. 아키텍처 및 코드 구조

### 4.1 아키텍처 패턴
- **MVVM 패턴 응용**
  - **Model**: Hive 데이터 모델 (SubscriptionModel, CategoryModel)
  - **View**: 화면 위젯 (StatelessWidget, StatefulWidget)
  - **ViewModel**: Provider 클래스 (ChangeNotifier)
- **서비스 레이어 패턴**
  - 비즈니스 로직을 서비스 클래스로 분리
  - 각 서비스는 단일 책임 원칙(SRP)을 따름

### 4.2 폴더 구조 및 파일 조직
```
/lib
 ├── main.dart                     # 앱 진입점
 ├── navigator_key.dart            # 전역 네비게이터 키
 ├── screens/                      # 화면 구성 파일
 │    ├── main_screen.dart         # 메인 화면
 │    ├── add_subscription_screen.dart  # 구독 추가 화면
 │    ├── detail_screen.dart       # 구독 상세 화면
 │    ├── analysis_screen.dart     # 분석 화면
 │    ├── settings_screen.dart     # 설정 화면
 │    ├── sms_scan_screen.dart     # SMS 스캔 화면
 │    ├── splash_screen.dart       # 스플래시 화면
 │    ├── app_lock_screen.dart     # 앱 잠금 화면
 │    └── category_management_screen.dart  # 카테고리 관리 화면
 ├── models/                       # 데이터 모델
 │    ├── subscription_model.dart  # 구독 모델
 │    ├── subscription_model.g.dart  # 자동 생성 코드
 │    ├── category_model.dart      # 카테고리 모델
 │    └── category_model.g.dart    # 자동 생성 코드
 ├── providers/                    # 상태 관리
 │    ├── subscription_provider.dart  # 구독 데이터 관리
 │    ├── category_provider.dart   # 카테고리 데이터 관리
 │    ├── app_lock_provider.dart   # 앱 잠금 관리
 │    ├── notification_provider.dart  # 알림 설정 관리
 │    └── locale_provider.dart     # 다국어 관리
 ├── services/                     # 서비스 클래스
 │    ├── notification_service.dart  # 알림 서비스
 │    ├── sms_scanner.dart         # SMS 스캔 로직
 │    ├── sms_service.dart         # SMS 접근 서비스
 │    ├── exchange_rate_service.dart  # 환율 서비스
 │    ├── currency_util.dart       # 통화 유틸리티
 │    └── subscription_url_matcher.dart  # URL 매칭 서비스
 ├── utils/                        # 유틸리티 함수
 │    ├── format_helper.dart       # 포맷팅 유틸리티
 │    ├── animation_controller_helper.dart  # 애니메이션 유틸리티
 │    └── subscription_category_helper.dart  # 카테고리 유틸리티
 ├── widgets/                      # 재사용 위젯
 │    ├── subscription_card.dart   # 구독 카드 위젯
 │    ├── main_summary_card.dart   # 메인 요약 카드
 │    ├── exchange_rate_widget.dart  # 환율 정보 위젯
 │    ├── website_icon.dart        # 웹사이트 아이콘 위젯
 │    ├── category_header_widget.dart  # 카테고리 헤더
 │    ├── subscription_list_widget.dart  # 구독 목록 위젯
 │    ├── animated_wave_background.dart  # 웨이브 배경 위젯
 │    ├── empty_state_widget.dart  # 빈 상태 위젯
 │    └── skeleton_loading.dart    # 로딩 스켈레톤 위젯
 ├── theme/                        # 테마 관련
 │    ├── app_theme.dart           # 앱 테마 정의
 │    └── app_colors.dart          # 색상 정의
 └── l10n/                         # 다국어 리소스
      ├── app_localizations.dart   # 다국어 클래스
      ├── app_en.arb               # 영어 리소스
      └── app_ko.arb               # 한국어 리소스
```

### 4.3 데이터 흐름
1. **사용자 입력** → **화면 위젯** → **Provider 메서드 호출**
2. **Provider** → **데이터 처리** → **Hive Box 저장/조회**
3. **Provider** → **상태 변경** → **notifyListeners()** → **UI 갱신**

### 4.4 코딩 표준 및 가이드라인
- **명명 규칙**
  - **클래스**: PascalCase (예: `SubscriptionModel`, `MainScreen`)
  - **변수/메서드**: camelCase (예: `monthlyCost`, `addSubscription()`)
  - **상수**: SNAKE_CASE (예: `MAX_SUBSCRIPTIONS`)
  - **비공개 멤버**: 언더스코어 접두사 (예: `_subscriptions`, `_loadData()`)
- **코드 구성**
  - 각 파일은 한 가지 주요 클래스/위젯만 포함
  - 주석을 통한 코드 문서화 (특히 복잡한 로직)
  - 관련 기능은 같은 디렉토리에 그룹화
- **코드 스타일**
  - Dart 공식 스타일 가이드 준수
  - Lint 규칙 적용 (`analysis_options.yaml`)
  - 한 줄 최대 80자 제한

## 5. 데이터 모델

### 5.1 구독 모델 (SubscriptionModel)
```dart
@HiveType(typeId: 0)
class SubscriptionModel extends HiveObject {
  @HiveField(0)
  final String id; // UUID 형식의 고유 식별자

  @HiveField(1)
  String serviceName; // 구독 서비스명

  @HiveField(2)
  double monthlyCost; // 월 비용

  @HiveField(3)
  String billingCycle; // 결제 주기: '월간', '연간', '주간'

  @HiveField(4)
  DateTime nextBillingDate; // 다음 결제 예정일

  @HiveField(5)
  bool isAutoDetected; // SMS 자동 탐지 여부

  @HiveField(6)
  String? categoryId; // 카테고리 ID (외래 키)

  @HiveField(7)
  String? websiteUrl; // 서비스 웹사이트 URL

  @HiveField(8)
  int repeatCount; // 반복 결제 횟수 (SMS 탐지용)

  @HiveField(9)
  DateTime? lastPaymentDate; // 마지막 결제일

  @HiveField(10)
  String currency; // 통화 단위: 'KRW' 또는 'USD'

  // 생성자
  SubscriptionModel({
    required this.id,
    required this.serviceName,
    required this.monthlyCost,
    required this.billingCycle,
    required this.nextBillingDate,
    this.isAutoDetected = false,
    this.categoryId,
    this.websiteUrl,
    this.repeatCount = 1,
    this.lastPaymentDate,
    this.currency = 'KRW', // 기본값은 KRW
  });

  // 주기적 결제 여부 확인
  bool get isRecurring => repeatCount > 1;
}
```

### 5.2 카테고리 모델 (CategoryModel)
```dart
@HiveType(typeId: 1)
class CategoryModel extends HiveObject {
  @HiveField(0)
  String id; // UUID 형식의 고유 식별자

  @HiveField(1)
  String name; // 카테고리 이름

  @HiveField(2)
  String color; // 색상 (HEX 코드: '#3B82F6')

  @HiveField(3)
  String icon; // 아이콘 이름 (Material Icons)

  // 생성자
  CategoryModel({
    required this.id,
    required this.name,
    required this.color,
    required this.icon,
  });
}
```

### 5.3 Provider 모델 구현

#### 5.3.1 구독 Provider (SubscriptionProvider)
```dart
class SubscriptionProvider extends ChangeNotifier {
  late Box<SubscriptionModel> _subscriptionBox;
  List<SubscriptionModel> _subscriptions = [];
  bool _isLoading = true;

  List<SubscriptionModel> get subscriptions => _subscriptions;
  bool get isLoading => _isLoading;

  double get totalMonthlyExpense {
    return _subscriptions.fold(
      0.0,
      (sum, subscription) => sum + subscription.monthlyCost,
    );
  }

  Future<void> init() async {
    try {
      _isLoading = true;
      notifyListeners();

      _subscriptionBox = await Hive.openBox<SubscriptionModel>('subscriptions');
      await refreshSubscriptions();

      _isLoading = false;
      notifyListeners();
    } catch (e) {
      debugPrint('구독 초기화 중 오류 발생: $e');
      _isLoading = false;
      notifyListeners();
      rethrow;
    }
  }

  Future<void> refreshSubscriptions() async {
    try {
      _subscriptions = _subscriptionBox.values.toList()
        ..sort((a, b) => a.nextBillingDate.compareTo(b.nextBillingDate));
      notifyListeners();
    } catch (e) {
      // 오류 처리
      rethrow;
    }
  }

  Future<void> addSubscription({
    required String serviceName,
    required double monthlyCost,
    required String billingCycle,
    required DateTime nextBillingDate,
    String? websiteUrl,
    String? categoryId,
    bool isAutoDetected = false,
    int repeatCount = 1,
    DateTime? lastPaymentDate,
    String currency = 'KRW',
  }) async {
    // 구현...
  }

  Future<void> updateSubscription(SubscriptionModel subscription) async {
    // 구현...
  }

  Future<void> deleteSubscription(String id) async {
    // 구현...
  }
}
```

#### 5.3.2 카테고리 Provider (CategoryProvider)
```dart
class CategoryProvider extends ChangeNotifier {
  List<CategoryModel> _categories = [];
  late Box<CategoryModel> _categoryBox;

  List<CategoryModel> get categories => _categories;

  Future<void> init() async {
    _categoryBox = await Hive.openBox<CategoryModel>('categories');
    _categories = _categoryBox.values.toList();

    // 카테고리가 비어있으면 기본 카테고리 추가
    if (_categories.isEmpty) {
      await _initDefaultCategories();
    }

    notifyListeners();
  }

  Future<void> _initDefaultCategories() async {
    final defaultCategories = [
      {'name': 'OTT 서비스', 'color': '#3B82F6', 'icon': 'live_tv'},
      {'name': '음악 서비스', 'color': '#EC4899', 'icon': 'music_note'},
      {'name': 'AI 서비스', 'color': '#8B5CF6', 'icon': 'psychology'},
      {'name': '프로그래밍/개발', 'color': '#10B981', 'icon': 'code'},
      {'name': '오피스/협업 툴', 'color': '#F59E0B', 'icon': 'business_center'},
      {'name': '기타 서비스', 'color': '#6B7280', 'icon': 'more_horiz'},
    ];

    // 구현...
  }

  // 기타 메서드...
}
``` 

## 6. UI 디자인 및 레이아웃

### 6.1 디자인 시스템

#### 6.1.1 색상 팔레트
- **주 색상(Primary)**: `#3B82F6` (파란색) - 버튼, 강조 텍스트, 앱바 등
  - Tailwind 매핑: `blue-500`
  - 밝은 변형: `#60A5FA` (blue-400), 어두운 변형: `#2563EB` (blue-600)
- **보조 색상(Secondary)**: `#EC4899` (분홍색) - 음악 카테고리, 부가 기능
  - Tailwind 매핑: `pink-500`
  - 밝은 변형: `#F472B6` (pink-400), 어두운 변형: `#DB2777` (pink-600)
- **배경색(Background)**: `#F8FAFC` (밝은 회색) - 앱 배경
  - Tailwind 매핑: `slate-50`
- **표면색(Surface)**: `#FFFFFF` (흰색) - 카드, 다이얼로그 배경
  - Tailwind 매핑: `white`
  - 대체 표면색: `#F1F5F9` (slate-100)
- **오류색(Error)**: `#EF4444` (빨간색) - 경고, 삭제 버튼
  - Tailwind 매핑: `red-500`
- **성공색(Success)**: `#10B981` (녹색) - 성공 메시지
  - Tailwind 매핑: `emerald-500`
- **카테고리 색상**:
  - OTT 서비스: `#3B82F6` (blue-500)
  - 음악 서비스: `#EC4899` (pink-500)
  - AI 서비스: `#8B5CF6` (violet-500)
  - 프로그래밍/개발: `#10B981` (emerald-500)
  - 오피스/협업 툴: `#F59E0B` (amber-500)
  - 기타 서비스: `#6B7280` (gray-500)

##### 그라데이션 색상
- **파란색 그라데이션**: `linear-gradient(135deg, #3B82F6 0%, #60A5FA 100%)`
  - Tailwind 구현: `bg-gradient-to-tr from-blue-500 to-blue-400`
- **분홍색 그라데이션**: `linear-gradient(135deg, #EC4899 0%, #F472B6 100%)`
  - Tailwind 구현: `bg-gradient-to-tr from-pink-500 to-pink-400`
- **보라색 그라데이션**: `linear-gradient(135deg, #8B5CF6 0%, #A78BFA 100%)`
  - Tailwind 구현: `bg-gradient-to-tr from-violet-500 to-violet-400`
- **요약 카드 그라데이션**: `linear-gradient(135deg, #3B82F6 0%, #2563EB 100%)`
  - Tailwind 구현: `bg-gradient-to-tr from-blue-500 to-blue-600`

#### 6.1.2 타이포그래피
- **폰트 패밀리**: Roboto (안드로이드), SF Pro (iOS), Noto Sans KR (한글)
  - Tailwind 매핑: `font-sans`
- **크기 체계**:
  - 타이틀 대: 24sp, 굵게(700) - `text-2xl font-bold`
  - 타이틀 중: 20sp, 굵게(700) - `text-xl font-bold`
  - 타이틀 소: 16sp, 굵게(700) - `text-base font-bold`
  - 본문: 14sp, 일반(400) - `text-sm font-normal`
  - 부제목: 12sp, 일반(400) - `text-xs font-normal`
  - 캡션: 10sp, 일반(400) - `text-[10px] font-normal`
- **행간**: 
  - 타이틀: 1.2 - `leading-tight`
  - 본문: 1.5 - `leading-normal`
- **색상**:
  - 기본 텍스트: `#0F172A` (slate-900) - `text-slate-900`
  - 보조 텍스트: `#64748B` (slate-500) - `text-slate-500`
  - 비활성화 텍스트: `#94A3B8` (slate-400) - `text-slate-400`
  - 반전 텍스트(어두운 배경): `#FFFFFF` (white) - `text-white`

#### 6.1.3 그림자 및 입체감
- **카드 그림자**: `box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06)`
  - Tailwind 매핑: `shadow-md`
- **버튼 그림자**: `box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1)`
  - Tailwind 매핑: `shadow-sm`
- **팝업 그림자**: `box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15)`
  - Tailwind 매핑: `shadow-lg`
- **입체 효과**:
  - 약간 올라옴: `shadow-sm hover:shadow-md transition-shadow`
  - 확실히 올라옴: `shadow-md hover:shadow-lg transition-shadow`
  - 플로팅 버튼: `shadow-lg`

#### 6.1.4 모서리 둥글기
- **카드**: BorderRadius.circular(16) - `rounded-2xl`
- **버튼**: BorderRadius.circular(12) - `rounded-xl`
- **입력 필드**: BorderRadius.circular(8) - `rounded-lg`
- **칩**: BorderRadius.circular(24) - `rounded-full`
- **팝업/다이얼로그**: BorderRadius.circular(24) - `rounded-3xl`

#### 6.1.5 아이콘 시스템
- **아이콘 세트**: Material Icons
- **아이콘 크기**:
  - 표준: 24dp - `w-6 h-6`
  - 작게: 18dp - `w-4.5 h-4.5`
  - 크게: 36dp - `w-9 h-9`
- **아이콘 색상**: 주로 색상 팔레트의 주 색상과 보조 색상 활용
  - 기본: `text-slate-700`
  - 강조: `text-blue-500`
  - 비활성화: `text-slate-300`

#### 6.1.6 간격 및 여백 체계
- **기본 여백(Margin)**: 16dp - `m-4`
- **기본 패딩(Padding)**: 16dp - `p-4`
- **섹션 간격**: 24dp - `gap-6`
- **항목 간격**: 8dp - `gap-2`
- **내부 패딩**:
  - 카드: 16dp - `p-4`
  - 버튼: 수평 16dp, 수직 12dp - `px-4 py-3`
  - 입력 필드: 수평 12dp, 수직 12dp - `px-3 py-3`

#### 6.1.7 애니메이션 및 전환 효과
- **지속 시간**:
  - 빠르게: 150ms - `duration-150`
  - 표준: 300ms - `duration-300`
  - 천천히: 500ms - `duration-500`
- **이징 함수**:
  - 기본: 'ease-in-out' - `ease-in-out`
  - 진입: 'ease-out' - `ease-out`
  - 퇴장: 'ease-in' - `ease-in`
- **기본 애니메이션**:
  - 페이드 인: `opacity-0 animate-fadeIn`
  - 슬라이드 업: `translate-y-4 animate-slideUp`
  - 확장: `scale-95 animate-scaleUp`

### 6.2 화면별 UI 레이아웃 상세

#### 6.2.1 스플래시 화면
- **배경**: 그라데이션 배경 (시작: `#3B82F6`, 끝: `#60A5FA`)
- **로고**: 앱 로고 중앙 배치, 애니메이션 적용
  - 페이드 인 애니메이션 (0.0 → 1.0, 300ms)
  - 스케일 애니메이션 (0.8 → 1.0, 400ms)
- **앱 이름**: 로고 아래 Text 위젯, 흰색(#FFFFFF)
- **애니메이션 효과**:
  - 물결 파동 배경 (AnimatedWaveBackground 위젯)
  - 파티클 효과 (20개의 움직이는 작은 원형 위젯)

#### 6.2.2 메인 화면
- **앱바**:
  - 제목: "SubManager" (좌측 정렬)
  - 설정 버튼 (우측 정렬)
  - 투명도 변화: 스크롤에 따라 0 → 1.0
- **상단 요약 카드**:
  - 파란색 그라데이션 배경
  - 총 구독 수 표시 (큰 숫자)
  - 총 지출 금액 표시 (중간 크기 텍스트)
  - 물결 애니메이션 배경
  - Card 형태, 모서리 둥글게
  - 터치 시 분석 화면으로 이동
- **카테고리 헤더**:
  - 카테고리 이름 (좌측)
  - 구독 수 및 화살표 아이콘 (우측)
- **구독 카드 리스트**:
  - 카테고리별로 그룹화
  - 각 구독은 Card 위젯으로 표현
  - 좌측: 웹사이트 아이콘/이미지
  - 중앙: 서비스명, 다음 결제일
  - 우측: 금액, 통화 단위
  - 스와이프 액션: 편집/삭제
- **플로팅 액션 버튼**:
  - 우측 하단 고정
  - + 아이콘
  - 주 색상 배경
  - 누르면 구독 추가 화면으로 이동

#### 6.2.3 구독 추가 화면
- **앱바**:
  - 제목: "구독 추가"
  - 뒤로 가기 버튼
  - SMS 스캔 버튼 (우측)
- **입력 폼**:
  - 각 필드는 Material 디자인의 OutlinedTextField
  - 에러 메시지는 필드 하단에 빨간색 텍스트
  - 필드 포커스 시 하이라이트 효과
- **서비스명 필드**:
  - 자동완성 기능 (인기 서비스 목록)
  - 입력 시 웹사이트 URL 추천
- **금액 필드**:
  - TextInputType.number 키보드
  - 통화 선택 드롭다운 (KRW/USD)
  - USD 선택 시 환율 정보 표시
- **결제 주기 선택**:
  - SegmentedButton 또는 DropdownButton
  - 옵션: 월간, 연간, 주간
- **결제일 선택**:
  - DatePicker 호출 아이콘 버튼
  - 선택된 날짜 표시
- **카테고리 선택**:
  - 카테고리 칩 목록 (가로 스크롤)
  - 각 칩은 카테고리 색상 적용
  - 선택된 카테고리 하이라이트
- **웹사이트 URL 필드**:
  - 선택적 입력 필드
  - URL 형식 검증
- **저장 버튼**:
  - 하단 고정
  - 넓은 버튼 (화면 폭의 80%)
  - 주 색상 배경
  - 저장 중에는 로딩 인디케이터 표시

#### 6.2.4 구독 상세 화면
- **헤더**:
  - 서비스 아이콘/이미지 (큰 크기)
  - 서비스명 (큰 텍스트)
  - 웹사이트 방문 버튼
- **정보 카드**:
  - 각 정보는 카드 섹션으로 구분
  - 편집 모드 전환 버튼 (우측 상단)
- **금액 정보**:
  - 큰 글씨로 금액 표시
  - 통화 단위 표시
  - 환율 정보 (해당하는 경우)
- **결제 주기 정보**:
  - 주기 아이콘 및 텍스트
  - 다음 결제일까지 남은 일수
- **카테고리 정보**:
  - 카테고리 색상 칩
  - 카테고리 이름
- **편집 모드 UI**:
  - 각 필드가 편집 가능한 상태로 전환
  - 저장/취소 버튼 표시
- **삭제 버튼**:
  - 하단 배치
  - 빨간색 텍스트와 아이콘
  - 누르면 확인 다이얼로그 표시

#### 6.2.5 분석 화면
- **앱바**:
  - 제목: "지출 분석"
  - 뒤로 가기 버튼
  - 기간 필터 옵션 (우측)
- **총 지출 요약 카드**:
  - 큰 숫자로 총액 표시
  - 이전 기간 대비 증감률 (화살표 아이콘)
  - 그라데이션 배경
- **그래프 섹션**:
  - 각 그래프는 카드 형태로 표시
  - 그래프 제목 및 설명
  - 인터랙티브 요소 (터치 피드백)
- **막대 그래프 (월별 지출)**:
  - fl_chart BarChart 위젯
  - x축: 월 레이블
  - y축: 금액
  - 데이터 막대: 그라데이션 색상
- **파이 차트 (서비스별 지출)**:
  - fl_chart PieChart 위젯
  - 각 섹션: 카테고리 색상
  - 터치 시 확대 효과
  - 레이블: 퍼센트 및 금액
- **카테고리별 지출 목록**:
  - 각 카테고리: 가로 막대형 진행 표시줄
  - 카테고리 아이콘, 이름, 금액, 퍼센트
  - 내림차순 정렬 (금액 기준)

#### 6.2.6 SMS 스캔 화면
- **헤더**:
  - 제목: "구독 자동 탐지"
  - 설명 텍스트
- **스캔 버튼**:
  - 큰 원형 버튼
  - 스캔 아이콘
  - 주 색상 배경
- **스캔 중 UI**:
  - 원형 프로그레스 인디케이터
  - "스캔 중..." 텍스트
- **결과 리스트**:
  - 각 발견된 구독은 카드 형태
  - 서비스명, 금액, 반복 횟수 표시
  - 체크박스로 선택 가능
- **추가 버튼**:
  - 하단 고정
  - 선택된 항목 수 표시
  - 비활성화 조건: 선택 항목 없음

#### 6.2.7 설정 화면
- **앱바**:
  - 제목: "설정"
  - 뒤로 가기 버튼
- **설정 그룹**:
  - 섹션 제목
  - 구분선으로 섹션 구분
- **알림 설정**:
  - 알림 마스터 스위치
  - 결제 예정 알림 스위치
  - 미사용 서비스 알림 스위치
- **보안 설정**:
  - 앱 잠금 마스터 스위치
  - 생체 인증 스위치
- **언어 설정**:
  - 라디오 버튼 목록
  - 현재 선택된 언어 표시
- **데이터 관리**:
  - 백업/복원 버튼
  - 데이터 초기화 버튼 (빨간색 경고 아이콘)
- **앱 정보**:
  - 앱 버전, 개발자 정보
  - 오픈소스 라이선스 링크 

### 6.3 모션 디자인 시스템

#### 6.3.1 기본 애니메이션 속성
- **기본 지속 시간**:
  - 컴포넌트 진입/퇴장: 300ms
  - 상태 변화: 200ms
  - 강조 효과: 150ms
  - 페이지 전환: 400ms
- **이징 함수**:
  - 기본: cubic-bezier(0.4, 0, 0.2, 1) - 부드러운 가속 및 감속 (ease-in-out)
  - 진입: cubic-bezier(0.0, 0, 0.2, 1) - 빠른 시작, 점진적 감속 (ease-out)
  - 퇴장: cubic-bezier(0.4, 0, 1, 1) - 점진적 가속, 빠른 종료 (ease-in)
  - 강조: cubic-bezier(0.2, 0, 0, 1) - 반동 효과 (spring)

#### 6.3.2 화면 전환 효과
- **기본 화면 전환** (일반 화면 간 이동):
  - 방식: 밀어내기 효과 (slide)
  - 새 화면: 오른쪽에서 왼쪽으로 슬라이드 인
  - 기존 화면: 왼쪽으로 밀려나가며 약간 투명해짐
  - 지속 시간: 300ms
  - 코드 구현:
    ```dart
    PageRouteBuilder(
      pageBuilder: (context, animation, secondaryAnimation) => NewScreen(),
      transitionsBuilder: (context, animation, secondaryAnimation, child) {
        var begin = Offset(1.0, 0.0);
        var end = Offset.zero;
        var curve = Curves.easeOutCubic;
        var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
        return SlideTransition(position: animation.drive(tween), child: child);
      },
    )
    ```

- **모달 화면 전환** (세부 정보, 설정 등):
  - 방식: 페이드 인 + 슬라이드 업
  - 새 화면: 아래에서 위로 슬라이드 인 + 불투명도 증가
  - 백드롭: 반투명 어두움으로 페이드 인
  - 지속 시간: 400ms
  - 코드 구현:
    ```dart
    showModalBottomSheet(
      context: context,
      isScrollControlled: true,
      backgroundColor: Colors.transparent,
      barrierColor: Colors.black54,
      transitionAnimationController: AnimationController(
        vsync: this,
        duration: Duration(milliseconds: 400),
      ),
      builder: (context) => /* 모달 내용 */,
    )
    ```

- **팝업 전환** (알림, 확인 다이얼로그):
  - 방식: 스케일 + 페이드 인
  - 새 화면: 중앙에서 스케일 업(0.8 → 1.0) + 불투명도 증가
  - 백드롭: 반투명 어두움으로 페이드 인
  - 지속 시간: 250ms
  - 코드 구현:
    ```dart
    showDialog(
      context: context,
      barrierDismissible: true,
      builder: (BuildContext context) {
        return TweenAnimationBuilder<double>(
          duration: Duration(milliseconds: 250),
          curve: Curves.easeOutCubic,
          tween: Tween(begin: 0.8, end: 1.0),
          builder: (context, value, child) {
            return Transform.scale(
              scale: value,
              child: Opacity(
                opacity: value,
                child: AlertDialog(/* 다이얼로그 내용 */),
              ),
            );
          },
        );
      },
    )
    ```

#### 6.3.3 스크롤 효과 및 동작
- **목록 스크롤 효과**:
  - 오버스크롤: 탄력적 바운스 효과 (iOS 스타일)
  - 스크롤바: 스크롤 중에만 표시되는 반투명 스크롤바
  - 목록 항목 애니메이션: 스크롤 시 항목별 지연된 페이드 인 효과
  - 코드 구현:
    ```dart
    ListView.builder(
      physics: BouncingScrollPhysics(),
      itemCount: items.length,
      itemBuilder: (context, index) {
        return AnimatedBuilder(
          animation: scrollController,
          builder: (context, child) {
            return AnimatedOpacity(
              duration: Duration(milliseconds: 300),
              opacity: index < scrollController.offset / 50 ? 1.0 : 0.0,
              child: child,
            );
          },
          child: ListItem(item: items[index]),
        );
      },
    )
    ```

- **패럴랙스 효과** (주로 상세 화면):
  - 배경 이미지: 스크롤 시 더 느리게 이동하여 깊이감 생성
  - 카드 요소: 스크롤 속도에 따라 약간의 회전 및 이동
  - 코드 구현:
    ```dart
    ScrollController _scrollController = ScrollController();
    // ...
    return SingleChildScrollView(
      controller: _scrollController,
      child: Column(
        children: [
          AnimatedBuilder(
            animation: _scrollController,
            builder: (context, child) {
              return Container(
                height: 200 - _scrollController.offset * 0.5,
                transform: Matrix4.translationValues(0, _scrollController.offset * 0.3, 0),
                child: Image.asset('assets/header_bg.png', fit: BoxFit.cover),
              );
            },
          ),
          // 나머지 콘텐츠
        ],
      ),
    )
    ```

- **스크롤 감지 기반 UI 변화**:
  - 앱바: 스크롤 다운 시 축소, 스크롤 업 시 확장
  - FAB(플로팅 액션 버튼): 스크롤 다운 시 숨김, 스크롤 업 시 표시
  - 코드 구현:
    ```dart
    ScrollController _scrollController = ScrollController();
    bool _isScrollingDown = false;
    bool _isAppBarExpanded = true;
    
    @override
    void initState() {
      super.initState();
      _scrollController.addListener(() {
        if (_scrollController.position.userScrollDirection == ScrollDirection.reverse) {
          if (!_isScrollingDown) {
            setState(() {
              _isScrollingDown = true;
              _isAppBarExpanded = false;
            });
          }
        }
        if (_scrollController.position.userScrollDirection == ScrollDirection.forward) {
          if (_isScrollingDown) {
            setState(() {
              _isScrollingDown = false;
              _isAppBarExpanded = true;
            });
          }
        }
      });
    }
    ```

#### 6.3.4 마이크로 인터랙션 및 상태 변화
- **버튼 상태 변화**:
  - 누름 효과: 스케일 다운(0.96) + 그림자 감소
  - 호버 효과: 약간의 상승 + 그림자 증가
  - 비활성화: 불투명도 감소(0.7) + 회색조 효과
  - 코드 구현:
    ```dart
    ElevatedButton(
      onPressed: isEnabled ? () => handlePress() : null,
      style: ElevatedButton.styleFrom(
        padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        elevation: 4,
      ).copyWith(
        foregroundColor: MaterialStateProperty.resolveWith<Color>((states) {
          if (states.contains(MaterialState.disabled)) return Colors.grey;
          return Colors.white;
        }),
        backgroundColor: MaterialStateProperty.resolveWith<Color>((states) {
          if (states.contains(MaterialState.disabled)) return Colors.grey[300]!;
          if (states.contains(MaterialState.pressed)) return Colors.blue[700]!;
          return Colors.blue[500]!;
        }),
        elevation: MaterialStateProperty.resolveWith<double>((states) {
          if (states.contains(MaterialState.disabled)) return 0;
          if (states.contains(MaterialState.pressed)) return 2;
          return 4;
        }),
        overlayColor: MaterialStateProperty.all<Color>(Colors.white.withOpacity(0.1)),
      ),
      child: Text('Button'),
    )
    ```

- **필드 포커스 효과**:
  - 포커스 진입: 테두리 색상 변경 + 약간의 확장(1.02)
  - 오류 상태: 좌우 흔들림 애니메이션 + 빨간색 테두리
  - 완료 상태: 페이드 인 체크 아이콘 + 녹색 테두리
  - 코드 구현:
    ```dart
    FocusNode _focusNode = FocusNode();
    bool _hasError = false;
    
    @override
    void initState() {
      super.initState();
      _focusNode.addListener(() {
        setState(() {});
      });
    }
    
    // 위젯 내부
    AnimatedContainer(
      duration: Duration(milliseconds: 200),
      curve: Curves.easeInOut,
      transform: _hasError 
          ? (Matrix4.identity()..translate(sin(DateTime.now().millisecondsSinceEpoch / 100) * 5.0, 0.0))
          : Matrix4.identity(),
      decoration: BoxDecoration(
        border: Border.all(
          color: _hasError 
              ? Colors.red 
              : (_focusNode.hasFocus ? Colors.blue : Colors.grey),
          width: _focusNode.hasFocus ? 2.0 : 1.0,
        ),
        borderRadius: BorderRadius.circular(8),
      ),
      child: TextField(
        focusNode: _focusNode,
        decoration: InputDecoration(
          border: InputBorder.none,
          contentPadding: EdgeInsets.all(12),
        ),
      ),
    )
    ```

- **체크 박스/토글 효과**:
  - 체크 표시: 페이드 인 + 스케일 업(0.5 → 1.0)
  - 토글 슬라이더: 부드러운 좌우 이동 + 배경색 변경
  - 코드 구현:
    ```dart
    AnimatedContainer(
      duration: Duration(milliseconds: 300),
      curve: Curves.easeInOut,
      width: 50,
      height: 30,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(15),
        color: isEnabled ? Colors.blue[500] : Colors.grey[300],
      ),
      child: Stack(
        children: [
          AnimatedPositioned(
            duration: Duration(milliseconds: 300),
            curve: Curves.easeInOut,
            left: isEnabled ? 20 : 0,
            top: 0,
            bottom: 0,
            child: Container(
              width: 30,
              height: 30,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.white,
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.1),
                    blurRadius: 4,
                    spreadRadius: 1,
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    )
    ```

#### 6.3.5 특수 효과 및 고급 애니메이션
- **물결 배경 효과** (메인 카드 및 스플래시 화면):
  - 구성: 여러 층의 곡선 Path를 사용한 파동 효과
  - 애니메이션: sin 함수 기반으로 연속적인 물결 모양 변형
  - 상호작용: 터치 위치에 따라 물결 진폭 변화
  - 코드 구현:
    ```dart
    class WaveBackground extends StatefulWidget {
      @override
      _WaveBackgroundState createState() => _WaveBackgroundState();
    }
    
    class _WaveBackgroundState extends State<WaveBackground> with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          vsync: this,
          duration: Duration(seconds: 4),
        )..repeat();
      }
      
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
      
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return CustomPaint(
              painter: WavePainter(
                animation: _controller,
                waveColor: Colors.blue.withOpacity(0.3),
              ),
              child: Container(),
            );
          },
        );
      }
    }
    
    class WavePainter extends CustomPainter {
      final Animation<double> animation;
      final Color waveColor;
      
      WavePainter({required this.animation, required this.waveColor});
      
      @override
      void paint(Canvas canvas, Size size) {
        // 물결 그리기 로직...
      }
      
      @override
      bool shouldRepaint(WavePainter oldDelegate) => true;
    }
    ```

- **파티클 효과** (달성, 저장 성공 시):
  - 구성: 랜덤한 크기, 색상, 방향으로 분산되는 작은 원형 파티클
  - 애니메이션: 중심점에서 밖으로 퍼져나가는 궤적 + 페이드 아웃
  - 코드 구현:
    ```dart
    class ParticleEffect extends StatefulWidget {
      @override
      _ParticleEffectState createState() => _ParticleEffectState();
    }
    
    class _ParticleEffectState extends State<ParticleEffect> with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      late List<Particle> particles;
      
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          vsync: this,
          duration: Duration(milliseconds: 1000),
        )..forward();
        
        particles = List.generate(30, (index) => Particle());
      }
      
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _controller,
          builder: (context, child) {
            return CustomPaint(
              painter: ParticlePainter(
                animation: _controller,
                particles: particles,
              ),
              child: Container(),
            );
          },
        );
      }
    }
    
    class Particle {
      late double speed;
      late double theta;
      late double radius;
      late Color color;
      
      Particle() {
        // 랜덤 초기화 로직...
      }
    }
    
    class ParticlePainter extends CustomPainter {
      // 구현...
    }
    ```

- **카드 스택 효과** (메인 화면 구독 카드):
  - 구성: 스택에 쌓인 카드들이 3D 회전 및 깊이감 표현
  - 애니메이션: 카드 선택 시 카드가 앞으로 나오며 확대
  - 스와이프: 스와이프로 카드 스택 탐색
  - 코드 구현:
    ```dart
    class CardStack extends StatefulWidget {
      final List<Widget> cards;
      CardStack({required this.cards});
      
      @override
      _CardStackState createState() => _CardStackState();
    }
    
    class _CardStackState extends State<CardStack> {
      int _selectedIndex = 0;
      
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          onHorizontalDragEnd: (details) {
            if (details.primaryVelocity! > 0) {
              // 오른쪽 스와이프
              setState(() {
                _selectedIndex = math.max(0, _selectedIndex - 1);
              });
            } else if (details.primaryVelocity! < 0) {
              // 왼쪽 스와이프
              setState(() {
                _selectedIndex = math.min(widget.cards.length - 1, _selectedIndex + 1);
              });
            }
          },
          child: Stack(
            alignment: Alignment.center,
            children: List.generate(widget.cards.length, (index) {
              // 각 카드의 위치 및 크기 계산
              final offset = index - _selectedIndex;
              final isSelected = offset == 0;
              
              return AnimatedPositioned(
                duration: Duration(milliseconds: 300),
                curve: Curves.easeOutCubic,
                top: isSelected ? 0 : 10 * offset.abs(),
                right: isSelected ? 0 : offset < 0 ? -20 * offset.abs() : 0,
                left: isSelected ? 0 : offset > 0 ? -20 * offset.abs() : 0,
                child: AnimatedOpacity(
                  duration: Duration(milliseconds: 300),
                  opacity: offset.abs() < 3 ? 1.0 - (0.2 * offset.abs()) : 0,
                  child: AnimatedScale(
                    duration: Duration(milliseconds: 300),
                    scale: 1.0 - (0.05 * offset.abs()),
                    child: widget.cards[index],
                  ),
                ),
              );
            }),
          ),
        );
      }
    }
    ```

- **로딩 및 진행 애니메이션**:
  - 스켈레톤 로딩: 펄스 애니메이션이 있는 그레이스케일 레이아웃
  - 원형 진행: 원형 프로그레스 인디케이터 + 숫자 증가 애니메이션
  - 성공 체크: 그리기 애니메이션으로 구현된 체크 마크
  - 코드 구현:
    ```dart
    // 스켈레톤 로딩
    class SkeletonLoading extends StatefulWidget {
      final Widget child;
      
      const SkeletonLoading({Key? key, required this.child}) : super(key: key);
      
      @override
      _SkeletonLoadingState createState() => _SkeletonLoadingState();
    }
    
    class _SkeletonLoadingState extends State<SkeletonLoading> with SingleTickerProviderStateMixin {
      late AnimationController _controller;
      late Animation<double> _animation;
      
      @override
      void initState() {
        super.initState();
        _controller = AnimationController(
          vsync: this,
          duration: Duration(milliseconds: 1500),
        )..repeat(reverse: true);
        
        _animation = Tween<double>(begin: 0.4, end: 0.9).animate(_controller);
      }
      
      @override
      Widget build(BuildContext context) {
        return AnimatedBuilder(
          animation: _animation,
          builder: (context, child) {
            return ShaderMask(
              shaderCallback: (rect) {
                return LinearGradient(
                  colors: [
                    Colors.grey[300]!,
                    Colors.grey[100]!,
                    Colors.grey[300]!,
                  ],
                  stops: [
                    0.0,
                    _animation.value,
                    1.0,
                  ],
                ).createShader(rect);
              },
              child: Container(
                color: Colors.white,
                child: widget.child,
              ),
            );
          },
        );
      }
    }
    ```

## 7. 핵심 알고리즘 및 기능 구현

### 7.1 SMS 기반 구독 탐지 알고리즘
```dart
// sms_scanner.dart 내부 구현 예시
const platform = MethodChannel('com.submanager/sms');
try {
  smsList = await platform.invokeMethod('scanSubscriptions');
  print('SmsScanner: 네이티브 호출 성공, SMS 데이터 개수: ${smsList.length}');
} catch (e) {
  print('SmsScanner: 네이티브 호출 실패: $e');
  // 오류 발생 시 빈 목록 반환
  return [];
}
```

### 7.2 구독 서비스 자동 카테고리 분류 알고리즘
```dart
// subscription_category_helper.dart 내부 구현
void autoDetectCategory(String serviceName) {
  // OTT 서비스 확인
  if (SubscriptionUrlMatcher.ottServices.keys.any((key) =>
      serviceName.toLowerCase().contains(key.toLowerCase()) ||
      key.toLowerCase().contains(serviceName.toLowerCase()))) {
    try {
      final ottCategory = categoryProvider.categories.firstWhere(
        (cat) => cat.name.contains('OTT') || cat.name.contains('스트리밍'),
      );
      _selectedCategoryId = ottCategory.id;
      return;
    } catch (_) {
      // 카테고리를 찾지 못한 경우 기본 처리
    }
  }

  // 음악 서비스 확인
  if (SubscriptionUrlMatcher.musicServices.keys.any((key) =>
      serviceName.toLowerCase().contains(key.toLowerCase()) ||
      key.toLowerCase().contains(serviceName.toLowerCase()))) {
    try {
      final musicCategory = categoryProvider.categories.firstWhere(
        (cat) => cat.name.contains('음악') || cat.name.contains('스트리밍'),
      );
      _selectedCategoryId = musicCategory.id;
      return;
    } catch (_) {
      // 카테고리를 찾지 못한 경우 기본 처리
    }
  }

  // AI 서비스, 프로그래밍/개발, 오피스/협업 툴 등 확인 로직 생략...

  // 기타 서비스 처리
  try {
    final otherCategory = categoryProvider.categories.firstWhere(
      (cat) => cat.name.contains('기타'),
    );
    _selectedCategoryId = otherCategory.id;
  } catch (_) {
    // 기타 카테고리도 없는 경우 첫 번째 카테고리 선택
    if (categoryProvider.categories.isNotEmpty) {
      _selectedCategoryId = categoryProvider.categories.first.id;
    }
  }
}
```

### 7.3 환율 변환 및 캐싱 알고리즘
```dart
// exchange_rate_service.dart 내부 구현
class ExchangeRateService {
  // 싱글톤 인스턴스
  static final ExchangeRateService _instance = ExchangeRateService._internal();
  factory ExchangeRateService() => _instance;
  ExchangeRateService._internal();

  // 캐싱된 환율 정보
  double? _usdToKrwRate;
  DateTime? _lastUpdated;
  final String _apiUrl = 'https://api.exchangerate-api.com/v4/latest/USD';

  Future<double?> getUsdToKrwRate() async {
    // 캐싱된 데이터 있고 6시간 이내면 캐싱된 데이터 반환
    if (_usdToKrwRate != null && _lastUpdated != null) {
      final difference = DateTime.now().difference(_lastUpdated!);
      if (difference.inHours < 6) {
        return _usdToKrwRate;
      }
    }

    try {
      // API 요청
      final response = await http.get(Uri.parse(_apiUrl));

      if (response.statusCode == 200) {
        final data = json.decode(response.body);
        _usdToKrwRate = data['rates']['KRW'].toDouble();
        _lastUpdated = DateTime.now();
        return _usdToKrwRate;
      } else {
        // 실패 시 캐싱된 값이라도 반환
        return _usdToKrwRate;
      }
    } catch (e) {
      // 오류 발생 시 캐싱된 값이라도 반환
      return _usdToKrwRate;
    }
  }

  // USD 금액을 KRW로 변환
  Future<double?> convertUsdToKrw(double usdAmount) async {
    final rate = await getUsdToKrwRate();
    if (rate != null) {
      return usdAmount * rate;
    }
    return null;
  }
}
```

### 7.4 결제일 알림 스케줄링 알고리즘
```dart
// notification_service.dart 내부 구현
static Future<void> schedulePaymentReminder({
  required int id,
  required String serviceName,
  required double amount,
  required DateTime billingDate,
}) async {
  // 결제 3일 전 오전 10시에 알림 설정
  final scheduledDate = billingDate
      .subtract(const Duration(days: 3))
      .copyWith(hour: 10, minute: 0, second: 0, millisecond: 0, microsecond: 0);

  await _notifications.zonedSchedule(
    id,
    '구독 결제 예정 알림',
    '$serviceName 구독료 ${amount.toStringAsFixed(0)}원이 3일 후 결제 예정입니다.',
    tz.TZDateTime.from(scheduledDate, tz.local),
    const NotificationDetails(
      android: AndroidNotificationDetails(
        'subscription_channel',
        'Subscription Notifications',
        channelDescription: 'Channel for subscription reminders',
        importance: Importance.high,
        priority: Priority.high,
      ),
      iOS: DarwinNotificationDetails(),
    ),
    uiLocalNotificationDateInterpretation:
        UILocalNotificationDateInterpretation.absoluteTime,
    androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
  );
}
```

### 7.5 구독료 통계 계산 알고리즘
```dart
Future<void> _calculateTotalExpense() async {
  setState(() => _isLoading = true);

  try {
    final provider = Provider.of<SubscriptionProvider>(context, listen: false);
    final subscriptions = provider.subscriptions;

    if (subscriptions.isEmpty) {
      setState(() {
        _totalExpense = 0.0;
        _isLoading = false;
      });
      return;
    }

    // 모든 구독의 월 비용을 원화로 환산하여 계산
    final total = await CurrencyUtil.calculateTotalMonthlyExpense(subscriptions);

    setState(() {
      _totalExpense = total;
      _isLoading = false;
    });
  } catch (e) {
    debugPrint('총 지출 계산 오류: $e');
    setState(() => _isLoading = false);
  }
}
```

### 7.6 서비스 데이터베이스 구현

구독 서비스의 URL 및 해지 페이지를 관리하기 위한 데이터베이스를 구현합니다. 사용자에게 서비스 관련 정보를 쉽게 제공하기 위해 다음과 같은 데이터 구조를 사용합니다:

```dart
// 서비스 URL 매핑
static final Map<String, String> serviceUrls = {
  'netflix': 'https://www.netflix.com',
  '넷플릭스': 'https://www.netflix.com',
  // ...많은 서비스 매핑...
};

// 해지 안내 페이지 URL 목록 (공식 해지 안내 페이지가 있는 서비스들)
static final Map<String, String> cancellationUrls = {
  'netflix': 'https://help.netflix.com/ko/node/407',
  '넷플릭스': 'https://help.netflix.com/ko/node/407',
  // ...많은 서비스 해지 페이지 매핑...
};
```

이 데이터는 `SubscriptionUrlMatcher` 서비스 클래스에서 관리되며, 서비스명으로 URL을 검색하거나 해지 페이지를 안내하는 기능을 제공합니다.

## 8. 테스트 및 품질 보증 계획

### 8.1 테스트 전략

#### 8.1.1 단위 테스트
- **대상**: Provider 클래스, 서비스 클래스, 유틸리티 함수
- **도구**: Flutter Test 프레임워크
- **테스트 사례 예시**:
  ```dart
  void main() {
    late SubscriptionProvider provider;
    late MockHiveBox mockBox;

    setUp(() {
      mockBox = MockHiveBox();
      provider = SubscriptionProvider();
      // 의존성 주입
    });

    test('새 구독 추가 시 목록에 추가되어야 함', () async {
      // Given
      final subscription = SubscriptionModel(
        id: 'test-id',
        serviceName: 'Test Service',
        monthlyCost: 9900,
        billingCycle: '월간',
        nextBillingDate: DateTime.now().add(Duration(days: 30)),
      );
      
      // When
      await provider.addSubscription(
        serviceName: subscription.serviceName,
        monthlyCost: subscription.monthlyCost,
        billingCycle: subscription.billingCycle,
        nextBillingDate: subscription.nextBillingDate,
      );
      
      // Then
      expect(provider.subscriptions.length, 1);
      expect(provider.subscriptions.first.serviceName, 'Test Service');
    });

    test('총 지출 계산이 정확해야 함', () {
      // Given
      final subscription1 = SubscriptionModel(/*...*/);
      final subscription2 = SubscriptionModel(/*...*/);
      provider.subscriptions = [subscription1, subscription2];
      
      // When
      final total = provider.totalMonthlyExpense;
      
      // Then
      expect(total, subscription1.monthlyCost + subscription2.monthlyCost);
    });
  }
  ```

#### 8.1.2 통합 테스트
- **대상**: 화면 간 이동, 데이터 흐름, 상태 관리
- **도구**: Flutter Integration Test
- **테스트 사례 예시**:
  ```dart
  void main() {
    IntegrationTestWidgetsFlutterBinding.ensureInitialized();

    testWidgets('구독 추가 후 메인 화면에 표시되는지 확인', (tester) async {
      // 앱 시작
      app.main();
      await tester.pumpAndSettle();
      
      // 구독 추가 화면으로 이동
      await tester.tap(find.byIcon(Icons.add));
      await tester.pumpAndSettle();
      
      // 구독 정보 입력
      await tester.enterText(
        find.byKey(Key('service_name_field')), 
        'Test Service'
      );
      await tester.enterText(
        find.byKey(Key('monthly_cost_field')), 
        '9900'
      );
      // 나머지 필드 입력...
      
      // 저장 버튼 클릭
      await tester.tap(find.text('저장하기'));
      await tester.pumpAndSettle();
      
      // 메인 화면에 추가된 구독이 표시되는지 확인
      expect(find.text('Test Service'), findsOneWidget);
      expect(find.text('₩9,900'), findsOneWidget);
    });
  }
  ```

#### 8.1.3 UI/UX 테스트
- **대상**: 화면 레이아웃, 애니메이션, 반응성
- **방법**: 다양한 화면 크기 및 기기에서 수동 테스트
- **체크리스트**:
  - [ ] 모든 화면이 다양한 기기 크기에서 적절히 렌더링됨
  - [ ] 애니메이션이 부드럽게 실행됨
  - [ ] 터치 영역이 충분히 큼
  - [ ] 색상 대비가 접근성 기준 충족
  - [ ] 키보드 네비게이션 가능

### 8.2 성능 테스트

#### 8.2.1 메모리 사용량
- **목표**: 앱 사용 중 메모리 누수 없음
- **측정 방법**: DevTools Memory 프로파일러
- **허용 기준**: 장시간 사용 후에도 비정상적 메모리 증가 없음

#### 8.2.2 응답성
- **목표**: 사용자 인터랙션에 빠르게 반응
- **측정 방법**: Flutter Performance 오버레이
- **허용 기준**: 
  - UI 스레드 지연 < 16ms (60fps 유지)
  - 화면 전환 < 300ms
  - 데이터 로딩 시 적절한 로딩 인디케이터 표시

#### 8.2.3 배터리 사용량
- **목표**: 최소한의 배터리 소모
- **측정 방법**: OS 배터리 사용량 통계
- **허용 기준**: 백그라운드에서 최소한의 배터리 사용

### 8.3 호환성 테스트

#### 8.3.1 OS 버전 호환성
- **Android**: API 레벨 21 (Android 5.0) 이상
- **iOS**: iOS 12.0 이상
- **테스트 기기 목록**:
  - Android: Pixel 3, Samsung Galaxy S10, Galaxy Tab S7
  - iOS: iPhone 8, iPhone 12, iPad 6세대

#### 8.3.2 화면 크기 호환성
- **작은 화면**: 4-5인치 스마트폰
- **중간 화면**: 5.5-6.5인치 스마트폰
- **큰 화면**: 태블릿 (7-10인치)

### 8.4 보안 테스트

#### 8.4.1 데이터 저장 보안
- **테스트 항목**: 민감 데이터가 암호화되어 저장되는지 확인
- **방법**: 기기 파일 시스템 검사, 앱 데이터 추출 시도

#### 8.4.2 권한 관리
- **테스트 항목**: 필요한 최소한의 권한만 요청하는지 확인
- **방법**: 앱 매니페스트 검사, 런타임 권한 요청 검증 

## 9. 구현 가이드라인

### 9.1 코딩 표준

#### 9.1.1 명명 규칙
- **클래스명**: PascalCase (예: `SubscriptionModel`, `MainScreen`)
- **변수/메서드명**: camelCase (예: `monthlyCost`, `addSubscription()`)
- **상수명**: SNAKE_CASE (예: `MAX_SUBSCRIPTIONS`)
- **비공개 멤버**: 언더스코어 접두사 (예: `_subscriptions`, `_loadData()`)
- **파일명**: snake_case (예: `subscription_model.dart`, `main_screen.dart`)

#### 9.1.2 코드 구성
- **클래스 멤버 순서**:
  1. 클래스 변수 (static)
  2. 인스턴스 변수
  3. 생성자
  4. getter/setter
  5. 메서드 (private 메서드는 호출되는 public 메서드 직후)
- **주석 형식**:
  - 클래스: 기능 및 역할 설명
  - 메서드: 파라미터, 반환값, 예외 설명
  - 복잡한 로직: 알고리즘 설명
- **import 구성**:
  1. Dart 기본 라이브러리
  2. Flutter 라이브러리
  3. 외부 패키지
  4. 프로젝트 내 패키지 (상대 경로)

#### 9.1.3 SOLID 원칙 적용
- **단일 책임 원칙(SRP)**: 각 클래스는 하나의 책임만 가짐
  - `SubscriptionProvider`는 구독 데이터 CRUD 작업 처리만 담당
  - `NotificationService`는 알림 관리만 담당
- **개방-폐쇄 원칙(OCP)**: 확장에 열려있고 수정에 닫혀있음
  - 새 카테고리 추가 시 기존 코드 수정 없이 가능
- **리스코프 치환 원칙(LSP)**: 하위 클래스는 상위 클래스를 대체 가능
- **인터페이스 분리 원칙(ISP)**: 하나의 일반 인터페이스보다 특정 인터페이스 선호
- **의존성 역전 원칙(DIP)**: 추상화에 의존, 구체화에 의존하지 않음

#### 9.1.4 성능 최적화
- **상태 관리**:
  - `setState()` 대신 `Provider`의 `notifyListeners()` 사용
  - 필요한 부분만 갱신되도록 `Consumer` 위젯 적절히 배치
- **메모리 관리**:
  - 큰 이미지는 캐싱 및 메모리 최적화
  - 리스트는 `ListView.builder`로 화면에 표시된 항목만 빌드
- **애니메이션**:
  - 복잡한 애니메이션은 `AnimationController` 활용
  - `dispose()` 메서드에서 컨트롤러 해제

### 9.2 프로젝트 디렉토리 구조 가이드

```
/lib
 ├── main.dart                     # 앱 진입점
 ├── navigator_key.dart            # 전역 네비게이터 키
 ├── screens/                      # 화면 구성 파일
 │    ├── main_screen.dart         # 메인 화면
 │    ├── add_subscription_screen.dart  # 구독 추가 화면
 │    ├── detail_screen.dart       # 구독 상세 화면
 │    ├── analysis_screen.dart     # 분석 화면
 │    ├── settings_screen.dart     # 설정 화면
 │    ├── sms_scan_screen.dart     # SMS 스캔 화면
 │    ├── splash_screen.dart       # 스플래시 화면
 │    ├── app_lock_screen.dart     # 앱 잠금 화면
 │    └── category_management_screen.dart  # 카테고리 관리 화면
 ├── models/                       # 데이터 모델
 │    ├── subscription_model.dart  # 구독 모델
 │    ├── subscription_model.g.dart  # 자동 생성 코드
 │    ├── category_model.dart      # 카테고리 모델
 │    └── category_model.g.dart    # 자동 생성 코드
 ├── providers/                    # 상태 관리
 │    ├── subscription_provider.dart  # 구독 데이터 관리
 │    ├── category_provider.dart   # 카테고리 데이터 관리
 │    ├── app_lock_provider.dart   # 앱 잠금 관리
 │    ├── notification_provider.dart  # 알림 설정 관리
 │    └── locale_provider.dart     # 다국어 관리
 ├── services/                     # 서비스 클래스
 │    ├── notification_service.dart  # 알림 서비스
 │    ├── sms_scanner.dart         # SMS 스캔 로직
 │    ├── sms_service.dart         # SMS 접근 서비스
 │    ├── exchange_rate_service.dart  # 환율 서비스
 │    ├── currency_util.dart       # 통화 유틸리티
 │    └── subscription_url_matcher.dart  # URL 매칭 서비스
 ├── utils/                        # 유틸리티 함수
 │    ├── format_helper.dart       # 포맷팅 유틸리티
 │    ├── animation_controller_helper.dart  # 애니메이션 유틸리티
 │    └── subscription_category_helper.dart  # 카테고리 유틸리티
 ├── widgets/                      # 재사용 위젯
 │    ├── subscription_card.dart   # 구독 카드 위젯
 │    ├── main_summary_card.dart   # 메인 요약 카드
 │    ├── exchange_rate_widget.dart  # 환율 정보 위젯
 │    ├── website_icon.dart        # 웹사이트 아이콘 위젯
 │    ├── category_header_widget.dart  # 카테고리 헤더
 │    ├── subscription_list_widget.dart  # 구독 목록 위젯
 │    ├── animated_wave_background.dart  # 웨이브 배경 위젯
 │    ├── empty_state_widget.dart  # 빈 상태 위젯
 │    └── skeleton_loading.dart    # 로딩 스켈레톤 위젯
 ├── theme/                        # 테마 관련
 │    ├── app_theme.dart           # 앱 테마 정의
 │    └── app_colors.dart          # 색상 정의
 └── l10n/                         # 다국어 리소스
      ├── app_localizations.dart   # 다국어 클래스
      ├── app_en.arb               # 영어 리소스
      └── app_ko.arb               # 한국어 리소스
```

### 9.3 플랫폼별 구현 가이드

#### 9.3.1 Android 구현 가이드
- **targetSdkVersion**: 33 (Android 13)
- **minSdkVersion**: 21 (Android 5.0)
- **권한 요청**:
  ```xml
  <!-- AndroidManifest.xml -->
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.READ_SMS" />
  <uses-permission android:name="android.permission.RECEIVE_SMS" />
  <uses-permission android:name="android.permission.USE_BIOMETRIC" />
  <uses-permission android:name="android.permission.VIBRATE" />
  <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
  ```
- **알림 채널 설정**:
  - 구독 알림: "subscription_channel"
  - 결제 알림: "payment_channel"
- **네이티브 코드**: 
  - SMS 스캐너 (코틀린): `MainActivity.kt`에 MethodChannel 구현

#### 9.3.2 iOS 구현 가이드
- **iOS 최소 버전**: 12.0
- **권한 요청**:
  ```xml
  <!-- Info.plist -->
  <key>NSFaceIDUsageDescription</key>
  <string>앱 잠금 해제를 위해 Face ID 사용</string>
  <key>NSUserNotificationsUsageDescription</key>
  <string>구독 결제 예정 알림을 위해 사용</string>
  ```
- **알림 설정**:
  - 권한 요청 시 UNUserNotificationCenter 사용
  - PushKit은 사용하지 않음 (로컬 알림만 사용)
- **앱 백그라운드 모드**:
  - "Background Fetch" 활성화

### 9.4 배포 준비 체크리스트

#### 9.4.1 앱 매니페스트 파일
- [ ] 앱 이름 설정
- [ ] 앱 아이콘 설정
- [ ] 버전 코드 및 버전명 설정
- [ ] 필요한 권한만 요청
- [ ] 심사 대비 사유 설명 준비 (SMS 접근 등)

#### 9.4.2 앱 서명 및 인증서
- [ ] Android: Keystore 파일 생성 및 보관
- [ ] iOS: 개발자 계정 및 인증서 설정

#### 9.4.3 스토어 등록 준비
- [ ] 앱 설명 작성
- [ ] 스크린샷 준비 (다양한 기기)
- [ ] 개인정보 처리방침 준비
- [ ] 마케팅 이미지 준비

## 10. 결론 및 요약

### 10.1 주요 기능 요약
SubManager 앱은 사용자가 구독 서비스를 쉽게 관리하고 지출을 최적화할 수 있는 도구입니다. 주요 기능은 다음과 같습니다:

1. **구독 관리**: 구독 서비스의 추가, 수정, 삭제 및 카테고리별 분류
2. **SMS 자동 탐지**: 문자 메시지를 분석하여 구독 서비스 자동 인식
3. **결제일 알림**: 결제 예정일 3일 전 알림으로 예상치 못한 결제 방지
4. **분석 및 리포트**: 월별 지출, 서비스별 비율 등 시각화된 분석 제공
5. **환율 지원**: 원화/달러 간 자동 변환으로 해외 서비스 관리 용이
6. **보안 기능**: 생체 인증을 통한 앱 잠금

### 10.2 개발 철학
SubManager 앱 개발의 핵심 철학은 다음과 같습니다:

1. **사용자 중심**: 사용자의 구독 관리 경험을 최우선으로 고려
2. **오프라인 우선**: 서버 없이 로컬에서 모든 데이터 처리
3. **단순함**: 복잡한 설정 없이 직관적인 UI/UX 제공
4. **개인정보 보호**: 민감한 금융 정보를 안전하게 보호

### 10.3 개발 로드맵
SubManager 앱의 개발 로드맵은 다음과 같습니다:

1. **1단계** (1개월):
   - 기본 구독 관리 기능 구현
   - UI/UX 기본 프레임워크 구축
   - Hive DB 초기 설정

2. **2단계** (1개월):
   - SMS 스캔 기능 추가
   - 알림 시스템 구현
   - 분석 및 리포트 화면 개발

3. **3단계** (2주):
   - 다국어 지원 추가
   - 환율 변환 기능 구현
   - 성능 최적화 및 버그 수정

4. **4단계** (2주):
   - 베타 테스트 진행
   - 사용자 피드백 반영
   - 출시 준비 및 스토어 등록

### 10.4 최종 산출물
이 PRD를 통해 개발될 최종 산출물은 다음과 같습니다:

1. **Flutter 앱**: Android 및 iOS 플랫폼 지원
2. **소스 코드**: 체계적으로 구조화된 확장 가능한 코드베이스
3. **사용자 문서**: 앱 사용 가이드 및 FAQ
4. **개발자 문서**: API 문서 및 아키텍처 설명

이 향상된 PRD 문서는 SubManager 앱을 성공적으로 개발하기 위한 포괄적인 청사진을 제공합니다. 개발자는 이 문서를 통해 앱의 모든 측면을 이해하고, 품질 높은 애플리케이션을 구현할 수 있을 것입니다. 

## 11. 아키텍처 및 유지보수 권장사항

품질 높은 코드베이스를 유지하고 지속적인 개발을 용이하게 하기 위해 다음과 같은 아키텍처 및 코드 구조화 방식을 권장합니다.

### 11.1 Clean Architecture 도입

데이터, 도메인, 프레젠테이션 계층을 명확하게 분리하여 코드의 의존성 방향을 제어하고 테스트 용이성을 높입니다.

현재 9.2절에 설명된 프로젝트 구조에서 발전하여, 더 체계적으로 관심사를 분리하는 구조로 점진적으로 마이그레이션할 수 있습니다. 기존 구성요소는 다음과 같이 매핑됩니다:
- `models/` → `data/models/`
- `services/` → `data/datasources/` 및 `data/repositories/`
- `providers/` → `presentation/providers/`
- `screens/` → `presentation/screens/`
- `widgets/` → `presentation/widgets/`

```
/lib
 ├── data/                              # 데이터 계층
 │    ├── datasources/                  # 데이터 소스 (Hive, API 등)
 │    ├── models/                       # 데이터 모델 (JSON 직렬화 포함)
 │    └── repositories/                 # 리포지토리 구현체
 ├── domain/                            # 도메인 계층
 │    ├── entities/                     # 비즈니스 엔티티
 │    ├── repositories/                 # 리포지토리 인터페이스
 │    └── usecases/                     # 비즈니스 유스케이스
 └── presentation/                      # 표현 계층
      ├── providers/                    # 상태 관리
      ├── screens/                      # 화면 위젯
      └── widgets/                      # 재사용 위젯
```

**장점:**
- 비즈니스 로직이 UI나 데이터 소스 구현에 의존하지 않음
- 각 계층별 단위 테스트 용이
- 확장성 및 유지보수성 향상

### 11.2 단일 책임 원칙 강화

현재 여러 책임을 가진 클래스들을 더 작고 집중된 컴포넌트로 분리합니다.

**기존 코드:**
```dart
// 데이터 관리와 알림 관리가 혼합된 Provider
class SubscriptionProvider extends ChangeNotifier {
  // 구독 CRUD 로직
  Future<void> addSubscription(...) async { ... }
  
  // 알림 관리 로직
  Future<void> _scheduleNotifications() async { ... }
}
```

**개선된 코드:**
```dart
// 데이터 관리만 담당
class SubscriptionRepository {
  Future<void> addSubscription(...) async { ... }
}

// 알림 관리만 담당
class SubscriptionNotificationManager {
  Future<void> scheduleNotification(...) async { ... }
}

// 상태 관리 및 UI 연결만 담당
class SubscriptionProvider extends ChangeNotifier {
  final SubscriptionRepository _repository;
  final SubscriptionNotificationManager _notificationManager;
  
  SubscriptionProvider(this._repository, this._notificationManager);
  // ...
}
```

**장점:**
- 코드 가독성 및 유지보수성 향상
- 단위 테스트 용이성 증가
- 컴포넌트 재사용 가능성 향상

### 11.3 재사용 가능한 UI 컴포넌트

애니메이션, 상태 관리, 화면 전환 등의 공통 기능을 재사용 가능한 위젯으로 추출합니다.

```dart
// 애니메이션된 카드 위젯
class AnimatedCard extends StatelessWidget {
  final Widget child;
  final Duration duration;
  final bool animate;
  
  const AnimatedCard({
    Key? key,
    required this.child,
    this.duration = const Duration(milliseconds: 300),
    this.animate = true,
  }) : super(key: key);
  
  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      tween: Tween(begin: animate ? 0.0 : 1.0, end: 1.0),
      duration: duration,
      curve: Curves.easeOut,
      builder: (context, value, child) {
        return Opacity(
          opacity: value,
          child: Transform.translate(
            offset: Offset(0, 20 * (1 - value)),
            child: child,
          ),
        );
      },
      child: child,
    );
  }
}
```

**장점:**
- 코드 중복 감소
- UI 일관성 향상
- 유지보수 용이성 증가

### 11.4 의존성 주입 패턴 적용

컴포넌트 간 의존성을 명시적으로 관리하여 테스트 용이성을 향상시킵니다.

```dart
// 의존성 주입 컨테이너 설정
final GetIt sl = GetIt.instance;

void setupDependencies() {
  // 서비스 등록
  sl.registerLazySingleton<ExchangeRateService>(() => ExchangeRateServiceImpl());
  sl.registerLazySingleton<NotificationService>(() => NotificationServiceImpl());
  
  // 리포지토리 등록
  sl.registerLazySingleton<SubscriptionRepository>(
    () => SubscriptionRepositoryImpl(sl<HiveDatabase>())
  );
  
  // 상태 관리 Provider 등록
  sl.registerFactory<SubscriptionProvider>(
    () => SubscriptionProvider(
      sl<SubscriptionRepository>(),
      sl<NotificationService>(),
    )
  );
}
```

**장점:**
- 명확한 의존성 관계
- 테스트 시 의존성 모킹 용이
- 컴포넌트 교체 용이성

### 11.5 데이터와 비즈니스 로직 분리

하드코딩된 데이터와 이를 처리하는 로직을 분리합니다.

**기존 코드:**
```dart
class SubscriptionUrlMatcher {
  static final Map<String, String> ottServices = {
    'netflix': 'https://www.netflix.com',
    '넷플릭스': 'https://www.netflix.com',
    // ... 더 많은 서비스 매핑
  };
  
  static String? findMatchingUrl(String serviceName) {
    // 매칭 로직
  }
}
```

**개선된 코드:**
```dart
// 데이터만 포함
class ServiceData {
  static const Map<String, String> ottServices = { ... };
  static const Map<String, String> musicServices = { ... };
  // ...
}

// 로직만 처리
class ServiceMatcher {
  final Map<String, Map<String, String>> allServices;
  
  ServiceMatcher(this.allServices);
  
  String? findMatchingUrl(String serviceName) {
    // 매칭 로직
  }
}
```

**장점:**
- 데이터 업데이트 용이성
- 비즈니스 로직 테스트 용이성
- 관심사 분리로 인한 유지보수성 향상

이러한 아키텍처 및 설계 원칙을 적용함으로써, SubManager 앱은 확장 가능하고 유지보수하기 쉬운 코드베이스를 갖추게 되어 장기적인 개발과 기능 추가가 용이해질 것입니다.

### 11.6 구현 이행 전략

기존 코드베이스에서 제안된 아키텍처로 마이그레이션하기 위한 단계적 접근 방식은 다음과 같습니다:

1. **점진적 리팩토링**:
   - 모든 코드를 한 번에 변경하지 않고, 기능별로 점진적으로 리팩토링
   - 특히 CRUD 작업과 비즈니스 로직이 혼합된 Provider 클래스부터 분리 시작

2. **어댑터 패턴 활용**:
   - 리포지토리 인터페이스를 도입하고 기존 Provider에서 이를 구현하는 방식으로 전환
   - 점진적으로 데이터 액세스 로직을 분리하여 리포지토리 구현체로 이동

3. **테스트 우선 접근방식**:
   - 변경 전후의 동일한 동작을 보장하기 위해 단위 테스트 및 통합 테스트 작성
   - 리팩토링 후 위젯 테스트로 UI 일관성 검증

4. **새 기능에 새 구조 적용**:
   - 신규 기능은 처음부터 제안된 아키텍처로 개발
   - 기존 코드는 점진적으로 새 구조에 맞게 업데이트

이러한 접근 방식을 통해 앱의 기능을 중단 없이 유지하면서도 코드 품질을 지속적으로 개선할 수 있습니다.

해지 안내 페이지 URL 목록은 이제 위의 7.6절 참조