# CEPI LMS — Mobile App (Flutter)

> Planning document for the Flutter Android/iOS app. No mobile codebase exists yet.
> Last updated: 2026-06-02.

---

## 1. Mobile App Scope

The CEPI mobile app is a **student-facing** native app for Android and iOS. It mirrors the core learning experience of the web platform.

### In Scope (MVP)
- Student registration and login (email/password + Google Sign-In)
- Email verification
- Course catalog browsing
- Course detail view (modules, lessons, pricing)
- Manual bank transfer enrollment
- Free course enrollment
- Lesson player (Bunny video via WebView or HLS)
- Lesson progress tracking (sync to backend)
- Student dashboard (active courses, progress, stats)
- Pending payment status view
- Profile management

### Out of Scope (MVP — Future Phases)
- Instructor or admin panels
- JazzCash/Stripe in-app payment (requires gateway SDK)
- Offline content download
- Quiz taking
- Course reviews
- Push notifications (planned Phase B)
- Certificate download

---

## 2. Supported Platforms

- Android: API level 21+ (Android 5.0+)
- iOS: iOS 13+
- Distribution: Google Play Store + Apple App Store

---

## 3. Recommended Flutter Architecture

### State Management: Riverpod 2.x
- `AsyncNotifier` for server-driven state
- `Provider` for global services (AuthService, ApiClient)
- No business logic in widgets

### Navigation: Go Router
- Declarative routing with guards (redirect unauthenticated users to login)
- Deep-linking ready

### HTTP: Dio + Retrofit (optional)
- Dio for HTTP with interceptors (auth token injection, error handling)
- Retrofit for typed API contracts (recommended)

### Local Storage
- `flutter_secure_storage` for Sanctum token storage
- `shared_preferences` for lightweight prefs (theme, last-viewed course)
- SQLite (`sqflite`) for progress caching (Phase B)

### Video Playback
- **Option A (Recommended MVP)**: `flutter_webview_plugin` or `webview_flutter` rendering Bunny's signed embed iframe
- **Option B (Phase B)**: `flutter_vlc_player` or `better_player` for native HLS playback using the signed HLS URL from `BunnyVideoService::generateSignedUrl()`

### Architecture Pattern: Clean Architecture (simplified)
```
lib/
├── core/
│   ├── api/           # Dio client, interceptors, API base URL
│   ├── auth/          # AuthService, token storage
│   ├── error/         # Failure types, error handling
│   └── routing/       # GoRouter config + guards
├── features/
│   ├── auth/          # login, register, verify email screens
│   ├── courses/       # catalog, detail, module/lesson list
│   ├── player/        # lesson player + progress tracking
│   ├── dashboard/     # student home screen
│   ├── payments/      # bank transfer, payment history
│   └── profile/       # profile edit, password change
├── shared/
│   ├── widgets/       # common UI components
│   ├── models/        # data models (Freezed recommended)
│   └── utils/         # date formatting, validators
└── main.dart
```

---

## 4. Auth Flow

```mermaid
sequenceDiagram
    participant App
    participant API as Laravel API
    App->>API: POST /api/v1/auth/login { email, password }
    API-->>App: { token, user }
    App->>App: Store token in flutter_secure_storage
    App->>API: POST /api/v1/auth/verify-email { code }
    API-->>App: { verified: true }
    App->>App: Navigate to Dashboard
```

- Token stored in `flutter_secure_storage` (not SharedPreferences — never plain storage)
- All API requests inject `Authorization: Bearer {token}` header via Dio interceptor
- On 401 response: clear token, redirect to login
- Google Sign-In: use `google_sign_in` package → send Google ID token to new API endpoint `POST /api/v1/auth/google` → backend verifies and returns Sanctum token

---

## 5. Student Dashboard Flow

1. App launches → check token in secure storage
2. If token exists → `GET /api/v1/auth/me` to validate and get user
3. If valid → load Dashboard (`GET /api/v1/dashboard`)
4. Display: active courses grid, recent progress, stats, pending payments

---

## 6. Course Browsing Flow

1. `GET /api/v1/courses` with optional filters (category, level, search)
2. Paginated grid/list with pull-to-refresh
3. Tap course → `GET /api/v1/courses/{slug}`
4. Show description, modules (collapsed), instructor, price
5. CTA: "Enroll Free" / "Pay via Bank Transfer" / "Continue Learning" (if enrolled)

---

## 7. Enrollment / Payment Flow

### Free Course
1. Tap "Enroll Free"
2. `POST /api/v1/enrollments/free { course_id }`
3. Navigate to lesson hub

### Bank Transfer
1. Tap "Pay via Bank Transfer"
2. `GET /api/v1/payments/bank-details`
3. Show bank account details
4. Student fills in transfer reference + payer info
5. `POST /api/v1/payments/bank-transfer`
6. Show "Under Review" state
7. When admin approves → enrollment activated (student sees in dashboard)

---

## 8. Lesson Player Flow

```mermaid
sequenceDiagram
    participant App
    participant API as Laravel API
    participant Bunny as Bunny CDN
    App->>API: GET /api/v1/courses/{slug}/lessons/{id}
    API->>Bunny: Generate signed embed URL (server-side)
    API-->>App: { bunny_embed_url, progress }
    App->>App: Load WebView with bunny_embed_url
    App->>App: Restore position from progress.last_position_seconds
    loop Every 30s
        App->>API: POST /api/v1/lessons/{id}/progress { watched_seconds, position }
    end
    App->>API: POST /api/v1/lessons/{id}/progress { is_completed: true }
```

