Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ name: Build APK
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:

concurrency:
group: build-apk-${{ github.ref }}
Expand Down Expand Up @@ -39,3 +41,47 @@ jobs:
name: localstream-debug-${{ github.run_number }}
path: native/app/build/outputs/apk/debug/*.apk
retention-days: 14

build-release-apk:
name: Build release APK
if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup JDK 17
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Setup Android SDK
uses: android-actions/setup-android@v3

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

- name: Decode Keystore
if: env.RELEASE_KEYSTORE_BASE64 != ''
run: echo "$RELEASE_KEYSTORE_BASE64" | base64 -d > native/release.keystore
env:
RELEASE_KEYSTORE_BASE64: ${{ secrets.RELEASE_KEYSTORE_BASE64 }}

- name: Assemble release APK
working-directory: native
env:
RELEASE_KEYSTORE_PATH: release.keystore
RELEASE_STORE_PASSWORD: ${{ secrets.RELEASE_STORE_PASSWORD }}
RELEASE_KEY_ALIAS: ${{ secrets.RELEASE_KEY_ALIAS }}
RELEASE_KEY_PASSWORD: ${{ secrets.RELEASE_KEY_PASSWORD }}
run: ./gradlew assembleRelease --no-daemon --stacktrace

- name: Upload release APK
uses: actions/upload-artifact@v4
with:
name: localstream-release-${{ github.run_number }}
path: native/app/build/outputs/apk/release/*.apk
retention-days: 30

3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ android/local.properties
android/app/build/
android/app/src/main/assets/public/
android/.kotlin/
*.jks
release.keystore
native/release.keystore

.gradle-home/
.claude/
Expand Down
3 changes: 0 additions & 3 deletions .idea/.gitignore

This file was deleted.

1,538 changes: 0 additions & 1,538 deletions .idea/caches/deviceStreaming.xml

This file was deleted.

13 changes: 0 additions & 13 deletions .idea/deviceManager.xml

This file was deleted.

9 changes: 0 additions & 9 deletions .idea/localstream.iml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/markdown.xml

This file was deleted.

5 changes: 0 additions & 5 deletions .idea/misc.xml

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

58 changes: 26 additions & 32 deletions native/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
# LocalStream — module natif Android
# LocalStream — Application Android Native

Réécriture **100 % native** (Kotlin + Jetpack Compose) de LocalStream, menée en
parallèle de l'app Capacitor existante (`../android/`). Ce dossier est un projet
Gradle **indépendant** : il ne partage ni configuration ni build avec l'app Capacitor,
qui est remplacée par le module natif en Phase 10.
Application **100 % native** (Kotlin + Jetpack Compose + Material 3) de LocalStream pour Android.

> `applicationId` **final** : `com.localstream.app` (Phase 10 — reprise de l'identifiant afin d'hériter des installations existantes).
> `applicationId` : `com.localstream.app`

## Stack
## Stack technique

| Élément | Choix |
| -------------- | ------------------------------------------------- |
| Langage | Kotlin 2.0 |
| UI | Jetpack Compose + Material 3, thème sombre unique |
| Lecteur Vidéo | ExoPlayer (androidx.media3) |
| Architecture | MVVM simple — `ui/` (screens, composables), `domain/` et `data/` |
| Base de données| Room (androidx.room) |
| Persistance | Jetpack DataStore & EncryptedSharedPreferences |
| Réseau / API | Retrofit, OkHttp, kotlinx.serialization |
| Images | Coil Compose |
| Architecture | MVVM — `ui/` (screens, composables), `domain/` et `data/` |
| DI | Injection manuelle via `AppContainer` |
| Navigation | Navigation Compose |
| minSdk | 24 |
| compileSdk / targetSdk | 36 |

## Builder
## Build & Commandes Gradle

Depuis ce dossier (`native/`) :

Expand All @@ -31,35 +32,28 @@ Depuis ce dossier (`native/`) :
# Tests unitaires JVM
./gradlew testDebugUnitTest

# Analyse statique
# Analyse statique (Detekt)
./gradlew detekt

# Tout (comme la CI)
# Tout exécuter (comme la CI)
./gradlew assembleDebug testDebugUnitTest detekt
```

L'APK est généré dans `app/build/outputs/apk/debug/`.
L'APK debug est généré dans `app/build/outputs/apk/debug/`.

Prérequis : JDK 17 et un Android SDK (via Android Studio ou `ANDROID_HOME`).
Prérequis : JDK 17 et Android SDK (configuré via `ANDROID_HOME` ou Android Studio).

## Navigation
## Navigation & Écrans

`NavHost` (voir `ui/navigation/`) avec les routes : `home`, `search`, `library`,
`playlists`, `history`, `details/{id}`, `player/{id}`, `settings`.

La barre basse (`LocalStreamBottomBar`) reproduit `src/components/BottomNav.tsx`
avec 4 onglets de premier niveau (masquée pendant la lecture vidéo).

## Cartographie des écrans

| Écran natif (route) | Source web (`src/`) | Onglet | Statut |
| ------------------------ | ------------------------------------------- | ------------ | ------------ |
| `home` | `components/screens/HomeScreen.tsx` | Accueil | Phase 7 ✅ |
| `library` | `components/screens/LibraryScreen.tsx` | Bibliothèque | Phase 7 ✅ |
| `playlists` | `components/screens/PlaylistsScreen.tsx` | Listes | Phase 8 ✅ |
| `history` | `components/screens/HistoryScreen.tsx` | Historique | Phase 8 ✅ |
| `search` | `components/screens/SearchScreen.tsx` | — | Phase 7 ✅ |
| `settings` | `components/SettingsModal.tsx` | — | Phase 8 ✅ |
| `details/{id}` | vue Héro / détails | — | Phase 8 ✅ |
| `player/{id}` | `components/WebPlayer.tsx` / `PlayerActivity`| — | Phase 9 ✅ |
Le routage est géré par `NavHost` (`ui/navigation/`) avec une barre de navigation (`LocalStreamBottomBar`) donnant accès aux écrans principaux :

| Route | Composable / Écran natif | Onglet / Description |
| ---------------- | ---------------------------------------------- | --------------------------------- |
| `home` | `com.localstream.app.ui.screens.HomeScreen` | Accueil (Hero, reprises, récents) |
| `library` | `com.localstream.app.ui.screens.LibraryScreen` | Bibliothèque (Films / Séries) |
| `playlists` | `com.localstream.app.ui.screens.PlaylistsScreen`| Listes de lecture utilisateur |
| `history` | `com.localstream.app.ui.screens.HistoryScreen` | Historique de visionnage |
| `search` | `com.localstream.app.ui.screens.SearchScreen` | Recherche locale et filtres |
| `settings` | `com.localstream.app.ui.screens.SettingsScreen` | Paramètres & clés API |
| `details/{id}` | `com.localstream.app.ui.screens.DetailsScreen` | Fiche détaillée film / série |
| `player/{id}` | `com.localstream.app.ui.player.PlayerScreen` | Lecteur vidéo ExoPlayer plein écran |
29 changes: 29 additions & 0 deletions native/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ android {
keyAlias = "androiddebugkey"
keyPassword = "android"
}
create("release") {
val releaseStoreFilePath = System.getenv("RELEASE_KEYSTORE_PATH")
?: System.getenv("KEYSTORE_PATH")
?: "${rootDir}/release.keystore"
val releaseStoreFile = file(releaseStoreFilePath)

if (releaseStoreFile.exists()) {
storeFile = releaseStoreFile
storePassword = System.getenv("RELEASE_STORE_PASSWORD") ?: System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("RELEASE_KEY_ALIAS") ?: System.getenv("KEY_ALIAS")
keyPassword = System.getenv("RELEASE_KEY_PASSWORD") ?: System.getenv("KEY_PASSWORD")
} else {
// Fallback vers le debug keystore si aucune clé release n'est fournie (permet le build local)
storeFile = file("${rootDir}/debug.keystore")
storePassword = "android"
keyAlias = "androiddebugkey"
keyPassword = "android"
}
}
}

defaultConfig {
Expand All @@ -35,6 +54,7 @@ android {
release {
isMinifyEnabled = true
isShrinkResources = true
signingConfig = signingConfigs.getByName("release")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",
Expand All @@ -51,11 +71,20 @@ android {
jvmTarget = "17"
}

sourceSets {
getByName("androidTest").assets.srcDirs("$projectDir/schemas")
getByName("test").assets.srcDirs("$projectDir/schemas")
}

buildFeatures {
compose = true
}
}

ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}

detekt {
// Analyse statique du code Kotlin. La config de base est enrichie par les
// réglages adaptés à Compose dans config/detekt/detekt.yml.
Expand Down
Loading
Loading