# Flutter Dating App — Complete Development Prompt

> **Purpose**: This document is a complete specification for an AI or developer to build the Flutter mobile app from scratch. The backend REST API is already built and fully tested. Your job is to build the Flutter client that consumes it.

---

## Table of Contents

1. [Project Overview](#1-project-overview)
2. [Tech Stack & Architecture](#2-tech-stack--architecture)
3. [Project Structure](#3-project-structure)
4. [Core Infrastructure](#4-core-infrastructure)
5. [Data Models](#5-data-models)
6. [API Endpoints Reference](#6-api-endpoints-reference)
7. [Feature Specifications](#7-feature-specifications)
8. [Design System](#8-design-system)
9. [Animation & UX Guidelines](#9-animation--ux-guidelines)
10. [Testing Credentials](#10-testing-credentials)
11. [Error Handling Strategy](#11-error-handling-strategy)
12. [Performance & Optimization](#12-performance--optimization)
13. [Accessibility](#13-accessibility)
14. [Build & Release](#14-build--release)

---

## 1. Project Overview

Build a **production-ready Tinder-like dating app** in Flutter targeting iOS and Android. The app features swipe-based matching, real-time chat with Firebase push notifications, premium subscriptions, and comprehensive profile management.

### Key Features
- Multi-step onboarding & auth (JWT)
- Swipeable discovery cards with physics-based animations
- Mutual matching with celebration animations
- Real-time chat with typing indicators via FCM
- Premium tier: unlimited likes, rewind, boost, see who likes you
- In-app subscription purchase (Apple Pay / Google Pay)
- Report, block, unmatch moderation tools
- Notification management with granular controls
- Offline-first with local caching

### Backend
- **Base URL (Dev)**: `http://localhost/dating_app/ci3_backend_api/v1`
- **Base URL (Prod)**: `https://api.yourdatingapp.com/v1`
- **Auth**: JWT Bearer Token (7-day expiry, 30-day refresh grace period)
- **OpenAPI Spec**: `GET /docs/openapi` (Swagger JSON)

---

## 2. Tech Stack & Architecture

### Framework & Language
- **Flutter** 3.24+ / **Dart** 3.5+
- Minimum targets: iOS 15.0, Android API 24

### Architecture: Clean MVC + GetX

```
Pattern: Clean Architecture with MVC separation
State Management: GetX (GetxController + Obx)
DI: GetX Bindings (lazy put)
Navigation: GetX Named Routes with middleware
HTTP Client: Dio with interceptors
Local Storage: get_storage (general) + flutter_secure_storage (tokens)
```

### Dependencies (pubspec.yaml)

```yaml
dependencies:
  flutter:
    sdk: flutter

  # State Management & DI & Routing
  get: ^4.6.6

  # Networking
  dio: ^5.7.0

  # Storage
  get_storage: ^2.1.1
  flutter_secure_storage: ^9.2.2

  # Firebase
  firebase_core: ^3.8.0
  firebase_messaging: ^15.1.5

  # Image Handling
  cached_network_image: ^3.4.1
  image_picker: ^1.1.2
  image_cropper: ^8.0.2

  # Location
  geolocator: ^13.0.1
  geocoding: ^3.0.0

  # UI Components
  flutter_card_swiper: ^7.0.1
  smooth_page_indicator: ^1.2.0+3
  shimmer: ^3.0.0
  lottie: ^3.1.3
  flutter_animate: ^4.5.0
  confetti: ^0.7.0

  # Forms & Validation
  flutter_form_builder: ^9.4.1
  form_builder_validators: ^11.0.0

  # In-App Purchase
  in_app_purchase: ^3.2.0

  # Utilities
  intl: ^0.19.0
  url_launcher: ^6.3.1
  permission_handler: ^11.3.1
  connectivity_plus: ^6.1.0
  package_info_plus: ^8.1.1
  flutter_local_notifications: ^18.0.1
  path_provider: ^2.1.4
  uuid: ^4.5.1
  logger: ^2.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0
  build_runner: ^2.4.12
  mockito: ^5.4.4
  flutter_launcher_icons: ^0.14.2
  flutter_native_splash: ^2.4.2
```

---

## 3. Project Structure

```
lib/
├── main.dart                          # App entry, init GetStorage, Firebase, DI
├── app.dart                           # GetMaterialApp with theme, routes, bindings
│
├── core/
│   ├── config/
│   │   ├── app_config.dart            # Base URLs, feature flags, timeouts
│   │   ├── api_endpoints.dart         # All endpoint constants
│   │   └── firebase_config.dart       # Firebase options
│   │
│   ├── network/
│   │   ├── api_client.dart            # Dio singleton with base config
│   │   ├── auth_interceptor.dart      # JWT injection, 401 handling, token refresh
│   │   ├── error_interceptor.dart     # Global error parsing → AppException
│   │   ├── connectivity_interceptor.dart  # Offline detection
│   │   └── api_response.dart          # Generic ApiResponse<T> wrapper
│   │
│   ├── storage/
│   │   ├── secure_storage.dart        # JWT token, sensitive data (flutter_secure_storage)
│   │   ├── local_storage.dart         # User prefs, cache (get_storage)
│   │   └── cache_manager.dart         # TTL-based cache for API responses
│   │
│   ├── errors/
│   │   ├── app_exception.dart         # Custom exception hierarchy
│   │   ├── error_handler.dart         # Global error → user-friendly message
│   │   └── failure.dart               # Failure classes for each error type
│   │
│   ├── theme/
│   │   ├── app_theme.dart             # ThemeData (light/dark)
│   │   ├── app_colors.dart            # Color palette constants
│   │   ├── app_text_styles.dart       # Text style presets
│   │   ├── app_dimensions.dart        # Spacing, radius, sizing constants
│   │   └── app_shadows.dart           # Shadow presets
│   │
│   ├── utils/
│   │   ├── validators.dart            # Email, password, age, phone validators
│   │   ├── date_utils.dart            # Relative time, formatting
│   │   ├── image_utils.dart           # Compression, resize before upload
│   │   ├── location_utils.dart        # Permission handling, distance formatting
│   │   ├── debouncer.dart             # Input debouncing
│   │   └── logger.dart                # Configured logger singleton
│   │
│   ├── widgets/
│   │   ├── buttons/
│   │   │   ├── primary_button.dart    # Gradient coral button
│   │   │   ├── secondary_button.dart  # Outlined button
│   │   │   ├── icon_action_button.dart # Circular icon button (like/pass/super)
│   │   │   └── social_button.dart     # Google/Apple sign-in style
│   │   ├── cards/
│   │   │   └── shimmer_card.dart      # Loading placeholder
│   │   ├── inputs/
│   │   │   ├── app_text_field.dart    # Styled text input
│   │   │   ├── app_dropdown.dart      # Styled dropdown
│   │   │   └── range_slider_field.dart # Age/distance range slider
│   │   ├── dialogs/
│   │   │   ├── confirm_dialog.dart    # Yes/No confirmation
│   │   │   ├── match_dialog.dart      # Match celebration overlay
│   │   │   └── report_dialog.dart     # Report reason picker
│   │   ├── loaders/
│   │   │   ├── full_screen_loader.dart
│   │   │   └── skeleton_loader.dart
│   │   ├── empty_state.dart           # No data illustration + message
│   │   ├── error_widget.dart          # Error with retry button
│   │   ├── app_bar_widget.dart        # Custom app bars
│   │   └── photo_grid.dart            # 6-slot photo grid for profile edit
│   │
│   ├── constants/
│   │   ├── app_strings.dart           # All user-facing strings (l10n ready)
│   │   ├── asset_paths.dart           # Lottie, image, icon paths
│   │   └── enums.dart                 # Gender, LookingFor, Drinking, etc.
│   │
│   ├── middleware/
│   │   └── auth_middleware.dart        # GetX route middleware: redirect to login if no token
│   │
│   └── bindings/
│       └── initial_binding.dart        # Register all singletons (ApiClient, Storage, etc.)
│
├── data/
│   ├── models/
│   │   ├── user_model.dart            # User (from login/register)
│   │   ├── user_profile_model.dart    # Full UserProfile with all fields
│   │   ├── user_basic_model.dart      # UserBasic (id, name, photo)
│   │   ├── photo_model.dart           # Photo
│   │   ├── discovery_card_model.dart  # DiscoveryCard
│   │   ├── discovery_preferences_model.dart
│   │   ├── match_model.dart           # Match list item
│   │   ├── match_detail_model.dart    # MatchDetail with full partner profile
│   │   ├── conversation_model.dart    # Conversation
│   │   ├── message_model.dart         # Message
│   │   ├── notification_model.dart    # Notification
│   │   ├── subscription_plan_model.dart
│   │   ├── subscription_record_model.dart
│   │   ├── pagination_model.dart      # Pagination
│   │   └── api_response_model.dart    # Generic {status, message, data}
│   │
│   ├── providers/
│   │   ├── auth_provider.dart         # Auth API calls (register, login, logout, etc.)
│   │   ├── user_provider.dart         # User/profile API calls
│   │   ├── discovery_provider.dart    # Discovery API calls
│   │   ├── match_provider.dart        # Match API calls
│   │   ├── chat_provider.dart         # Chat API calls
│   │   ├── notification_provider.dart # Notification API calls
│   │   ├── report_provider.dart       # Report/block API calls
│   │   ├── subscription_provider.dart # Subscription API calls
│   │   └── settings_provider.dart     # App settings API call
│   │
│   └── repositories/
│       ├── auth_repository.dart       # Auth business logic + caching
│       ├── user_repository.dart
│       ├── discovery_repository.dart
│       ├── match_repository.dart
│       ├── chat_repository.dart
│       ├── notification_repository.dart
│       ├── report_repository.dart
│       ├── subscription_repository.dart
│       └── settings_repository.dart
│
├── modules/
│   ├── splash/
│   │   ├── splash_binding.dart
│   │   ├── splash_controller.dart     # Auto-login check, token refresh
│   │   └── splash_view.dart           # Logo animation → route to login or home
│   │
│   ├── onboarding/
│   │   ├── onboarding_binding.dart
│   │   ├── onboarding_controller.dart
│   │   └── onboarding_view.dart       # Feature highlights carousel
│   │
│   ├── auth/
│   │   ├── auth_binding.dart
│   │   ├── auth_controller.dart       # Login, register, forgot/reset password
│   │   └── views/
│   │       ├── login_view.dart
│   │       ├── register_view.dart     # Multi-step: credentials → basics → photos → bio
│   │       ├── forgot_password_view.dart
│   │       └── reset_password_view.dart
│   │
│   ├── home/
│   │   ├── home_binding.dart
│   │   ├── home_controller.dart       # Bottom nav state, badge counts
│   │   └── home_view.dart             # Scaffold with bottom tab bar (4 tabs)
│   │
│   ├── discovery/
│   │   ├── discovery_binding.dart
│   │   ├── discovery_controller.dart  # Card stack, swipe actions, limits
│   │   └── views/
│   │       ├── discovery_view.dart    # Card stack + action buttons
│   │       ├── profile_detail_view.dart  # Full profile overlay (tap on card)
│   │       └── widgets/
│   │           ├── swipe_card.dart     # Individual profile card
│   │           ├── action_buttons.dart # Rewind, Pass, SuperLike, Like, Boost
│   │           ├── card_overlay.dart   # LIKE/NOPE/SUPER stamp overlay
│   │           └── no_more_cards.dart  # Empty state when cards exhausted
│   │
│   ├── matches/
│   │   ├── matches_binding.dart
│   │   ├── matches_controller.dart    # Fetch matches, separate new vs conversations
│   │   └── views/
│   │       ├── matches_view.dart      # New matches row + conversation list
│   │       └── widgets/
│   │           ├── new_match_avatar.dart  # Circular avatar in horizontal scroll
│   │           └── conversation_tile.dart # Name, photo, last msg, time, unread badge
│   │
│   ├── chat/
│   │   ├── chat_binding.dart
│   │   ├── chat_controller.dart       # Messages, send, typing, polling/push
│   │   └── views/
│   │       ├── chat_view.dart         # Message bubbles + input bar
│   │       └── widgets/
│   │           ├── message_bubble.dart # Sent (right/coral) vs received (left/gray)
│   │           ├── chat_input_bar.dart # Text field + emoji + image + GIF
│   │           ├── typing_indicator.dart # Bouncing dots animation
│   │           └── chat_app_bar.dart   # Partner photo + name + actions menu
│   │
│   ├── profile/
│   │   ├── profile_binding.dart
│   │   ├── profile_controller.dart    # Current user profile, settings nav
│   │   └── views/
│   │       ├── profile_view.dart      # Photo, name, age, settings list
│   │       ├── edit_profile_view.dart  # Photo grid, form fields, interests
│   │       └── widgets/
│   │           ├── profile_photo_grid.dart  # 6 slots, add/remove/reorder
│   │           ├── interest_chips.dart      # Tag selector
│   │           └── profile_section.dart     # Collapsible section card
│   │
│   ├── settings/
│   │   ├── settings_binding.dart
│   │   ├── settings_controller.dart
│   │   └── views/
│   │       ├── discovery_preferences_view.dart  # Age slider, distance, gender
│   │       ├── notification_settings_view.dart  # Toggle switches
│   │       ├── privacy_settings_view.dart       # Hide age, distance, read receipts
│   │       ├── account_settings_view.dart       # Change password, delete account
│   │       └── blocked_users_view.dart          # Blocked list with unblock
│   │
│   ├── subscription/
│   │   ├── subscription_binding.dart
│   │   ├── subscription_controller.dart  # Plans, purchase, restore, status
│   │   └── views/
│   │       ├── premium_view.dart      # Benefits, plan cards, CTA
│   │       └── widgets/
│   │           ├── plan_card.dart      # Price, features, savings badge
│   │           └── feature_row.dart    # Icon + feature text
│   │
│   └── notifications/
│       ├── notification_binding.dart
│       ├── notification_controller.dart
│       └── views/
│           └── notifications_view.dart  # Notification list with icons by type
│
├── routes/
│   ├── app_routes.dart                # Route name constants
│   └── app_pages.dart                 # GetPage list with bindings & middleware
│
└── services/
    ├── auth_service.dart              # Global auth state, token management, auto-logout
    ├── notification_service.dart      # FCM init, foreground/background handling
    ├── location_service.dart          # GPS permission, current position, updates
    ├── connectivity_service.dart      # Online/offline monitoring
    └── in_app_purchase_service.dart   # IAP initialization, purchase flow, receipt

assets/
├── images/
│   ├── logo.png
│   ├── onboarding_1.png
│   ├── onboarding_2.png
│   ├── onboarding_3.png
│   ├── empty_matches.png
│   ├── empty_messages.png
│   └── premium_banner.png
├── animations/
│   ├── splash_logo.json              # Lottie: logo reveal
│   ├── heart_burst.json              # Lottie: match celebration
│   ├── confetti.json                 # Lottie: confetti overlay
│   ├── swipe_hint.json              # Lottie: tutorial swipe gesture
│   ├── loading.json                  # Lottie: loading spinner
│   └── empty_state.json             # Lottie: nothing found
├── icons/
│   ├── ic_like.svg
│   ├── ic_pass.svg
│   ├── ic_superlike.svg
│   ├── ic_rewind.svg
│   ├── ic_boost.svg
│   └── ic_premium.svg
└── fonts/
    ├── Inter-Regular.ttf
    ├── Inter-Medium.ttf
    ├── Inter-SemiBold.ttf
    └── Inter-Bold.ttf
```

---

## 4. Core Infrastructure

### 4.1 API Client (`core/network/api_client.dart`)

```dart
/// Singleton Dio instance. Registered via GetX DI in initial_binding.dart.
///
/// Base configuration:
///   - baseUrl: AppConfig.baseUrl (switches dev/prod via flavor)
///   - connectTimeout: 30 seconds
///   - receiveTimeout: 30 seconds
///   - Content-Type: application/json
///
/// Interceptors (in order):
///   1. ConnectivityInterceptor — rejects if offline
///   2. AuthInterceptor — injects Bearer token, handles 401 (auto-refresh)
///   3. ErrorInterceptor — maps DioException → AppException
///   4. LogInterceptor (debug only)
```

### 4.2 Auth Interceptor (`core/network/auth_interceptor.dart`)

```
Flow:
1. onRequest: Read JWT from SecureStorage → add Authorization header
2. onError (401):
   a. If refresh already in flight → queue this request
   b. Call POST /auth/refresh_token with expired token
   c. If 200 → save new token → retry all queued requests
   d. If 401 → force logout → navigate to /login
```

### 4.3 Secure Storage (`core/storage/secure_storage.dart`)

```
Keys:
  - 'jwt_token'  → JWT string
  - 'user_id'    → int as string
  - 'user_email' → string

Methods:
  - saveToken(String token)
  - getToken() → String?
  - deleteToken()
  - saveUserId(int id)
  - getUserId() → int?
  - clearAll()
```

### 4.4 Auth Service (`services/auth_service.dart`)

```
GetxService (lives for app lifetime)

Properties:
  - Rx<UserModel?> currentUser
  - RxBool isLoggedIn
  - RxBool isPremium

Methods:
  - init() → try token refresh on cold start
  - login(email, password) → save token + user
  - register(email, password, fullName, phone?) → save token + user
  - logout() → clear storage, reset state, navigate /login
  - refreshUser() → GET /users/profile → update currentUser
  - updatePremiumStatus(bool status)
```

### 4.5 Route Middleware (`core/middleware/auth_middleware.dart`)

```
GetMiddleware:
  - redirect: if no token in SecureStorage → redirect to /login
  - Applied to all routes except: /splash, /onboarding, /login, /register,
    /forgot-password, /reset-password
```

---

## 5. Data Models

Every model must have: `factory fromJson(Map<String, dynamic>)`, `Map<String, dynamic> toJson()`, and `copyWith(...)`.

### User

```dart
class UserModel {
  final int id;
  final String email;
  final String fullName;
  final bool isVerified;
  final bool isPremium;
  final String? createdAt;
}
```

### UserBasic

```dart
class UserBasicModel {
  final int id;
  final String fullName;
  final String? photoUrl;
}
```

### UserProfile

```dart
class UserProfileModel {
  final int id;
  final String email;
  final String fullName;
  final String? phone;
  final bool isVerified;
  final bool isPremium;
  final String? premiumUntil;
  final String? bio;
  final String? gender;          // 'male' | 'female' | 'other'
  final int? age;
  final String? jobTitle;
  final String? company;
  final String? education;
  final String? city;
  final String? country;
  final int? height;             // in cm
  final String? lookingFor;      // 'relationship' | 'casual' | 'friendship' | 'both'
  final String? relationshipType;
  final String? drinking;        // 'never' | 'socially' | 'regularly'
  final String? smoking;         // 'never' | 'socially' | 'regularly'
  final String? children;        // 'have' | 'want' | 'dont_want' | 'want_someday'
  final String? religion;
  final List<String> interests;
  final int prefMinAge;
  final int prefMaxAge;
  final int prefDistance;
  final String prefGender;       // 'male' | 'female' | 'both'
  final List<PhotoModel> photos;
}
```

### Photo

```dart
class PhotoModel {
  final int id;
  final String photoUrl;
  final bool isPrimary;
  final String status;           // 'pending' | 'approved' | 'rejected'
  final String? createdAt;
}
```

### DiscoveryCard

```dart
class DiscoveryCardModel {
  final int id;
  final String fullName;
  final int age;
  final String? bio;
  final String? city;
  final double? distanceKm;
  final String? jobTitle;
  final String? education;
  final List<PhotoModel> photos;
  final List<String> interests;

  /// Convenience getter for primary photo URL
  String? get primaryPhotoUrl => photos.firstWhereOrNull((p) => p.isPrimary)?.photoUrl ?? photos.firstOrNull?.photoUrl;
}
```

### DiscoveryPreferences

```dart
class DiscoveryPreferencesModel {
  final int prefMinAge;
  final int prefMaxAge;
  final int prefDistance;
  final String prefGender;
}
```

### Match

```dart
class MatchModel {
  final int matchId;
  final int partnerId;
  final String fullName;
  final String? photoUrl;
  final String matchedAt;
  final String? lastMessage;
  final String? lastMessageAt;
}
```

### MatchDetail

```dart
class MatchDetailModel {
  final int matchId;
  final String matchedAt;
  final UserProfileModel partner;
}
```

### Conversation

```dart
class ConversationModel {
  final int matchId;
  final String matchedAt;
  final String? lastMessage;
  final String? lastMessageAt;
  final int partnerId;
  final String partnerName;
  final String? partnerPhoto;
  final int unreadCount;
}
```

### Message

```dart
class MessageModel {
  final int id;
  final int matchId;
  final int senderId;
  final String message;
  final String messageType;      // 'text' | 'image' | 'gif'
  final bool isRead;
  final String? readAt;
  final bool isDeleted;
  final String createdAt;
  final String? senderName;

  bool isMine(int currentUserId) => senderId == currentUserId;
}
```

### Notification

```dart
class NotificationModel {
  final int id;
  final String type;             // 'match' | 'message' | 'like' | 'superlike' | 'system'
  final String title;
  final String message;
  final Map<String, dynamic>? data;
  final bool isRead;
  final String createdAt;
  final String? senderName;
  final String? senderPhoto;
}
```

### SubscriptionPlan

```dart
class SubscriptionPlanModel {
  final String id;               // 'monthly' | '6months' | '12months'
  final String name;
  final int durationMonths;
  final double price;
  final double? pricePerMonth;
  final String currency;
  final int savingsPercent;
  final String? badge;           // e.g. 'Best Value'
  final List<String> features;
}
```

### SubscriptionRecord

```dart
class SubscriptionRecordModel {
  final int id;
  final String planName;
  final double price;
  final String currency;
  final String startsAt;
  final String expiresAt;
  final bool isActive;
  final String paymentMethod;
  final String transactionId;
  final String createdAt;
}
```

### Pagination

```dart
class PaginationModel {
  final int page;
  final int limit;
  final int total;
  final int pages;

  bool get hasMore => page < pages;
}
```

---

## 6. API Endpoints Reference

### Authentication Header
```
Authorization: Bearer <jwt_token>
```

All responses follow this structure:
```json
{
  "status": true|false,
  "message": "...",
  "data": { ... }
}
```

### 6.1 Auth (No auth required unless noted)

| Method | Endpoint | Body Fields | Auth |
|--------|----------|-------------|------|
| POST | `/auth/register` | `email*`, `password*` (min 6), `full_name*`, `phone` | No |
| POST | `/auth/login` | `email*`, `password*` | No |
| POST | `/auth/logout` | `push_token` | Yes |
| POST | `/auth/forgot_password` | `email*` | No |
| POST | `/auth/reset_password` | `token*`, `password*` (min 6) | No |
| POST | `/auth/refresh_token` | _(empty body, uses Bearer header)_ | Yes |

**Login/Register response data:** `{ token: "...", user: User }`
**Refresh response data:** `{ token: "...", user: User }`

### 6.2 Users (All require auth)

| Method | Endpoint | Body / Params | Notes |
|--------|----------|---------------|-------|
| GET | `/users/profile` | — | Full profile with photos, parsed interests |
| GET | `/users/view/{id}` | Path: `id` | Public profile; 404 if blocked |
| POST | `/users/update_profile` | `full_name`, `phone`, `bio`, `gender`, `age`, `job_title`, `company`, `education`, `city`, `country`, `height`, `looking_for`, `relationship_type`, `drinking`, `smoking`, `children`, `religion`, `interests` (JSON array) | Validates: age 18-120, height 50-300, enums |
| POST | `/users/location` | `latitude*`, `longitude*` | Decimal degrees |
| POST | `/users/upload_photo` | `photo*` (multipart file), `is_primary` ("0"/"1") | Max 6 photos, max 5MB, jpg/png/gif/webp |
| DELETE | `/users/photo/{id}` | Path: `id` | Ownership verified |
| POST | `/users/photo/primary` | `photo_id*` | Unsets previous primary |
| POST | `/users/change_password` | `current_password*`, `new_password*` (min 6) | |
| POST | `/users/verify_email` | `token*` | Sets is_verified=1 |
| DELETE | `/users/account` | `password*` | Soft-delete with password confirmation |

### 6.3 Discovery (All require auth)

| Method | Endpoint | Body / Params | Notes |
|--------|----------|---------------|-------|
| GET | `/discovery/cards` | Query: `limit` (default 10) | Excludes blocked/interacted, prioritizes boosted |
| POST | `/discovery/like` | `target_user_id*` | Daily limit 50 (free). Returns `is_match`, `match_id`, `matched_user` |
| POST | `/discovery/pass` | `target_user_id*` | Counts toward daily limit |
| POST | `/discovery/superlike` | `target_user_id*` | Daily limit: 3 free, 5 premium. Returns `is_match`, `remaining_superlikes` |
| POST | `/discovery/rewind` | _(empty)_ | **Premium only**. 3/day. Returns `rewound_user`, `remaining_rewinds` |
| GET | `/discovery/likes_me` | — | **Premium**: full list. **Free**: count only + `is_premium_feature: true` |
| POST | `/discovery/boost` | _(empty)_ | **Premium only**. 1/day. Returns `expires_at` (30 min) |
| GET | `/discovery/get_preferences` | — | Returns preferences object |
| POST | `/discovery/preferences` | `min_age`, `max_age`, `distance_km`, `gender_preference` | Validates min ≤ max. Maps to `pref_*` in DB |

### 6.4 Matches (All require auth)

| Method | Endpoint | Body / Params | Notes |
|--------|----------|---------------|-------|
| GET | `/matches/list` | Query: `page`, `limit` (max 50) | Returns `new_matches` + `conversations` + `pagination` |
| GET | `/matches/detail/{match_id}` | Path: `match_id` | Full partner profile with photos |
| POST | `/matches/swipe` | `target_user_id*`, `action*` ("like"\|"dislike"\|"superlike") | Delegates to Discovery logic |

### 6.5 Chat (All require auth)

| Method | Endpoint | Body / Params | Notes |
|--------|----------|---------------|-------|
| GET | `/chat/conversations` | — | Active matches only, with unread counts |
| GET | `/chat/messages/{match_id}` | Path: `match_id`, Query: `page`, `limit` (max 100) | Marks as read. 410 if inactive match |
| POST | `/chat/send` | `match_id*`, `message*` (max 5000), `message_type` (text\|image\|gif, default text) | Returns `message_id`, `created_at`. Sends FCM push |
| DELETE | `/chat/message/{id}` | Path: `id` | Soft-delete; sender only |
| POST | `/chat/typing` | `match_id*`, `is_typing` (bool) | Silent FCM data push to partner |
| GET | `/chat/unread` | — | Total unread across all matches |

### 6.6 Notifications (All require auth)

| Method | Endpoint | Body / Params | Notes |
|--------|----------|---------------|-------|
| GET | `/notifications` | Query: `page`, `limit` (default 20) | With sender info, parsed JSON data |
| POST | `/notifications/read` | `notification_id` OR `all: true` | Mark single or mark all read |
| GET | `/notifications/count` | — | Unread count |
| POST | `/notifications/settings` | `push_matches`, `push_messages`, `push_likes`, `email_notifications` (all boolean) | |
| POST | `/notifications/token` | `token*`, `platform*` ("ios"\|"android") | Register FCM device token |

### 6.7 Report & Block (All require auth)

| Method | Endpoint | Body Fields | Notes |
|--------|----------|-------------|-------|
| POST | `/report/user` | `user_id*`, `reason*` (inappropriate\|fake\|harassment\|spam\|other), `description` | Rate-limited |
| POST | `/report/block` | `user_id*` | Also soft-deletes match |
| POST | `/report/unblock` | `user_id*` | |
| GET | `/report/blocked` | — | List with name, photo, blocked_at |
| POST | `/report/unmatch` | `match_id*` | Soft-delete (is_active=0) |

### 6.8 Subscription (All require auth)

| Method | Endpoint | Body Fields | Notes |
|--------|----------|-------------|-------|
| GET | `/subscription/plans` | — | 3 tiers with features |
| GET | `/subscription/status` | — | `is_premium`, active sub, `days_remaining` |
| POST | `/subscription/purchase` | `plan_id*` (monthly\|6months\|12months), `transaction_id*`, `payment_method*` (apple_pay\|google_pay\|stripe\|card), `receipt` | Extends from current expiry if active |
| POST | `/subscription/cancel` | _(empty)_ | Keeps access until expiry |
| POST | `/subscription/restore` | `transaction_id*` | Verifies ownership + expiry |
| GET | `/subscription/history` | — | Full purchase history |

### 6.9 Settings (Requires auth)

| Method | Endpoint | Notes |
|--------|----------|-------|
| GET | `/settings` | Key-value app configuration |

---

## 7. Feature Specifications

### 7.1 Splash Screen
- Play Lottie logo animation (2 seconds)
- Check `SecureStorage` for JWT token
- If token exists → call `POST /auth/refresh_token`
  - Success → navigate to `/home`
  - Failure → clear token → navigate to `/onboarding`
- If no token → navigate to `/onboarding`

### 7.2 Onboarding
- 3 page carousel showcasing features:
  1. "Discover People" — swipe cards illustration
  2. "Match & Connect" — hearts / match illustration
  3. "Chat Instantly" — messaging illustration
- Dots indicator + "Get Started" button on last page
- "Already have an account? Login" link

### 7.3 Registration Flow (Multi-Step)
Use a `PageView` with 4 steps and progress indicator:

**Step 1: Credentials**
- Email (validate format)
- Password (min 6, show/hide toggle)
- Full Name (required)
- Phone (optional)

**Step 2: Profile Basics**
- Gender (male / female / other) — pill selector
- Age (date picker → calculate age, must be ≥ 18)
- Looking For (relationship / casual / friendship / both) — pill selector

**Step 3: Photos**
- 6-slot photo grid (minimum 1 required)
- Tap empty slot → image picker (camera / gallery)
- First photo auto-set as primary
- Crop before upload (1:1 ratio)

**Step 4: Bio & Interests**
- Bio textarea (max 500 chars, char counter)
- Interest chips from predefined list (min 3)
- Optional: Job Title, Company, Education

On "Complete":
1. `POST /auth/register` with email, password, full_name, phone
2. Use returned token for all subsequent calls
3. `POST /users/update_profile` with profile data
4. `POST /users/upload_photo` for each photo
5. `POST /users/location` with current GPS
6. Navigate to `/home`

### 7.4 Login
- Email + Password fields
- "Forgot Password?" link
- Login button → `POST /auth/login`
- On success: save token, navigate `/home`
- On 403: show "Account deactivated" message

### 7.5 Home Screen (Bottom Tab Bar)
4 tabs with icons:

| Tab | Icon | View | Badge |
|-----|------|------|-------|
| Discover | 🔥 fire/cards | DiscoveryView | — |
| Matches | ❤️ heart | MatchesView | New match count |
| Chat | 💬 message | ConversationsView (reuse from matches) | Unread count |
| Profile | 👤 person | ProfileView | — |

Badge counts: poll `GET /chat/unread` and `GET /notifications/count` every 30 seconds, or react to FCM push.

### 7.6 Discovery Screen

**Card Stack:**
- Use `flutter_card_swiper` package
- Stack of 3 visible cards (top = interactive)
- Each card shows:
  - Primary photo (full card background, gradient overlay at bottom)
  - Name, Age — large white text
  - Distance — small text with pin icon
  - Job title / Education — subtitle
  - Tap to expand → ProfileDetailView

**Swipe Actions:**
- Swipe RIGHT → `POST /discovery/like` with `target_user_id`
- Swipe LEFT → `POST /discovery/pass` with `target_user_id`
- Swipe UP → `POST /discovery/superlike` with `target_user_id`
- Show overlay stamp while swiping: green "LIKE", red "NOPE", blue "SUPER LIKE"

**Action Buttons (below card):**
1. ↩️ Rewind (gold, small) — Premium only, `POST /discovery/rewind`
2. ✖️ Pass (red, medium) — Swipe left
3. ⭐ Super Like (blue, small) — Swipe up
4. ❤️ Like (green, large) — Swipe right
5. ⚡ Boost (purple, small) — Premium only, `POST /discovery/boost`

**Match Detected:**
When `is_match: true` in response:
1. Show `MatchDialog` overlay with:
   - "It's a Match!" title with confetti animation (Lottie)
   - Both users' photos side by side
   - "Send Message" button → navigate to chat
   - "Keep Swiping" button → dismiss
2. Play haptic feedback

**Filter Button:**
Top-right icon → opens `DiscoveryPreferencesView`

**Rate Limited (429):**
Show upgrade prompt: "You've used all your likes today. Upgrade to Premium for unlimited!"

### 7.7 Profile Detail View
Full-screen overlay (hero animation from card):
- Swipeable photo gallery with `PageView` + `SmoothPageIndicator`
- Scrollable content below photos:
  - **About**: Bio text
  - **Basics**: Job, Education, Location (icon + text rows)
  - **Lifestyle**: Height, Drinking, Smoking, Children, Religion
  - **Interests**: Wrapped `Chip` widgets
- Bottom bar: Pass / Super Like / Like buttons
- Menu (⋮): Report, Block

### 7.8 Matches Screen
**Top Section: New Matches**
- Horizontal `ListView` of circular avatars
- Fetch from `GET /matches/list` → `new_matches` array
- Tap → navigate to `ProfileDetailView` or `ChatView`

**Bottom Section: Conversations**
- Vertical `ListView` from `conversations` array
- Each tile: avatar, name, last message preview (truncated), relative time, unread badge
- Tap → navigate to `ChatView`
- Pull to refresh

### 7.9 Chat Screen
**App Bar:**
- Back arrow, partner avatar (circular), partner name
- Tap name/avatar → `ProfileDetailView`
- Menu (⋮): Unmatch, Report, Block

**Message List:**
- `ListView.builder` reversed (newest at bottom)
- Messages grouped by date (date separator)
- Sent messages: right-aligned, coral gradient bubble, white text
- Received messages: left-aligned, light gray bubble, dark text
- Show timestamp below each bubble
- Read receipts: double check icon if `is_read`
- Typing indicator at bottom when partner typing (bouncing dots)

**Input Bar:**
- Text field with rounded border
- Send button (coral, disabled when empty)
- Attachment icon → image picker → upload as message_type "image"
- Emoji icon → system emoji keyboard

**Behavior:**
- On open: `GET /chat/messages/{match_id}?page=1&limit=50`
- Scroll up → load more pages (infinite scroll)
- On send: `POST /chat/send` → append to list optimistically
- Typing: debounce 1s → `POST /chat/typing` with `is_typing: true`
- Stop typing: after 3s of no input → `POST /chat/typing` with `is_typing: false`
- Poll for new messages every 5 seconds (or react to FCM push in foreground)
- 410 response → show "Conversation ended" and disable input

### 7.10 Profile Screen
- Large circular photo (primary)
- Name, Age below photo
- "Edit Profile" button
- Settings list sections:
  - **Discovery Preferences** → `DiscoveryPreferencesView`
  - **Notification Settings** → `NotificationSettingsView`
  - **Privacy Settings** → `PrivacySettingsView`
  - **Account Settings** → `AccountSettingsView`
  - **Blocked Users** → `BlockedUsersView`
  - **Premium** → `PremiumView`
  - **Help & Support** → URL launcher
  - **Logout** → confirm dialog → `POST /auth/logout`

### 7.11 Edit Profile Screen
- **Photo Grid**: 2×3 grid of photo slots
  - Filled: show photo with ✕ delete button
  - Empty: show + icon, tap to add
  - First slot highlighted as "Primary"
- **Form Fields** (below photos):
  - Full Name, Bio (multiline, char count), Job Title, Company, Education
  - City, Country (text fields)
  - Height (number input + "cm" suffix)
  - Gender (3 pill buttons)
  - Looking For (4 pill buttons)
  - Drinking, Smoking (3 pill buttons each)
  - Children (4 pill buttons)
  - Religion (text input)
- **Interests Section**:
  - Predefined chip list (tappable to toggle)
  - Categories: Sports, Music, Travel, Food, Art, Technology, Reading, Gaming, Photography, Fitness, Movies, Cooking, Dancing, Yoga, Hiking, etc.
- **Save Button**: sticky at bottom → `POST /users/update_profile`

### 7.12 Discovery Preferences
- **Age Range**: `RangeSlider` (18–100), show values
- **Distance**: `Slider` (1–500 km), show value
- **Gender**: 3 pill buttons (Male / Female / Both)
- Save → `POST /discovery/preferences` with `min_age`, `max_age`, `distance_km`, `gender_preference`

### 7.13 Premium Screen
**Header**: Gradient banner (purple/blue) with premium icon

**Benefits Grid** (2 column):
1. ♾️ Unlimited Likes
2. 👀 See Who Likes You
3. ↩️ Rewind Last Swipe
4. ⚡ Boost Your Profile
5. ⭐ 5 Super Likes/Day
6. 🚫 No Ads

**Plan Cards** (horizontal scroll or stacked):
| Plan | Price | Per Month | Badge |
|------|-------|-----------|-------|
| Monthly | $14.99 | $14.99/mo | — |
| 6 Months | $59.99 | $10.00/mo | Save 33% |
| 12 Months | $89.99 | $7.50/mo | Best Value ⭐ |

**Subscribe Button**: gradient primary, full width
- Triggers `InAppPurchaseService.purchase(planId)`
- On success: `POST /subscription/purchase` with receipt
- Update `AuthService.isPremium`

**Restore Purchases**: text button at bottom
- `POST /subscription/restore`

### 7.14 Notifications Screen
- List of notifications with icons by type:
  - 💕 Match → coral heart icon
  - 💬 Message → chat bubble icon
  - ❤️ Like → heart icon
  - ⭐ Super Like → star icon
  - 📢 System → bell icon
- Each item: icon, title, message, relative time, unread dot
- Tap → navigate based on data:
  - Match → ChatView with match_id
  - Message → ChatView with match_id
  - Like → PremiumView (if not premium) or likes_me
- Mark as read on view (`POST /notifications/read` with `all: true`)

### 7.15 FCM Push Notifications

**Initialization (`services/notification_service.dart`):**
1. `FirebaseMessaging.instance.requestPermission()`
2. Get FCM token → `POST /notifications/token` with `platform` (ios/android)
3. Listen `onTokenRefresh` → re-register

**Foreground Handling:**
- Show local notification banner (flutter_local_notifications)
- Parse payload → update badge counts, refresh relevant data

**Background/Terminated:**
- `onBackgroundMessage` handler registered
- Tap notification → parse data → deep link:
  - `type: "match"` → `/chat/{match_id}`
  - `type: "message"` → `/chat/{match_id}`
  - `type: "like"` → `/discovery`
  - `type: "typing"` → update typing indicator (data-only, no visible notification)

---

## 8. Design System

### 8.1 Colors (`core/theme/app_colors.dart`)

```dart
class AppColors {
  // Primary
  static const primary = Color(0xFFFF6B6B);         // Coral Red
  static const primaryDark = Color(0xFFE85555);
  static const primaryLight = Color(0xFFFF8A8A);

  // Secondary
  static const secondary = Color(0xFF4ECDC4);        // Teal
  static const accent = Color(0xFFFFE66D);            // Yellow (super like)

  // Neutral
  static const background = Color(0xFFFFFFFF);
  static const surface = Color(0xFFF8F9FA);
  static const textPrimary = Color(0xFF2D3436);
  static const textSecondary = Color(0xFF636E72);
  static const textMuted = Color(0xFFB2BEC3);
  static const border = Color(0xFFDFE6E9);

  // Semantic
  static const success = Color(0xFF00B894);
  static const warning = Color(0xFFFDCB6E);
  static const error = Color(0xFFD63031);
  static const info = Color(0xFF0984E3);

  // Gradients
  static const gradientPrimary = LinearGradient(
    colors: [Color(0xFFFF6B6B), Color(0xFFFF8E53)],
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
  );
  static const gradientPremium = LinearGradient(
    colors: [Color(0xFF667EEA), Color(0xFF764BA2)],
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
  );

  // Action button colors
  static const passRed = Color(0xFFFF6B6B);
  static const likeGreen = Color(0xFF00B894);
  static const superlikeBlue = Color(0xFF0984E3);
  static const rewindGold = Color(0xFFFDCB6E);
  static const boostPurple = Color(0xFF764BA2);
}
```

### 8.2 Typography (`core/theme/app_text_styles.dart`)

```dart
/// Font: Inter (all weights)
///
/// Sizes: xs=12, sm=14, base=16, lg=18, xl=20, 2xl=24, 3xl=30, 4xl=36
/// Weights: regular=400, medium=500, semibold=600, bold=700
///
/// Named presets:
///   heading1: 36/bold
///   heading2: 30/bold
///   heading3: 24/semibold
///   title: 20/semibold
///   subtitle: 18/medium
///   body: 16/regular
///   bodyBold: 16/semibold
///   caption: 14/regular
///   small: 12/regular
///   button: 16/semibold
```

### 8.3 Dimensions (`core/theme/app_dimensions.dart`)

```dart
class AppDimensions {
  // Spacing
  static const sp1 = 4.0;
  static const sp2 = 8.0;
  static const sp3 = 12.0;
  static const sp4 = 16.0;
  static const sp5 = 20.0;
  static const sp6 = 24.0;
  static const sp8 = 32.0;
  static const sp10 = 40.0;
  static const sp12 = 48.0;

  // Border Radius
  static const radiusSm = 4.0;
  static const radiusMd = 8.0;
  static const radiusLg = 12.0;
  static const radiusXl = 16.0;
  static const radius2xl = 24.0;
  static const radiusFull = 9999.0;

  // Icon sizes
  static const iconSm = 16.0;
  static const iconMd = 24.0;
  static const iconLg = 32.0;
  static const iconXl = 48.0;

  // Action button sizes
  static const actionButtonSmall = 44.0;
  static const actionButtonMedium = 56.0;
  static const actionButtonLarge = 64.0;

  // Card
  static const cardHeight = 580.0;
  static const cardBorderRadius = 16.0;

  // Avatar
  static const avatarSm = 32.0;
  static const avatarMd = 48.0;
  static const avatarLg = 64.0;
  static const avatarXl = 120.0;
}
```

### 8.4 Shadows (`core/theme/app_shadows.dart`)

```dart
class AppShadows {
  static final sm = BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 2, offset: Offset(0, 1));
  static final md = BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 6, offset: Offset(0, 4));
  static final lg = BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 15, offset: Offset(0, 10));
  static final xl = BoxShadow(color: Colors.black.withOpacity(0.15), blurRadius: 25, offset: Offset(0, 20));
}
```

---

## 9. Animation & UX Guidelines

### Card Swipe Physics
- Use spring physics for card return (when swipe is insufficient)
- Rotate card ±15° proportional to horizontal drag
- Scale card down to 0.9 while swiping
- Background card scales up from 0.92 → 1.0 as top card leaves
- Minimum velocity threshold for swipe completion: 300 px/s

### Match Celebration
1. Both photos fly in from sides → meet in center (300ms, ease-out)
2. Heart burst animation behind photos (Lottie, 600ms)
3. Confetti rain from top (Lottie, loops 2x)
4. "It's a Match!" text scales in with bounce (200ms)
5. Buttons fade in (200ms delay)
6. Haptic: `HapticFeedback.heavyImpact()`

### Like/Pass/SuperLike Buttons
- On press: scale down to 0.85 (100ms)
- On release: scale bounce to 1.1 → 1.0 (200ms, spring)
- Color fills from center outward
- Haptic: `HapticFeedback.lightImpact()`

### Page Transitions
- Default: `GetPageRoute` with `Transition.rightToLeft` (300ms)
- Profile detail from card: `Transition.fadeIn` with hero animation on photo
- Modal sheets: `Transition.downToUp`

### Loading States
- Shimmer placeholders matching the layout shape:
  - Discovery: card-shaped shimmer
  - Chat: message bubble shimmers
  - Matches: avatar circle + text line shimmers
- Never show raw `CircularProgressIndicator` — use branded Lottie loader

### Pull to Refresh
- Custom `RefreshIndicator` with coral color
- Match list, conversation list, notification list

### Typing Indicator
- 3 dots bouncing with staggered timing (each dot delays 150ms)
- Dots use `AppColors.textMuted`
- Container: rounded pill, light gray background

### Message Animations
- New sent message: slide in from right + fade in (200ms)
- New received message: slide in from left + fade in (200ms)

---

## 10. Testing Credentials

### API Test Users

| Email | Password | Gender | Age | Notes |
|-------|----------|--------|-----|-------|
| john@example.com | password | Male | 28 | Has photos & profile |
| jane@example.com | password | Female | 25 | Has photos & profile |
| mike@example.com | password | Male | 32 | |
| sarah@example.com | password | Female | 27 | |
| alex@example.com | password | Male | 30 | |

### Development URLs
```
API Base:    http://localhost/dating_app/ci3_backend_api/v1
Swagger:     http://localhost/dating_app/ci3_backend_api/docs
OpenAPI JSON: http://localhost/dating_app/ci3_backend_api/docs/openapi
Admin Panel: http://localhost/dating_app/ci3_admin_panel/
```

---

## 11. Error Handling Strategy

### AppException Hierarchy

```dart
abstract class AppException implements Exception {
  final String message;
  final int? statusCode;
}

class NetworkException extends AppException {}          // No internet
class TimeoutException extends AppException {}          // Request timeout
class UnauthorizedException extends AppException {}     // 401 → trigger refresh or logout
class ForbiddenException extends AppException {}        // 403 → premium required, banned
class NotFoundException extends AppException {}         // 404
class ConflictException extends AppException {}         // 409 → already exists, duplicate
class ValidationException extends AppException {        // 400, 422
  final Map<String, List<String>>? fieldErrors;
}
class RateLimitException extends AppException {         // 429
  final String? resetAt;
}
class GoneException extends AppException {}             // 410 → conversation ended
class ServerException extends AppException {}           // 500
```

### Error Display Rules
- **Network**: Snackbar "No internet connection" + retry button
- **401**: Silent refresh attempt → if fails, logout + "Session expired"
- **403**: Show premium upgrade dialog or "Account restricted"
- **404**: "Not found" screen or redirect
- **409**: Inline message ("Already interacted", "Email exists")
- **429**: Show limit dialog with upgrade CTA + reset time
- **410**: Disable input, show "Conversation ended"
- **500**: Snackbar "Something went wrong. Please try again."
- **Validation (400)**: Show field-level errors inline below fields

### Snackbar Styling
- Error: Red left border, error icon
- Success: Green left border, check icon
- Info: Blue left border, info icon
- Position: top, floating, 3 second auto-dismiss

---

## 12. Performance & Optimization

### Image Optimization
- Compress images before upload: max 1024px, quality 80%
- Use `CachedNetworkImage` everywhere with shimmer placeholder
- Set `memCacheWidth`/`memCacheHeight` for thumbnail contexts (lists)
- Photo gallery: use `PhotoView` with hero animations

### API Optimization
- Cache `GET /users/profile` locally, refresh on pull-to-refresh
- Cache `GET /discovery/cards` — preload next batch when 3 cards remaining
- Cache `GET /settings` for 1 hour
- Use pagination with `page`/`limit` params everywhere
- Debounce typing indicator calls (1 second)
- Batch badge count polling: single timer polls both `/chat/unread` and `/notifications/count`

### Offline Support
- User profile: cached in GetStorage, show cached data immediately
- Queue failed message sends → retry when online
- Show offline banner when `ConnectivityService` detects no connection
- Discovery cards: show cached cards if available
- Gracefully degrade: disable actions that require network

### Memory Management
- Dispose GetxControllers properly via Bindings
- Use `const` constructors for stateless widgets
- Limit image cache: `PaintingBinding.instance.imageCache.maximumSize = 100`
- Chat: only render visible messages (ListView.builder is already lazy)
- Dispose `ScrollController`, `TextEditingController` in `onClose()`

### Build Optimization
- Use `--split-debug-info` and `--obfuscate` for release
- Tree-shake icons: `uses-material-design: false`, use specific icon fonts
- Enable deferred loading for heavy modules (subscription, settings)

---

## 13. Accessibility

### Requirements
- All images: `Semantics(label: ...)` or `semanticLabel` parameter
- All tappable elements: minimum 44×44 touch target
- Color contrast: WCAG AA minimum (4.5:1 for text)
- Support `MediaQuery.textScaleFactor` — don't use fixed heights for text containers
- `Semantics` tree for card swiping: "Swipe right to like, left to pass"
- Focus order: logical tab order through forms
- Respect `MediaQuery.disableAnimations` — skip Lottie/transitions when enabled
- Screen reader: card reads "Name, Age, from City, Distance away"

---

## 14. Build & Release

### Environment Configuration
Use `--dart-define` for environment switching:

```bash
# Development
flutter run --dart-define=ENV=dev

# Production
flutter build apk --dart-define=ENV=prod
flutter build ios --dart-define=ENV=prod
```

```dart
class AppConfig {
  static String get baseUrl {
    const env = String.fromEnvironment('ENV', defaultValue: 'dev');
    switch (env) {
      case 'prod': return 'https://api.yourdatingapp.com/v1';
      default:     return 'http://localhost/dating_app/ci3_backend_api/v1';
    }
  }
}
```

### Firebase Setup
1. Create Firebase project
2. Add iOS + Android apps
3. Download `google-services.json` (Android) and `GoogleService-Info.plist` (iOS)
4. Enable Cloud Messaging
5. Place service account JSON at `assets/firebase/` for admin SDK (optional, backend only)

### App Icons & Splash
```yaml
flutter_launcher_icons:
  android: true
  ios: true
  image_path: "assets/images/logo.png"
  adaptive_icon_background: "#FF6B6B"
  adaptive_icon_foreground: "assets/images/logo_foreground.png"

flutter_native_splash:
  color: "#FFFFFF"
  image: "assets/images/logo.png"
  android_12:
    icon_background_color: "#FFFFFF"
    image: "assets/images/logo.png"
```

### Release Checklist
- [ ] All API endpoints integrated and tested
- [ ] Error handling for all failure modes
- [ ] Loading states for all async operations
- [ ] Empty states for all lists
- [ ] Pull-to-refresh on all list screens
- [ ] Offline mode graceful degradation
- [ ] FCM push notifications working (foreground + background + terminated)
- [ ] In-app purchase flow complete
- [ ] Deep linking from notifications
- [ ] Analytics events (optional)
- [ ] Crashlytics setup (optional)
- [ ] ProGuard rules (Android release)
- [ ] Info.plist permissions (iOS): camera, photo library, location, notifications
- [ ] AndroidManifest permissions: internet, camera, location, notifications
- [ ] App icons generated for all sizes
- [ ] Splash screen configured
- [ ] Test on physical devices (iOS + Android)
- [ ] Performance profiling (no jank on card swipes)

---

## Quick Reference: GetX Controller Pattern

```dart
class ExampleController extends GetxController {
  final ExampleRepository _repository = Get.find<ExampleRepository>();

  // Observable state
  final items = <ItemModel>[].obs;
  final isLoading = false.obs;
  final error = Rxn<String>();

  @override
  void onInit() {
    super.onInit();
    fetchItems();
  }

  Future<void> fetchItems() async {
    try {
      isLoading.value = true;
      error.value = null;
      final result = await _repository.getItems();
      items.assignAll(result);
    } on AppException catch (e) {
      error.value = e.message;
      ErrorHandler.handle(e);
    } finally {
      isLoading.value = false;
    }
  }

  @override
  void onClose() {
    // Dispose resources
    super.onClose();
  }
}
```

```dart
// In view:
class ExampleView extends GetView<ExampleController> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Obx(() {
        if (controller.isLoading.value) return const ShimmerLoader();
        if (controller.error.value != null) return ErrorWidget(message: controller.error.value!, onRetry: controller.fetchItems);
        if (controller.items.isEmpty) return const EmptyState(message: 'Nothing here yet');
        return ListView.builder(
          itemCount: controller.items.length,
          itemBuilder: (_, i) => ItemTile(item: controller.items[i]),
        );
      }),
    );
  }
}
```

---

> **Note to Developer**: The backend API is fully built and tested. Every endpoint listed above returns the exact response structure documented. Start by setting up the project skeleton, core infrastructure (ApiClient, AuthInterceptor, SecureStorage, AuthService), then build features module by module: Auth → Discovery → Matches → Chat → Profile → Settings → Subscription → Notifications.