- Progress synced every 30 seconds during playback and on pause/close
- Resume playback using `last_position_seconds` from API response
- `bunny_embed_url` TTL is 300s — app must fetch fresh URL per session, not cache it

---

## 9. Bunny Video Playback Considerations

- The signed embed URL is a Bunny Player iframe URL — best loaded in `webview_flutter`
- For native HLS playback (better UX, Phase B):
  - Call new API endpoint `GET /api/v1/lessons/{id}/stream-token` which returns the signed HLS `.m3u8` URL
  - Feed to `flutter_vlc_player` or `better_player`
  - HLS URL also has short TTL — must be refreshed before expiry
- Never pass raw Bunny credentials or `token_key` to the mobile client

---

## 10. Offline / Low-Connectivity Behavior (MVP)

- Cache course catalog in memory for duration of app session
- Show stale data from last successful fetch when offline (using Riverpod's `previous` state)
- Progress updates queue locally and retry on reconnect (using a simple retry interceptor in Dio)
- No offline video download in MVP

---

## 11. Push Notification Plan (Phase B)

- Backend: integrate **Firebase Cloud Messaging (FCM)**
- New table: `device_push_tokens` (user_id, fcm_token, platform, created_at)
- New API endpoint: `POST /api/v1/notifications/register-token`
- Notification triggers:
  - Payment approved → "Your enrollment in [Course] is now active"
  - New lesson published in enrolled course
  - Admin broadcast announcements
- Flutter: use `firebase_messaging` package
- Handle foreground, background, and terminated state notifications

---

## 12. API Dependency List

The following API endpoints must be built before Flutter development can start:

| Priority | Endpoint | Required For |
|---|---|---|
| 🔴 Critical | POST /api/v1/auth/login | Login screen |
| 🔴 Critical | POST /api/v1/auth/register | Registration |
| 🔴 Critical | POST /api/v1/auth/logout | Session management |
| 🔴 Critical | GET /api/v1/auth/me | Token validation |
| 🔴 Critical | GET /api/v1/courses | Course catalog |
| 🔴 Critical | GET /api/v1/courses/{slug} | Course detail |
| 🔴 Critical | GET /api/v1/enrollments | Student dashboard |
| 🔴 Critical | GET /api/v1/courses/{slug}/lessons/{id} | Lesson player |
| 🔴 Critical | POST /api/v1/lessons/{id}/progress | Progress sync |
| 🔴 Critical | GET /api/v1/dashboard | Dashboard stats |
| 🔴 Critical | GET /api/v1/payments/bank-details | Checkout |
| 🔴 Critical | POST /api/v1/payments/bank-transfer | Checkout |
| 🟠 High | POST /api/v1/enrollments/free | Free course |
| 🟠 High | GET /api/v1/payments | Payment history |
| 🟠 High | PUT /api/v1/profile | Profile edit |
| 🟠 High | POST /api/v1/auth/verify-email | Email verification |
| 🟠 High | POST /api/v1/auth/resend-verification | Re-verify |
| 🟡 Medium | POST /api/v1/auth/google | Google Sign-In |
| 🟡 Medium | POST /api/v1/profile/avatar | Avatar upload |

---

## 13. Mobile Security Rules

- Store tokens in `flutter_secure_storage` only (not SharedPreferences or plaintext)
- Never log token or user credentials in release builds
- Certificate pinning (Phase B) to prevent MITM
- Disable screenshot on sensitive screens (payment, lesson player) using `FLAG_SECURE` on Android
- Obfuscate release build: `flutter build apk --obfuscate --split-debug-info=...`
- No hardcoded API URLs — use build-time config (`--dart-define` or `flutter_dotenv`)
- Bunny embed URL must never be stored locally or logged

---

## 14. App Store Readiness Checklist

### Android (Google Play)
- [ ] Target SDK 34+
- [ ] Privacy policy URL
- [ ] App signing configured (upload keystore)
- [ ] Content rating questionnaire completed
- [ ] `flutter build appbundle` (not APK for Play Store)

### iOS (Apple App Store)
- [ ] Apple Developer account enrolled
- [ ] Bundle ID registered in App Store Connect
- [ ] Provisioning profile configured
- [ ] Privacy policy URL
- [ ] Required device capabilities set in Info.plist
- [ ] `flutter build ipa` tested on real device

### Both Platforms
- [ ] App icon (1024×1024)
- [ ] Splash screen
- [ ] Screenshots for store listing
- [ ] Short and long descriptions
- [ ] No use of private APIs

---

## 15. Suggested Flutter Packages

| Package | Purpose |
|---|---|
| `flutter_riverpod` | State management |
| `go_router` | Navigation |
| `dio` | HTTP client |
| `flutter_secure_storage` | Token storage |
| `webview_flutter` | Bunny video embed |
| `google_sign_in` | Google OAuth |
| `image_picker` | Avatar upload |
| `cached_network_image` | Course thumbnails |
| `shimmer` | Loading skeletons |
| `intl` | Date/currency formatting |
| `freezed` | Immutable data models |
| `json_annotation` | JSON serialization |
| `firebase_messaging` | Push notifications (Phase B) |
| `flutter_local_notifications` | Local notifications (Phase B) |
| `better_player` | Native HLS playback (Phase B) |
