Architecture
The three layers
Section titled “The three layers”The app follows clean architecture with three inner layers wired together via GetIt:
| Layer | Path | What lives here |
|---|---|---|
| Domain | lib/domain/ |
Pure-Dart entities, abstract repository contracts, use cases, service interfaces. No Flutter imports. |
| Data | lib/data/ |
Datasources (HTTP via DioApiProvider, local via Hive / SharedPreferences), repository implementations, DTO ↔ entity mappers. |
| Presentation | lib/presentation/ |
BLoC/Cubit classes, pages, widgets. Each feature folder in pages/ has a mirrored entry in bloc/. |
| Core | lib/core/ |
Shared constants, enums, error types, networking base classes, utilities. |
Dependencies point one way: presentation → domain ← data. Domain knows nothing about the layers around it.
Key conventions
Section titled “Key conventions”dartzEitherfor repository return types — success/failure without exceptions.- Use cases are thin: one public
call()method, delegating to a repository. - Models are hand-written
fromJson/toEntity()classes, not generated. Only routes (AppRouter) and Hive adapters use codegen.
Dependency injection
Section titled “Dependency injection”lib/injection_container.dart bootstraps GetIt; init() is called from
main(). Every datasource → repository → use case → bloc registration lives
in this one file. When you introduce a feature, add its registrations
here. test/injection_container_test.dart fails if the graph can’t
resolve.
Navigation
Section titled “Navigation”AppRouter in lib/core/router/app_router.dart defines all routes with
AutoRoute. After any route change,
regenerate app_router.gr.dart:
dart run build_runner build --delete-conflicting-outputsDeep links are resolved by resolveDeepLink
(lib/core/utils/deep_link_path.dart) and dispatched from
lib/domain/services/deeplink_services.dart.
Global state via ValueNotifier
Section titled “Global state via ValueNotifier”lib/core/constant/const.dart holds app-wide ValueNotifier singletons —
current_user, internet_available, country, bottom_bar_index and
friends. These are accessed directly, not through DI or BLoCs. It’s a
deliberate trade-off: simple, but any test touching them needs
tearDown(resetGlobals).
The player stack
Section titled “The player stack”The video player surface has three layers:
ottstack_player— providesOSPlayerController/OSPlayerViewand theOSMediaModelentry type.kvideo— the native layer underneath: a Pigeon bridge to ExoPlayer/Media3 (Android) and AVPlayer (iOS).lib/presentation/widgets/player/— app-level widgets (player_media_builder.dart,player_controls.dart,player_gesture_handler.dart,trailer_controls.dart, thecast/andairplay/subtrees) composing the above.
A single OSPlayerController is constructed in main.dart and provided
app-wide, so the content-page trailer and the full-screen player share one
controller.
Both ottstack_player and kvideo are absolute path dependencies (see
Environment setup).
Offline persistence
Section titled “Offline persistence”Hive is the offline store: downloads (CacheContentEntity) and the
notification queue (FirebaseNotification). Register adapters in main()
before opening boxes.