From 3f82e2c629eb1b47381fe613be2ea3bd56b05c53 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:40:31 +0200 Subject: [PATCH 01/12] Add MediaRepository dependency to LauncherContinueWatchingRepository --- .../tv/data/repository/LauncherContinueWatchingRepository.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt index 9ae20af3a..8607d5ac2 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt @@ -18,6 +18,7 @@ import com.arflix.tv.MainActivity import com.arflix.tv.R import com.arflix.tv.data.model.MediaType import com.arflix.tv.data.model.SportsAddonCapabilities +import com.arflix.tv.data.repository.MediaRepository import com.arflix.tv.navigation.Screen import com.arflix.tv.util.AppLogger import com.arflix.tv.util.Constants @@ -42,7 +43,8 @@ class LauncherContinueWatchingRepository @Inject constructor( private val profileManager: ProfileManager, private val traktRepository: TraktRepository, private val watchHistoryRepository: WatchHistoryRepository, - private val streamRepository: StreamRepository + private val streamRepository: StreamRepository, + private val mediaRepository: MediaRepository ) { companion object { private const val TAG = "LauncherCW" From f1fa10bdaaa45db92c102f06052ca2aa52968d29 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Mon, 3 Aug 2026 11:21:37 +0200 Subject: [PATCH 02/12] Enhance continue watching item title resolution Refactor continue watching logic to improve title resolution and episode title handling. --- .../LauncherContinueWatchingRepository.kt | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt index 8607d5ac2..2cbcf5747 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt @@ -118,14 +118,33 @@ class LauncherContinueWatchingRepository @Inject constructor( return historyFallback .sortedByDescending { it.updated_at ?: it.paused_at.orEmpty() } .map { entry -> + val mediaType = if (entry.media_type == "tv") MediaType.TV else MediaType.MOVIE + + val localizedTitle = runCatching { + if (mediaType == MediaType.TV) { + mediaRepository.getTvDetails(entry.show_tmdb_id)?.title + } else { + mediaRepository.getMovieDetails(entry.show_tmdb_id)?.title + } + }.getOrNull()?.takeIf { it.isNotBlank() } ?: entry.title.orEmpty() + + val resolvedEpisodeTitle = runCatching { + if (mediaType == MediaType.TV && entry.season != null && entry.episode != null) { + val episodes = mediaRepository.getSeasonEpisodes(entry.show_tmdb_id, entry.season) + episodes?.firstOrNull { it.episodeNumber == entry.episode }?.name + } else { + null + } + }.getOrNull()?.takeIf { it.isNotBlank() } ?: entry.episode_title + ContinueWatchingItem( id = entry.show_tmdb_id, - title = entry.title.orEmpty(), - mediaType = if (entry.media_type == "tv") MediaType.TV else MediaType.MOVIE, + title = localizedTitle, + mediaType = mediaType, progress = (entry.progress * 100f).toInt().coerceIn(0, 99), season = entry.season, episode = entry.episode, - episodeTitle = entry.episode_title, + episodeTitle = resolvedEpisodeTitle, posterPath = entry.poster_path, backdropPath = entry.backdrop_path, resumePositionSeconds = entry.position_seconds, @@ -144,8 +163,6 @@ class LauncherContinueWatchingRepository @Inject constructor( } .distinctBy { "${it.mediaType}:${it.id}:${it.season ?: -1}:${it.episode ?: -1}" } .take(Constants.MAX_CONTINUE_WATCHING) - } - @RequiresApi(Build.VERSION_CODES.O) private fun syncPublishedRows(items: List) { From 0eea343b45470ee3466f11db09585553e9e0b26b Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:15:19 +0200 Subject: [PATCH 03/12] Compilar APK --- .github/workflows/android.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000..6742a725d --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,31 @@ +name: Compilar APK + +on: + workflow_dispatch: # Esto es la magia que crea el botón para ejecutarlo manualmente + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Clonar el código + uses: actions/checkout@v4 + + - name: Configurar Java 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + + - name: Dar permisos de ejecución a Gradle + run: chmod +x gradlew + + - name: Compilar la App (Modo Debug) + run: ./gradlew assembleDebug + + - name: Guardar el APK para descargar + uses: actions/upload-artifact@v4 + with: + name: ARVIO-APK + path: app/build/outputs/apk/debug/*.apk From 022c95b00d089be0e1fae6665becfa94f099ddb9 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:23:26 +0200 Subject: [PATCH 04/12] Refactor loadPublisherItems for better item handling Refactor loadPublisherItems to streamline item selection and localization. --- .../LauncherContinueWatchingRepository.kt | 110 ++++++++++-------- 1 file changed, 59 insertions(+), 51 deletions(-) diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt index 2cbcf5747..d655b5b60 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt @@ -101,6 +101,7 @@ class LauncherContinueWatchingRepository @Inject constructor( private suspend fun loadPublisherItems(): List { val installedAddons = streamRepository.installedAddons.first() val primaryItems = runCatching { traktRepository.getContinueWatching() }.getOrDefault(emptyList()) + val filteredPrimary = primaryItems.filterNot { item -> SportsAddonCapabilities.isLiveStreamOrSportsItem( mediaType = item.mediaType, @@ -110,60 +111,67 @@ class LauncherContinueWatchingRepository @Inject constructor( addons = installedAddons ) } - if (filteredPrimary.isNotEmpty()) { - return filteredPrimary.take(Constants.MAX_CONTINUE_WATCHING) - } - val historyFallback = runCatching { watchHistoryRepository.getContinueWatching() }.getOrDefault(emptyList()) - return historyFallback - .sortedByDescending { it.updated_at ?: it.paused_at.orEmpty() } - .map { entry -> - val mediaType = if (entry.media_type == "tv") MediaType.TV else MediaType.MOVIE - - val localizedTitle = runCatching { - if (mediaType == MediaType.TV) { - mediaRepository.getTvDetails(entry.show_tmdb_id)?.title - } else { - mediaRepository.getMovieDetails(entry.show_tmdb_id)?.title - } - }.getOrNull()?.takeIf { it.isNotBlank() } ?: entry.title.orEmpty() + val selectedItems = if (filteredPrimary.isNotEmpty()) { + filteredPrimary.take(Constants.MAX_CONTINUE_WATCHING) + } else { + val historyFallback = runCatching { watchHistoryRepository.getContinueWatching() }.getOrDefault(emptyList()) + historyFallback + .sortedByDescending { it.updated_at ?: it.paused_at.orEmpty() } + .map { entry -> + ContinueWatchingItem( + id = entry.show_tmdb_id, + title = entry.title.orEmpty(), + mediaType = if (entry.media_type == "tv") MediaType.TV else MediaType.MOVIE, + progress = (entry.progress * 100f).toInt().coerceIn(0, 99), + season = entry.season, + episode = entry.episode, + episodeTitle = entry.episode_title, + posterPath = entry.poster_path, + backdropPath = entry.backdrop_path, + resumePositionSeconds = entry.position_seconds, + durationSeconds = entry.duration_seconds, + streamAddonId = entry.stream_addon_id + ) + } + .filterNot { item -> + SportsAddonCapabilities.isLiveStreamOrSportsItem( + mediaType = item.mediaType, + id = item.id, + streamAddonId = item.streamAddonId, + title = item.title, + addons = installedAddons + ) + } + .distinctBy { "${it.mediaType}:${it.id}:${it.season ?: -1}:${it.episode ?: -1}" } + .take(Constants.MAX_CONTINUE_WATCHING) + } - val resolvedEpisodeTitle = runCatching { - if (mediaType == MediaType.TV && entry.season != null && entry.episode != null) { - val episodes = mediaRepository.getSeasonEpisodes(entry.show_tmdb_id, entry.season) - episodes?.firstOrNull { it.episodeNumber == entry.episode }?.name - } else { - null - } - }.getOrNull()?.takeIf { it.isNotBlank() } ?: entry.episode_title - - ContinueWatchingItem( - id = entry.show_tmdb_id, - title = localizedTitle, - mediaType = mediaType, - progress = (entry.progress * 100f).toInt().coerceIn(0, 99), - season = entry.season, - episode = entry.episode, - episodeTitle = resolvedEpisodeTitle, - posterPath = entry.poster_path, - backdropPath = entry.backdrop_path, - resumePositionSeconds = entry.position_seconds, - durationSeconds = entry.duration_seconds, - streamAddonId = entry.stream_addon_id - ) - } - .filterNot { item -> - SportsAddonCapabilities.isLiveStreamOrSportsItem( - mediaType = item.mediaType, - id = item.id, - streamAddonId = item.streamAddonId, - title = item.title, - addons = installedAddons - ) - } - .distinctBy { "${it.mediaType}:${it.id}:${it.season ?: -1}:${it.episode ?: -1}" } - .take(Constants.MAX_CONTINUE_WATCHING) + return selectedItems.map { item -> + val localizedTitle = runCatching { + if (item.mediaType == MediaType.TV) { + mediaRepository.getTvDetails(item.id).title + } else { + mediaRepository.getMovieDetails(item.id).title + } + }.getOrNull()?.takeIf { it.isNotBlank() } ?: item.title + + val resolvedEpisodeTitle = runCatching { + if (item.mediaType == MediaType.TV && item.season != null && item.episode != null) { + val episodes = mediaRepository.getSeasonEpisodes(item.id, item.season) + episodes.firstOrNull { it.episodeNumber == item.episode }?.name + } else { + null + } + }.getOrNull()?.takeIf { it.isNotBlank() } ?: item.episodeTitle + item.copy( + title = localizedTitle, + episodeTitle = resolvedEpisodeTitle + ) + } + } + @RequiresApi(Build.VERSION_CODES.O) private fun syncPublishedRows(items: List) { val channelId = ensurePreviewChannel() ?: return From 3efd204f1c53a6973c0f9c1f5e501ac59e23fa86 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:27:16 +0200 Subject: [PATCH 05/12] Remove unused import in LauncherContinueWatchingRepository Removed unused import for MediaRepository. --- .../tv/data/repository/LauncherContinueWatchingRepository.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt index d655b5b60..f994f7e8c 100644 --- a/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt +++ b/app/src/main/kotlin/com/arflix/tv/data/repository/LauncherContinueWatchingRepository.kt @@ -18,7 +18,6 @@ import com.arflix.tv.MainActivity import com.arflix.tv.R import com.arflix.tv.data.model.MediaType import com.arflix.tv.data.model.SportsAddonCapabilities -import com.arflix.tv.data.repository.MediaRepository import com.arflix.tv.navigation.Screen import com.arflix.tv.util.AppLogger import com.arflix.tv.util.Constants From 23a022ec8e17ba9472831c60e7036bcce66fcc6e Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Wed, 5 Aug 2026 09:59:50 +0200 Subject: [PATCH 06/12] Fix APK upload path in android.yml workflow From 258440ee577086f4e423201e19c9ccb127c253b7 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:07:35 +0200 Subject: [PATCH 07/12] Update APK path in GitHub Actions workflow --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 6742a725d..9efdea0e8 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -28,4 +28,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: ARVIO-APK - path: app/build/outputs/apk/debug/*.apk + path: app/build/outputs/apk/sideload/debug/*.apk From d221a864b6909983502d0a2a9fea454c32624650 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:39:15 +0200 Subject: [PATCH 08/12] Add GitHub Actions workflow for build check --- .github/workflows/prueba.yml | 64 ++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/prueba.yml diff --git a/.github/workflows/prueba.yml b/.github/workflows/prueba.yml new file mode 100644 index 000000000..4deb8a45b --- /dev/null +++ b/.github/workflows/prueba.yml @@ -0,0 +1,64 @@ +name: Build Check + +on: + pull_request: + branches: + - main + +concurrency: + group: build-check-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Verify build + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: "17" + cache: gradle + + - name: Prepare local properties + env: + LOCAL_PROPERTIES_BASE64: ${{ secrets.LOCAL_PROPERTIES_BASE64 }} + run: | + if [ -n "$LOCAL_PROPERTIES_BASE64" ]; then + printf '%s' "$LOCAL_PROPERTIES_BASE64" | base64 --decode > local.properties + fi + + - name: Prepare secrets.properties + env: + SUPABASE_URL: ${{ secrets.SUPABASE_URL }} + SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} + TMDB_API_KEY: ${{ secrets.TMDB_API_KEY }} + TRAKT_CLIENT_ID: ${{ secrets.TRAKT_CLIENT_ID }} + TRAKT_CLIENT_SECRET: ${{ secrets.TRAKT_CLIENT_SECRET }} + run: | + echo "SUPABASE_URL=${SUPABASE_URL:-https://your-project.supabase.co}" > secrets.properties + echo "SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY:-your-supabase-anon-key}" >> secrets.properties + echo "TMDB_API_KEY=${TMDB_API_KEY:-your-tmdb-api-key}" >> secrets.properties + echo "TRAKT_CLIENT_ID=${TRAKT_CLIENT_ID:-your-trakt-client-id}" >> secrets.properties + echo "TRAKT_CLIENT_SECRET=${TRAKT_CLIENT_SECRET:-your-trakt-client-secret}" >> secrets.properties + + - name: Create CI keystore + run: | + keytool -genkeypair -keystore arvio.jks -alias arvio \ + -keyalg RSA -keysize 2048 -validity 1 \ + -dname "CN=CI" -storepass 815787 -keypass 815787 + + - name: Build + run: ./gradlew assembleSideloadDebug --no-daemon + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: arvio-sideload-debug-apk + path: app/build/outputs/apk/sideload/debug/*.apk + if-no-files-found: error From ffc58093498d099ff4ec1833f6325c53a0503b5c Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Wed, 5 Aug 2026 10:42:47 +0200 Subject: [PATCH 09/12] Add workflow_dispatch trigger to build-check.yml --- .github/workflows/build-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml index feaafa0d4..449f13002 100644 --- a/.github/workflows/build-check.yml +++ b/.github/workflows/build-check.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + workflow_dispatch: concurrency: group: build-check-${{ github.ref }} From 441fdfa8b07a153d5799b20f637b71b5c7bc88ae Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:55:18 +0200 Subject: [PATCH 10/12] Delete .github/workflows/android.yml --- .github/workflows/android.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml deleted file mode 100644 index 9efdea0e8..000000000 --- a/.github/workflows/android.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Compilar APK - -on: - workflow_dispatch: # Esto es la magia que crea el botón para ejecutarlo manualmente - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Clonar el código - uses: actions/checkout@v4 - - - name: Configurar Java 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: gradle - - - name: Dar permisos de ejecución a Gradle - run: chmod +x gradlew - - - name: Compilar la App (Modo Debug) - run: ./gradlew assembleDebug - - - name: Guardar el APK para descargar - uses: actions/upload-artifact@v4 - with: - name: ARVIO-APK - path: app/build/outputs/apk/sideload/debug/*.apk From d7ab90754cbd7b0c3885645d045d00c1cdcaf4f4 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:55:26 +0200 Subject: [PATCH 11/12] Delete .github/workflows/prueba.yml --- .github/workflows/prueba.yml | 64 ------------------------------------ 1 file changed, 64 deletions(-) delete mode 100644 .github/workflows/prueba.yml diff --git a/.github/workflows/prueba.yml b/.github/workflows/prueba.yml deleted file mode 100644 index 4deb8a45b..000000000 --- a/.github/workflows/prueba.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Build Check - -on: - pull_request: - branches: - - main - -concurrency: - group: build-check-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - name: Verify build - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Java - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: "17" - cache: gradle - - - name: Prepare local properties - env: - LOCAL_PROPERTIES_BASE64: ${{ secrets.LOCAL_PROPERTIES_BASE64 }} - run: | - if [ -n "$LOCAL_PROPERTIES_BASE64" ]; then - printf '%s' "$LOCAL_PROPERTIES_BASE64" | base64 --decode > local.properties - fi - - - name: Prepare secrets.properties - env: - SUPABASE_URL: ${{ secrets.SUPABASE_URL }} - SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }} - TMDB_API_KEY: ${{ secrets.TMDB_API_KEY }} - TRAKT_CLIENT_ID: ${{ secrets.TRAKT_CLIENT_ID }} - TRAKT_CLIENT_SECRET: ${{ secrets.TRAKT_CLIENT_SECRET }} - run: | - echo "SUPABASE_URL=${SUPABASE_URL:-https://your-project.supabase.co}" > secrets.properties - echo "SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY:-your-supabase-anon-key}" >> secrets.properties - echo "TMDB_API_KEY=${TMDB_API_KEY:-your-tmdb-api-key}" >> secrets.properties - echo "TRAKT_CLIENT_ID=${TRAKT_CLIENT_ID:-your-trakt-client-id}" >> secrets.properties - echo "TRAKT_CLIENT_SECRET=${TRAKT_CLIENT_SECRET:-your-trakt-client-secret}" >> secrets.properties - - - name: Create CI keystore - run: | - keytool -genkeypair -keystore arvio.jks -alias arvio \ - -keyalg RSA -keysize 2048 -validity 1 \ - -dname "CN=CI" -storepass 815787 -keypass 815787 - - - name: Build - run: ./gradlew assembleSideloadDebug --no-daemon - - - name: Upload APK - uses: actions/upload-artifact@v4 - with: - name: arvio-sideload-debug-apk - path: app/build/outputs/apk/sideload/debug/*.apk - if-no-files-found: error From 973a4d91c93ca37fc32f8ffa72040714119c4c68 Mon Sep 17 00:00:00 2001 From: Aaronnn17 <108125048+Aaronnn17@users.noreply.github.com> Date: Thu, 6 Aug 2026 08:56:44 +0200 Subject: [PATCH 12/12] Update build-check.yml --- .github/workflows/build-check.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build-check.yml b/.github/workflows/build-check.yml index 449f13002..feaafa0d4 100644 --- a/.github/workflows/build-check.yml +++ b/.github/workflows/build-check.yml @@ -4,7 +4,6 @@ on: pull_request: branches: - main - workflow_dispatch: concurrency: group: build-check-${{ github.ref }}