Files
iptv-android-tv-hermes/ARCHITECTURE.md
T

137 lines
4.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture d'iMedia TV+
## Principes fondateurs
- **Clean Architecture** simplifiée : UI → ViewModel → Repository → Data
- **Unidirectional Data Flow (UDF)** : les events montent, les states descendent
- **Single Activity** : une seule `MainActivity`, navigation par écrans Compose
- **Programmation réactive** : Kotlin Coroutines + Flow pour tout ce qui est async
- **Séparation claire** : chaque couche ignore l'existence des couches au-dessus d'elle
## Flux de données complet
```mermaid
sequenceDiagram
actor User as Utilisateur (Télécommande)
participant UI as Compose Screen
participant VM as ViewModel
participant Repo as Repository
participant API as Retrofit / Xtream
participant DB as Room (Cache)
participant Store as EncryptedSP
User->>UI: Appuie sur OK (connexion)
UI->>VM: Event: Login(server, user, pass)
VM->>Repo: authRepository.login(server, user, pass)
Repo->>API: POST /player_api.php?username=...
API-->>Repo: Response { auth: "1", status: "Active" }
Repo->>Store: saveCredentials(encrypted)
Repo-->>VM: Result.Success(userInfo)
VM->>VM: _uiState = UiState.Success
VM-->>UI: StateFlow: HomeScreen data
UI-->>User: Affiche la liste des chaînes
```
## Diagramme de navigation des écrans
```mermaid
stateDiagram-v2
[*] --> SplashScreen
SplashScreen --> LoginScreen : Pas de session
SplashScreen --> HomeScreen : Session valide
LoginScreen --> HomeScreen : Connexion OK
LoginScreen --> LoginScreen : Erreur → reste
HomeScreen --> PlayerScreen : Sélection chaîne Live
HomeScreen --> ProgramGuideScreen : Appui Guide TV
HomeScreen --> ReplayGuideScreen : Appui Replay
HomeScreen --> VodScreen : Appui VOD
HomeScreen --> SeriesScreen : Appui Séries
ProgramGuideScreen --> PlayerScreen : "Regarder"
ReplayGuideScreen --> PlayerScreen : "Revoir"
VodScreen --> PlayerScreen : Lecture film
SeriesScreen --> PlayerScreen : Lecture épisode
PlayerScreen --> HomeScreen : Retour × 2
PlayerScreen --> ProgramGuideScreen : Retour × 1 (si venu du guide)
PlayerScreen --> ReplayGuideScreen : Retour × 1 (si venu du replay)
HomeScreen --> ConfirmQuit : Retour
ConfirmQuit --> [*] : Confirmer
ConfirmQuit --> HomeScreen : Annuler
```
## Organisation des ViewModels
Chaque ViewModel expose :
- Un `StateFlow<UiState<T>>` pour l'état de l'UI
- Des fonctions `onAction(X)` pour les intents utilisateur
- Une vue unique (`viewModelScope`) pour les coroutines
```kotlin
class HomeViewModel(
private val liveRepo: LiveRepository,
private val epgRepo: EpgRepository,
private val favRepo: FavoritesRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UiState<HomeData>>(UiState.Loading)
val uiState: StateFlow<UiState<HomeData>> = _uiState.asStateFlow()
sealed interface Action {
data class SelectCategory(val id: Int) : Action
data class ToggleFavorite(val streamId: Int) : Action
data class PinChannel(val streamId: Int) : Action
object Refresh : Action
}
fun onAction(action: Action) {
// ...
}
}
```
## Injection de dépendances
Utilisation d'un **manual DI** simple via `AppContainer` (pas de framework lourd comme Hilt/Dagger pour un projet de cette taille).
```kotlin
// AppContainer.kt
class AppContainer(context: Context) {
private val secureStorage = SecureStorage(context)
private val okHttpClient = OkHttpClient.Builder()
.addInterceptor(ErrorInterceptor())
.addInterceptor(AuthInterceptor(secureStorage))
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
private val retrofit = Retrofit.Builder()
.baseUrl("http://placeholder") // overridden per-call
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
val xtreamApi = retrofit.create(XtreamApi::class.java)
// Repositories
val authRepository = AuthRepository(xtreamApi, secureStorage)
val liveRepository = LiveRepository(xtreamApi)
val epgRepository = EpgRepository(xtreamApi, EpgDatabase.getInstance(context))
// ...
}
```
## Décisions d'architecture
| Décision | Alternative écartée | Raison |
|----------|-------------------|--------|
| Single Activity | Multi-Activity | Navigation + état simplifiés |
| Manual DI | Hilt / Koin | Pas de overhead pour projet solo |
| Room pour cache EPG | Simple JSON file | Requêtes typées, ACID, migrations |
| EncryptedSharedPreferences | DataStore | Chiffrement natif, API simple |
| ViewModel + StateFlow | MVVM + LiveData | Flow plus puissant, compose-ready |
| sealed interface pour actions | Callbacks | Testable, traçable, immutable |