diff --git a/app-tv/build.gradle.kts b/app-tv/build.gradle.kts index 654af0e2..38ac9e86 100644 --- a/app-tv/build.gradle.kts +++ b/app-tv/build.gradle.kts @@ -59,6 +59,7 @@ dependencies { implementation(project(":core-ui")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.navigation3) implementation(libs.androidx.activity.compose) diff --git a/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt b/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt index 6aa3af41..f3f6fb7a 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/tv/TvActivity.kt @@ -18,8 +18,8 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -147,8 +147,8 @@ class TvActivity : ComponentActivity() { updateViewModel: AppUpdateViewModel = hiltViewModel() ) { var sessionLoaded by remember { mutableStateOf(false) } - val isLoggedIn by userSessionRepository.isLoggedIn.collectAsState(initial = false) - val availableUpdate by updateViewModel.availableUpdate.collectAsState() + val isLoggedIn by userSessionRepository.isLoggedIn.collectAsStateWithLifecycle(initialValue = false) + val availableUpdate by updateViewModel.availableUpdate.collectAsStateWithLifecycle() val context = LocalContext.current LaunchedEffect(Unit) { diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/TvAppScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/TvAppScreen.kt index 5ce86aa0..d9be787f 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/TvAppScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/TvAppScreen.kt @@ -7,8 +7,8 @@ import androidx.compose.material.icons.outlined.Movie import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.Tv import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel @@ -35,10 +35,10 @@ fun TvAppScreen( viewModel: AppViewModel = hiltViewModel(), modifier: Modifier = Modifier ) { - val libraries by viewModel.libraries.collectAsState() - val continueWatching by viewModel.continueWatching.collectAsState() - val nextUp by viewModel.nextUp.collectAsState() - val latestLibraryContent by viewModel.latestLibraryContent.collectAsState() + val libraries by viewModel.libraries.collectAsStateWithLifecycle() + val continueWatching by viewModel.continueWatching.collectAsStateWithLifecycle() + val nextUp by viewModel.nextUp.collectAsStateWithLifecycle() + val latestLibraryContent by viewModel.latestLibraryContent.collectAsStateWithLifecycle() val navigationManager = LocalNavigationManager.current @Suppress("UNCHECKED_CAST") diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/episode/TvEpisodeScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/episode/TvEpisodeScreen.kt index 743f993a..a69c8ada 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/episode/TvEpisodeScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/episode/TvEpisodeScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Modifier @@ -40,8 +40,8 @@ fun TvEpisodeScreen( viewModel.selectEpisode(episode) } - val episode = viewModel.episode.collectAsState() - val seriesTitle = viewModel.seriesTitle.collectAsState() + val episode = viewModel.episode.collectAsStateWithLifecycle() + val seriesTitle = viewModel.seriesTitle.collectAsStateWithLifecycle() val selectedEpisode = episode.value if (selectedEpisode == null) { diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/library/TvLibraryScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/library/TvLibraryScreen.kt index 8f2e1563..3ed6a59c 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/library/TvLibraryScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/library/TvLibraryScreen.kt @@ -15,7 +15,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -35,7 +35,7 @@ fun TvLibraryScreen( viewModel.selectLibrary(libraryId = library.id) } - val libraryItems = viewModel.contents.collectAsState() + val libraryItems = viewModel.contents.collectAsStateWithLifecycle() Scaffold( modifier = modifier, diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginScreen.kt index 3906d2b3..f2eb2348 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/login/TvLoginScreen.kt @@ -1,8 +1,8 @@ package hu.bbara.purefin.ui.screen.login import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -16,18 +16,18 @@ fun TvLoginScreen( viewModel: LoginViewModel = hiltViewModel(), modifier: Modifier = Modifier ) { - val serverUrl by viewModel.url.collectAsState() - val phase by viewModel.phase.collectAsState() - val selectedServer by viewModel.selectedServer.collectAsState() - val discoveredServers by viewModel.discoveredServers.collectAsState() - val username by viewModel.username.collectAsState() - val password by viewModel.password.collectAsState() - val isSearching by viewModel.isSearching.collectAsState() - val isLoggingIn by viewModel.isLoggingIn.collectAsState() - val quickConnectAvailable by viewModel.quickConnectAvailable.collectAsState() - val quickConnectCode by viewModel.quickConnectCode.collectAsState() - val isQuickConnecting by viewModel.isQuickConnecting.collectAsState() - val errorMessage by viewModel.errorMessage.collectAsState() + val serverUrl by viewModel.url.collectAsStateWithLifecycle() + val phase by viewModel.phase.collectAsStateWithLifecycle() + val selectedServer by viewModel.selectedServer.collectAsStateWithLifecycle() + val discoveredServers by viewModel.discoveredServers.collectAsStateWithLifecycle() + val username by viewModel.username.collectAsStateWithLifecycle() + val password by viewModel.password.collectAsStateWithLifecycle() + val isSearching by viewModel.isSearching.collectAsStateWithLifecycle() + val isLoggingIn by viewModel.isLoggingIn.collectAsStateWithLifecycle() + val quickConnectAvailable by viewModel.quickConnectAvailable.collectAsStateWithLifecycle() + val quickConnectCode by viewModel.quickConnectCode.collectAsStateWithLifecycle() + val isQuickConnecting by viewModel.isQuickConnecting.collectAsStateWithLifecycle() + val errorMessage by viewModel.errorMessage.collectAsStateWithLifecycle() val coroutineScope = rememberCoroutineScope() val state = remember( diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt index 933f90b1..5f55aea2 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/movie/TvMovieScreen.kt @@ -6,7 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.runtime.withFrameNanos import androidx.compose.ui.Modifier @@ -32,7 +32,7 @@ fun TvMovieScreen( viewModel.selectMovie(movie) } - val movieItem = viewModel.movie.collectAsState() + val movieItem = viewModel.movie.collectAsStateWithLifecycle() if (movieItem.value != null) { TvMovieScreenContent( diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt index 53c3bf36..ed3e2a5f 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/player/TvPlayerScreen.kt @@ -29,8 +29,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -85,8 +85,8 @@ fun TvPlayerScreen( viewModel.loadMedia(mediaId) } - val uiState by viewModel.uiState.collectAsState() - val controlsVisible by viewModel.controlsVisible.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val controlsVisible by viewModel.controlsVisible.collectAsStateWithLifecycle() var isPlaylistExpanded by remember { mutableStateOf(false) } var trackPanelType by remember { mutableStateOf(null) } var pendingTrackButtonFocus by remember { mutableStateOf(null) } diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt index 28a33525..e4f84df1 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/series/TvSeriesScreen.kt @@ -8,8 +8,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -46,7 +46,7 @@ fun TvSeriesScreen( viewModel.selectSeries(series) } - val series = viewModel.series.collectAsState() + val series = viewModel.series.collectAsStateWithLifecycle() val seriesData = series.value if (seriesData != null && seriesData.seasons.isNotEmpty()) { diff --git a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/settings/TvSettingsScreen.kt b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/settings/TvSettingsScreen.kt index 3e4b367f..a2f93be6 100644 --- a/app-tv/src/main/java/hu/bbara/purefin/ui/screen/settings/TvSettingsScreen.kt +++ b/app-tv/src/main/java/hu/bbara/purefin/ui/screen/settings/TvSettingsScreen.kt @@ -26,8 +26,8 @@ import androidx.compose.material3.Switch import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -53,7 +53,7 @@ fun TvSettingsScreen( onBack: () -> Unit = viewModel::onBack, modifier: Modifier = Modifier ) { - val settingGroups by viewModel.settingGroups.collectAsState() + val settingGroups by viewModel.settingGroups.collectAsStateWithLifecycle() val context = LocalContext.current LaunchedEffect(viewModel, context) { @@ -117,7 +117,7 @@ private fun TvSettingOptionItem( ) { when (option) { is RangeSetting -> { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { TvNumberSettingItem( title = option.title, @@ -129,7 +129,7 @@ private fun TvSettingOptionItem( } is BooleanSetting -> { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { TvBooleanSettingItem( title = option.title, @@ -140,7 +140,7 @@ private fun TvSettingOptionItem( } is StringSetting -> { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { TvStringSettingItem( title = option.title, @@ -178,7 +178,7 @@ private fun TvDropdownSettingOptionItem( option: DropdownSetting, viewModel: SettingsViewModel ) { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { TvDropdownSettingItem( title = option.title, diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e6af5e22..94d9636b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -58,6 +58,7 @@ dependencies { implementation(project(":core-ui")) implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.navigation3) implementation(libs.androidx.activity.compose) diff --git a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt index ae8085f9..a453b0db 100644 --- a/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt +++ b/app/src/main/java/hu/bbara/purefin/PurefinActivity.kt @@ -16,8 +16,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -146,7 +146,7 @@ class PurefinActivity : ComponentActivity() { navigationManager: NavigationManager ) { var sessionLoaded by remember { mutableStateOf(false) } - val isLoggedIn by userSessionRepository.isLoggedIn.collectAsState(initial = false) + val isLoggedIn by userSessionRepository.isLoggedIn.collectAsStateWithLifecycle(initialValue = false) LaunchedEffect(Unit) { userSessionRepository.isLoggedIn.collect { diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt index 4d77fda1..948b5661 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/AppScreen.kt @@ -11,8 +11,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.hilt.navigation.compose.hiltViewModel @@ -38,15 +38,15 @@ fun AppScreen( updateViewModel: AppUpdateViewModel = hiltViewModel(), modifier: Modifier = Modifier ) { - val libraries by viewModel.libraries.collectAsState() - val libraryContent by viewModel.latestLibraryContent.collectAsState() - val suggestions by viewModel.suggestions.collectAsState() - val continueWatching by viewModel.continueWatching.collectAsState() - val nextUp by viewModel.nextUp.collectAsState() - val isRefreshing by viewModel.isRefreshing.collectAsState() - val isOnline by viewModel.isOnline.collectAsState() - val isCheckingForUpdates by updateViewModel.isCheckingForUpdates.collectAsState() - val availableUpdate by updateViewModel.availableUpdate.collectAsState() + val libraries by viewModel.libraries.collectAsStateWithLifecycle() + val libraryContent by viewModel.latestLibraryContent.collectAsStateWithLifecycle() + val suggestions by viewModel.suggestions.collectAsStateWithLifecycle() + val continueWatching by viewModel.continueWatching.collectAsStateWithLifecycle() + val nextUp by viewModel.nextUp.collectAsStateWithLifecycle() + val isRefreshing by viewModel.isRefreshing.collectAsStateWithLifecycle() + val isOnline by viewModel.isOnline.collectAsStateWithLifecycle() + val isCheckingForUpdates by updateViewModel.isCheckingForUpdates.collectAsStateWithLifecycle() + val availableUpdate by updateViewModel.availableUpdate.collectAsStateWithLifecycle() val navigationManager = LocalNavigationManager.current val snackbarHostState = remember { SnackbarHostState() } diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt index 69491cad..dd7b20e7 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/download/components/DownloadsContent.kt @@ -17,7 +17,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -32,8 +32,8 @@ fun DownloadsContent( modifier: Modifier = Modifier, viewModel: DownloadsViewModel = hiltViewModel(), ) { - val downloads = viewModel.downloads.collectAsState(emptyList()) - val activeDownloads = viewModel.activeDownloads.collectAsState() + val downloads = viewModel.downloads.collectAsStateWithLifecycle(initialValue = emptyList()) + val activeDownloads = viewModel.activeDownloads.collectAsStateWithLifecycle() val isEmpty = downloads.value.isEmpty() && activeDownloads.value.isEmpty() diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt index 9bebb4a7..5aea79a5 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/episode/EpisodeScreen.kt @@ -8,7 +8,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight @@ -46,8 +46,8 @@ fun EpisodeScreen( viewModel.selectEpisode(episode) } - val episode = viewModel.episode.collectAsState() - val downloadState = viewModel.downloadState.collectAsState() + val episode = viewModel.episode.collectAsStateWithLifecycle() + val downloadState = viewModel.downloadState.collectAsStateWithLifecycle() val notificationPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchOverlay.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchOverlay.kt index f7b5c1dc..7ad9c2a7 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchOverlay.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/home/components/search/SearchOverlay.kt @@ -24,8 +24,8 @@ import androidx.compose.material3.SearchBar import androidx.compose.material3.SearchBarDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable @@ -55,7 +55,7 @@ fun SearchOverlay( BackHandler(onBack = onDismiss) var query by rememberSaveable { mutableStateOf("") } - val searchResults by searchViewModel.searchResult.collectAsState() + val searchResults by searchViewModel.searchResult.collectAsStateWithLifecycle() val dismissInteractionSource = remember { MutableInteractionSource() } Box( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt index 27b443c7..298080cd 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/library/LibraryScreen.kt @@ -14,7 +14,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -40,7 +40,7 @@ fun LibraryScreen( viewModel.selectLibrary(libraryId = library.id) } - val libraryItems = viewModel.contents.collectAsState() + val libraryItems = viewModel.contents.collectAsStateWithLifecycle() Scaffold( modifier = modifier.testTag(LibraryScreenTag), diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt index d6ce3ca3..38ff9115 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/login/LoginScreen.kt @@ -1,8 +1,8 @@ package hu.bbara.purefin.ui.screen.login import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier @@ -16,18 +16,18 @@ fun LoginScreen( viewModel: LoginViewModel = hiltViewModel(), modifier: Modifier = Modifier ) { - val serverUrl by viewModel.url.collectAsState() - val phase by viewModel.phase.collectAsState() - val selectedServer by viewModel.selectedServer.collectAsState() - val discoveredServers by viewModel.discoveredServers.collectAsState() - val username by viewModel.username.collectAsState() - val password by viewModel.password.collectAsState() - val isSearching by viewModel.isSearching.collectAsState() - val isLoggingIn by viewModel.isLoggingIn.collectAsState() - val quickConnectAvailable by viewModel.quickConnectAvailable.collectAsState() - val quickConnectCode by viewModel.quickConnectCode.collectAsState() - val isQuickConnecting by viewModel.isQuickConnecting.collectAsState() - val errorMessage by viewModel.errorMessage.collectAsState() + val serverUrl by viewModel.url.collectAsStateWithLifecycle() + val phase by viewModel.phase.collectAsStateWithLifecycle() + val selectedServer by viewModel.selectedServer.collectAsStateWithLifecycle() + val discoveredServers by viewModel.discoveredServers.collectAsStateWithLifecycle() + val username by viewModel.username.collectAsStateWithLifecycle() + val password by viewModel.password.collectAsStateWithLifecycle() + val isSearching by viewModel.isSearching.collectAsStateWithLifecycle() + val isLoggingIn by viewModel.isLoggingIn.collectAsStateWithLifecycle() + val quickConnectAvailable by viewModel.quickConnectAvailable.collectAsStateWithLifecycle() + val quickConnectCode by viewModel.quickConnectCode.collectAsStateWithLifecycle() + val isQuickConnecting by viewModel.isQuickConnecting.collectAsStateWithLifecycle() + val errorMessage by viewModel.errorMessage.collectAsStateWithLifecycle() val coroutineScope = rememberCoroutineScope() val state = remember( diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt index c458efe8..b579aced 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/movie/MovieScreen.kt @@ -8,7 +8,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview @@ -39,8 +39,8 @@ fun MovieScreen( viewModel.selectMovie(movie) } - val movieItem = viewModel.movie.collectAsState() - val downloadState = viewModel.downloadState.collectAsState() + val movieItem = viewModel.movie.collectAsStateWithLifecycle() + val downloadState = viewModel.downloadState.collectAsStateWithLifecycle() val notificationPermissionLauncher = rememberLauncherForActivityResult( ActivityResultContracts.RequestPermission() diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/player/PlayerScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/player/PlayerScreen.kt index a33c0889..63c82641 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/player/PlayerScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/player/PlayerScreen.kt @@ -25,8 +25,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -71,8 +71,8 @@ fun PlayerScreen( viewModel: PlayerViewModel, onBack: () -> Unit ) { - val uiState by viewModel.uiState.collectAsState() - val controlsVisible by viewModel.controlsVisible.collectAsState() + val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val controlsVisible by viewModel.controlsVisible.collectAsStateWithLifecycle() val context = LocalContext.current val activity = context as? Activity diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt index dcf3997f..9b92e3b9 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/series/SeriesScreen.kt @@ -6,8 +6,8 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -49,7 +49,7 @@ fun SeriesScreen( viewModel.selectSeries(series) } - val seriesState = viewModel.series.collectAsState() + val seriesState = viewModel.series.collectAsStateWithLifecycle() val seriesData = seriesState.value if (seriesData != null && seriesData.seasons.isNotEmpty()) { @@ -58,9 +58,9 @@ fun SeriesScreen( } SeriesScreenInternal( series = seriesData, - seriesDownloadState = viewModel.seriesDownloadState.collectAsState().value, - seasonDownloadState = viewModel.seasonDownloadState.collectAsState().value, - isSmartDownloadEnabled = viewModel.isSmartDownloadEnabled.collectAsState().value, + seriesDownloadState = viewModel.seriesDownloadState.collectAsStateWithLifecycle().value, + seasonDownloadState = viewModel.seasonDownloadState.collectAsStateWithLifecycle().value, + isSmartDownloadEnabled = viewModel.isSmartDownloadEnabled.collectAsStateWithLifecycle().value, onDownloadOptionSelected = { option, selectedSeason -> when (option) { SeriesDownloadOption.SEASON -> diff --git a/app/src/main/java/hu/bbara/purefin/ui/screen/settings/SettingsScreen.kt b/app/src/main/java/hu/bbara/purefin/ui/screen/settings/SettingsScreen.kt index 43c78096..20091871 100644 --- a/app/src/main/java/hu/bbara/purefin/ui/screen/settings/SettingsScreen.kt +++ b/app/src/main/java/hu/bbara/purefin/ui/screen/settings/SettingsScreen.kt @@ -14,8 +14,8 @@ import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -42,7 +42,7 @@ fun SettingsScreen( viewModel: SettingsViewModel = hiltViewModel(), modifier: Modifier = Modifier ) { - val settingGroups by viewModel.settingGroups.collectAsState() + val settingGroups by viewModel.settingGroups.collectAsStateWithLifecycle() val snackbarHostState = remember { SnackbarHostState() } LaunchedEffect(viewModel) { @@ -107,7 +107,7 @@ private fun SettingOptionItem( ) { when (option) { is RangeSetting -> { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { RangeSettingItem( title = option.title, @@ -119,7 +119,7 @@ private fun SettingOptionItem( } is BooleanSetting -> { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { BooleanSettingItem( title = option.title, @@ -130,7 +130,7 @@ private fun SettingOptionItem( } is StringSetting -> { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { StringSettingItem( title = option.title, @@ -168,7 +168,7 @@ private fun DropdownSettingOptionItem( option: DropdownSetting, viewModel: SettingsViewModel ) { - val value by viewModel.value(option).collectAsState(initial = null) + val value by viewModel.value(option).collectAsStateWithLifecycle(initialValue = null) value?.let { DropdownSettingItem( title = option.title, diff --git a/core/src/main/java/hu/bbara/purefin/core/feature/browse/home/AppViewModel.kt b/core/src/main/java/hu/bbara/purefin/core/feature/browse/home/AppViewModel.kt index 94d198f6..e73761e0 100644 --- a/core/src/main/java/hu/bbara/purefin/core/feature/browse/home/AppViewModel.kt +++ b/core/src/main/java/hu/bbara/purefin/core/feature/browse/home/AppViewModel.kt @@ -9,6 +9,7 @@ import hu.bbara.purefin.core.data.NetworkMonitor import hu.bbara.purefin.core.data.UserSessionRepository import hu.bbara.purefin.core.download.MediaDownloadController import hu.bbara.purefin.core.jellyfin.JellyfinMediaMetadataUpdater +import hu.bbara.purefin.core.settings.SettingsRepository import hu.bbara.purefin.core.model.EpisodeUiModel import hu.bbara.purefin.core.model.LibraryUiModel import hu.bbara.purefin.core.model.MediaUiModel @@ -41,6 +42,7 @@ class AppViewModel @Inject constructor( private val jellyfinMediaMetadataUpdater: JellyfinMediaMetadataUpdater, private val navigationManager: NavigationManager, private val mediaDownloadManager: MediaDownloadController, + private val settingsRepository: SettingsRepository, networkMonitor: NetworkMonitor, ) : ViewModel() { @@ -147,26 +149,31 @@ class AppViewModel @Inject constructor( homeRepository.latestLibraryContent, localMediaRepository.movies, localMediaRepository.series, - localMediaRepository.episodes - ) { libraryMap, moviesMap, seriesMap, episodesMap -> - libraryMap.mapValues { (_, items) -> - items.mapNotNull { media -> - when (media) { - is Media.MovieMedia -> moviesMap[media.movieId]?.let { - MovieUiModel(movie = it) + localMediaRepository.episodes, + settingsRepository.settings, + ) { libraryMap, moviesMap, seriesMap, episodesMap, settings -> + libraryMap + .filter { (libraryId, _) -> + settings.booleanSettings["home_library_visible_$libraryId"] ?: true + } + .mapValues { (_, items) -> + items.mapNotNull { media -> + when (media) { + is Media.MovieMedia -> moviesMap[media.movieId]?.let { + MovieUiModel(movie = it) + } + is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let { + EpisodeUiModel(episode = it) + } + is Media.SeriesMedia -> seriesMap[media.seriesId]?.let { + SeriesUiModel(series = it) + } + is Media.SeasonMedia -> seriesMap[media.seriesId]?.let { + SeriesUiModel(series = it) + } } - is Media.EpisodeMedia -> episodesMap[media.episodeId]?.let { - EpisodeUiModel(episode = it) - } - is Media.SeriesMedia -> seriesMap[media.seriesId]?.let { - SeriesUiModel(series = it) - } - is Media.SeasonMedia -> seriesMap[media.seriesId]?.let { - SeriesUiModel(series = it) - } - } - }.distinctBy { it.id } - } + }.distinctBy { it.id } + } }.stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), diff --git a/core/src/main/java/hu/bbara/purefin/core/feature/settings/HomeLibrarySettingsProvider.kt b/core/src/main/java/hu/bbara/purefin/core/feature/settings/HomeLibrarySettingsProvider.kt new file mode 100644 index 00000000..abaf738e --- /dev/null +++ b/core/src/main/java/hu/bbara/purefin/core/feature/settings/HomeLibrarySettingsProvider.kt @@ -0,0 +1,35 @@ +package hu.bbara.purefin.core.feature.settings + +import hu.bbara.purefin.core.data.HomeRepository +import hu.bbara.purefin.core.settings.BooleanSetting +import hu.bbara.purefin.core.settings.SettingGroup +import hu.bbara.purefin.core.settings.SettingsGroupProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class HomeLibrarySettingsProvider @Inject constructor( + private val homeRepository: HomeRepository, +) : SettingsGroupProvider { + + override val settingGroups: Flow> = homeRepository.libraries.map { libraries -> + if (libraries.isEmpty()) return@map emptyList() + + val options = libraries.map { library -> + BooleanSetting( + key = "home_library_visible_${library.id}", + title = library.name, + defaultValue = true + ) + } + + listOf( + SettingGroup( + title = "Home Screen Libraries", + options = options + ) + ) + } +} diff --git a/core/src/main/java/hu/bbara/purefin/core/settings/SettingsGroupProviderModule.kt b/core/src/main/java/hu/bbara/purefin/core/settings/SettingsGroupProviderModule.kt index 75a7078f..475f7492 100644 --- a/core/src/main/java/hu/bbara/purefin/core/settings/SettingsGroupProviderModule.kt +++ b/core/src/main/java/hu/bbara/purefin/core/settings/SettingsGroupProviderModule.kt @@ -5,6 +5,7 @@ import dagger.Module import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent import dagger.multibindings.IntoSet +import hu.bbara.purefin.core.feature.settings.HomeLibrarySettingsProvider import hu.bbara.purefin.core.feature.settings.LogoutSettingsProvider import hu.bbara.purefin.core.feature.update.AppUpdateController @@ -16,6 +17,10 @@ abstract class SettingsGroupProviderModule { @IntoSet abstract fun bindAppUpdateSettingsProvider(impl: AppUpdateController): SettingsGroupProvider + @Binds + @IntoSet + abstract fun bindHomeLibrarySettingsProvider(impl: HomeLibrarySettingsProvider): SettingsGroupProvider + @Binds @IntoSet abstract fun bindLogoutSettingsProvider(impl: LogoutSettingsProvider): SettingsGroupProvider diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2841c714..0fa87946 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,6 +37,7 @@ androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-co androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" } androidx-lifecycle-viewmodel-navigation3 = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-navigation3", version.ref = "lifecycleRuntimeKtx" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }