From 95e656036d05334e2f61e5e78a3da9b23b6a8ebc Mon Sep 17 00:00:00 2001 From: kalzEOS <49320606+kalzEOS@users.noreply.github.com> Date: Mon, 13 Apr 2026 12:00:02 -0400 Subject: [PATCH] Refactor root screen wiring and release 3.4.5 --- app/build.gradle.kts | 4 +- .../com/example/xtreamplayer/MainActivity.kt | 43 +++-------------- .../example/xtreamplayer/MainActivityUi.kt | 48 +++++++++++++------ .../xtreamplayer/di/RootScreenEntryPoint.kt | 28 +++++++++++ .../xtreamplayer/viewmodel/UpdateViewModel.kt | 16 +++++++ release-notes-3.4.5.md | 4 ++ 6 files changed, 91 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/com/example/xtreamplayer/di/RootScreenEntryPoint.kt create mode 100644 app/src/main/java/com/example/xtreamplayer/viewmodel/UpdateViewModel.kt create mode 100644 release-notes-3.4.5.md diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 47e6f6b..74dc25d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,8 +3,8 @@ import java.util.Properties import org.gradle.api.provider.Property import org.jetbrains.kotlin.gradle.dsl.JvmTarget -val appVersionCode = 140 -val appVersionName = "3.4.4" +val appVersionCode = 141 +val appVersionName = "3.4.5" plugins { alias(libs.plugins.android.application) diff --git a/app/src/main/java/com/example/xtreamplayer/MainActivity.kt b/app/src/main/java/com/example/xtreamplayer/MainActivity.kt index f3717b6..8611363 100644 --- a/app/src/main/java/com/example/xtreamplayer/MainActivity.kt +++ b/app/src/main/java/com/example/xtreamplayer/MainActivity.kt @@ -3,54 +3,25 @@ package com.example.xtreamplayer import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import com.example.xtreamplayer.content.ContentRepository -import com.example.xtreamplayer.content.ContinueWatchingRepository -import com.example.xtreamplayer.content.FavoritesRepository -import com.example.xtreamplayer.content.HistoryRepository -import com.example.xtreamplayer.content.SubtitleRepository -import com.example.xtreamplayer.player.Media3PlaybackEngine -import com.example.xtreamplayer.settings.PlaybackSettingsController +import com.example.xtreamplayer.di.RootScreenEntryPoint import dagger.hilt.android.AndroidEntryPoint -import okhttp3.OkHttpClient -import javax.inject.Inject +import dagger.hilt.android.EntryPointAccessors @AndroidEntryPoint class MainActivity : ComponentActivity() { - @Inject lateinit var playbackSettingsController: PlaybackSettingsController - - @Inject lateinit var playbackEngine: Media3PlaybackEngine - - @Inject lateinit var contentRepository: ContentRepository - - @Inject lateinit var favoritesRepository: FavoritesRepository - - @Inject lateinit var historyRepository: HistoryRepository - - @Inject lateinit var continueWatchingRepository: ContinueWatchingRepository - - @Inject lateinit var subtitleRepository: SubtitleRepository - - @Inject lateinit var okHttpClient: OkHttpClient - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { - RootScreen( - playbackSettingsController = playbackSettingsController, - playbackEngine = playbackEngine, - contentRepository = contentRepository, - favoritesRepository = favoritesRepository, - historyRepository = historyRepository, - continueWatchingRepository = continueWatchingRepository, - subtitleRepository = subtitleRepository, - updateHttpClient = okHttpClient - ) + RootScreen() } } override fun onDestroy() { if (isFinishing) { - playbackEngine.release() + EntryPointAccessors.fromApplication( + applicationContext, + RootScreenEntryPoint::class.java + ).playbackEngine().release() } super.onDestroy() } diff --git a/app/src/main/java/com/example/xtreamplayer/MainActivityUi.kt b/app/src/main/java/com/example/xtreamplayer/MainActivityUi.kt index be20ed4..936dd91 100644 --- a/app/src/main/java/com/example/xtreamplayer/MainActivityUi.kt +++ b/app/src/main/java/com/example/xtreamplayer/MainActivityUi.kt @@ -143,11 +143,13 @@ import com.example.xtreamplayer.content.SeriesInfo import com.example.xtreamplayer.content.SearchNormalizer import com.example.xtreamplayer.content.SubtitleRepository import com.example.xtreamplayer.content.shouldStoreContinueWatchingEntry +import com.example.xtreamplayer.di.RootScreenEntryPoint import com.example.xtreamplayer.observability.AppDiagnostics import com.example.xtreamplayer.player.Media3PlaybackEngine import com.example.xtreamplayer.player.BufferProfile import com.example.xtreamplayer.settings.PlaybackSettingsController import com.example.xtreamplayer.settings.ClockFormatOption +import com.example.xtreamplayer.settings.SettingsRepository import com.example.xtreamplayer.settings.SettingsState import com.example.xtreamplayer.settings.SettingsViewModel import com.example.xtreamplayer.settings.SubtitleAppearanceSettings @@ -193,8 +195,8 @@ import com.example.xtreamplayer.ui.theme.AppColors import com.example.xtreamplayer.ui.theme.XtreamPlayerTheme import com.example.xtreamplayer.viewmodel.BrowseViewModel import com.example.xtreamplayer.viewmodel.PlayerViewModel -import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject +import com.example.xtreamplayer.viewmodel.UpdateViewModel +import dagger.hilt.android.EntryPointAccessors import java.util.Locale import java.io.File import android.provider.Settings @@ -209,7 +211,6 @@ import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import timber.log.Timber -import kotlin.LazyThreadSafetyMode import kotlin.math.max import kotlin.math.min @@ -226,7 +227,7 @@ private enum class UpdateCheckSource { STARTUP } -private data class UpdateUiState( +data class UpdateUiState( val showDialog: Boolean = false, val inProgress: Boolean = false, val pendingRelease: UpdateRelease? = null @@ -255,7 +256,29 @@ private const val PLAYBACK_PROGRESS_SAVE_INTERVAL_MS = 30_000L private const val STARTUP_DEFER_NON_CRITICAL_MS = 1_000L @Composable -fun RootScreen( +fun RootScreen() { + val context = LocalContext.current + val rootScreenEntryPoint = remember(context.applicationContext) { + EntryPointAccessors.fromApplication( + context.applicationContext, + RootScreenEntryPoint::class.java + ) + } + RootScreenContent( + playbackSettingsController = rootScreenEntryPoint.playbackSettingsController(), + playbackEngine = rootScreenEntryPoint.playbackEngine(), + contentRepository = rootScreenEntryPoint.contentRepository(), + favoritesRepository = rootScreenEntryPoint.favoritesRepository(), + historyRepository = rootScreenEntryPoint.historyRepository(), + continueWatchingRepository = rootScreenEntryPoint.continueWatchingRepository(), + subtitleRepository = rootScreenEntryPoint.subtitleRepository(), + settingsRepository = rootScreenEntryPoint.settingsRepository(), + updateHttpClient = rootScreenEntryPoint.okHttpClient() + ) +} + +@Composable +private fun RootScreenContent( playbackSettingsController: PlaybackSettingsController, playbackEngine: Media3PlaybackEngine, contentRepository: ContentRepository, @@ -263,6 +286,7 @@ fun RootScreen( historyRepository: HistoryRepository, continueWatchingRepository: ContinueWatchingRepository, subtitleRepository: SubtitleRepository, + settingsRepository: SettingsRepository, updateHttpClient: OkHttpClient ) { val context = LocalContext.current @@ -287,6 +311,7 @@ fun RootScreen( val authViewModel: AuthViewModel = hiltViewModel() val browseViewModel: BrowseViewModel = hiltViewModel() val playerViewModel: PlayerViewModel = hiltViewModel() + val updateViewModel: UpdateViewModel = hiltViewModel() val authState by authViewModel.uiState.collectAsStateWithLifecycle() val savedConfig by authViewModel.savedConfig.collectAsStateWithLifecycle() val savedConfigLoaded by authViewModel.savedConfigLoaded.collectAsStateWithLifecycle() @@ -310,10 +335,10 @@ fun RootScreen( var focusManageListsOnSettingsReturn by remember { mutableStateOf(false) } var wasShowingAppearance by remember { mutableStateOf(false) } var wasShowingManageLists by remember { mutableStateOf(false) } - var updateUiState by remember { mutableStateOf(UpdateUiState()) } - var updateCheckJob by remember { mutableStateOf(null) } - var startupUpdateCheckEnabled by remember { mutableStateOf(null) } - var startupUpdateCheckHandled by remember { mutableStateOf(false) } + var updateUiState by updateViewModel.updateUiState + var updateCheckJob by updateViewModel.updateCheckJob + var startupUpdateCheckEnabled by updateViewModel.startupUpdateCheckEnabled + var startupUpdateCheckHandled by updateViewModel.startupUpdateCheckHandled val showApiKeyDialogState = remember { mutableStateOf(false) } var showApiKeyDialog by showApiKeyDialogState val showThemeDialogState = remember { mutableStateOf(false) } @@ -369,11 +394,6 @@ fun RootScreen( } // Progressive sync coordinator - val settingsRepository by remember { - lazy(LazyThreadSafetyMode.NONE) { - com.example.xtreamplayer.settings.SettingsRepository(context) - } - } LaunchedEffect(settings.subtitleCacheAutoClearIntervalMs, startupDeferredReady) { if (!startupDeferredReady) { return@LaunchedEffect diff --git a/app/src/main/java/com/example/xtreamplayer/di/RootScreenEntryPoint.kt b/app/src/main/java/com/example/xtreamplayer/di/RootScreenEntryPoint.kt new file mode 100644 index 0000000..9279eb2 --- /dev/null +++ b/app/src/main/java/com/example/xtreamplayer/di/RootScreenEntryPoint.kt @@ -0,0 +1,28 @@ +package com.example.xtreamplayer.di + +import com.example.xtreamplayer.content.ContentRepository +import com.example.xtreamplayer.content.ContinueWatchingRepository +import com.example.xtreamplayer.content.FavoritesRepository +import com.example.xtreamplayer.content.HistoryRepository +import com.example.xtreamplayer.content.SubtitleRepository +import com.example.xtreamplayer.player.Media3PlaybackEngine +import com.example.xtreamplayer.settings.PlaybackSettingsController +import com.example.xtreamplayer.settings.SettingsRepository +import dagger.hilt.EntryPoint +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient + +@EntryPoint +@InstallIn(SingletonComponent::class) +interface RootScreenEntryPoint { + fun playbackSettingsController(): PlaybackSettingsController + fun playbackEngine(): Media3PlaybackEngine + fun contentRepository(): ContentRepository + fun favoritesRepository(): FavoritesRepository + fun historyRepository(): HistoryRepository + fun continueWatchingRepository(): ContinueWatchingRepository + fun subtitleRepository(): SubtitleRepository + fun settingsRepository(): SettingsRepository + fun okHttpClient(): OkHttpClient +} diff --git a/app/src/main/java/com/example/xtreamplayer/viewmodel/UpdateViewModel.kt b/app/src/main/java/com/example/xtreamplayer/viewmodel/UpdateViewModel.kt new file mode 100644 index 0000000..7b2f20d --- /dev/null +++ b/app/src/main/java/com/example/xtreamplayer/viewmodel/UpdateViewModel.kt @@ -0,0 +1,16 @@ +package com.example.xtreamplayer.viewmodel + +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import com.example.xtreamplayer.UpdateUiState +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject +import kotlinx.coroutines.Job + +@HiltViewModel +class UpdateViewModel @Inject constructor() : ViewModel() { + val updateUiState = mutableStateOf(UpdateUiState()) + val updateCheckJob = mutableStateOf(null) + val startupUpdateCheckEnabled = mutableStateOf(null) + val startupUpdateCheckHandled = mutableStateOf(false) +} diff --git a/release-notes-3.4.5.md b/release-notes-3.4.5.md new file mode 100644 index 0000000..5648ed1 --- /dev/null +++ b/release-notes-3.4.5.md @@ -0,0 +1,4 @@ +# Changes: +- Refactored root screen dependency wiring through a Hilt entry point. +- Moved update dialog and startup-check state into a Hilt ViewModel. +- Kept player engine release handling in the activity teardown path.