From 2c6af4b126e1e861dfc7649425da7bc2fc9e46d1 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Wed, 21 Oct 2026 02:47:54 +0000 Subject: [PATCH] library: keep detail scroll positions across navigation --- .../subtracks/ui/library/AlbumDetailScreen.kt | 6 ++-- .../ui/library/AlbumDetailViewModel.kt | 14 ++++++--- .../ui/library/ArtistDetailScreen.kt | 6 ++-- .../ui/library/ArtistDetailViewModel.kt | 31 +++++++++++-------- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt index 80829033..62c5b26b 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailScreen.kt @@ -67,9 +67,9 @@ fun AlbumDetailRoute( viewModel: AlbumDetailViewModel = koinViewModel(key = albumId) { parametersOf(albumId) }, playbackController: PlaybackController = koinInject(), ) { - val album by viewModel.album.collectAsStateWithLifecycle(initialValue = null) - val songs by viewModel.songs.collectAsStateWithLifecycle(initialValue = emptyList()) - val discs by viewModel.discs.collectAsStateWithLifecycle(initialValue = emptyList()) + val album by viewModel.album.collectAsStateWithLifecycle() + val songs by viewModel.songs.collectAsStateWithLifecycle() + val discs by viewModel.discs.collectAsStateWithLifecycle() val downloads by viewModel.downloads.collectAsStateWithLifecycle() val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle() val offline by viewModel.offline.collectAsStateWithLifecycle() diff --git a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailViewModel.kt index db1151bd..0db9e33c 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/AlbumDetailViewModel.kt @@ -15,7 +15,6 @@ import com.subtracks.data.repo.LibraryRepository import com.subtracks.data.repo.SourceRepository import com.subtracks.playback.PlaybackController import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine @@ -35,13 +34,20 @@ class AlbumDetailViewModel( ) : ViewModel() { private val sourceId = libraryRepository.activeSourceId.filterNotNull() - val album: Flow = sourceId.flatMapLatest { libraryRepository.album(it, albumId) } + val album: StateFlow = + sourceId + .flatMapLatest { libraryRepository.album(it, albumId) } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) - val songs: Flow> = + val songs: StateFlow> = combine(sourceId, sourceRepository.offline) { id, offline -> id to offline } .flatMapLatest { (id, offline) -> libraryRepository.albumSongs(id, albumId, offline) } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) - val discs: Flow> = sourceId.flatMapLatest { libraryRepository.albumDiscs(it, albumId) } + val discs: StateFlow> = + sourceId + .flatMapLatest { libraryRepository.albumDiscs(it, albumId) } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) val downloads: StateFlow> = downloadRepository.states() diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt index 02927839..2bd8f512 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailScreen.kt @@ -191,9 +191,9 @@ fun ArtistDetailRoute( viewModel: ArtistDetailViewModel = koinViewModel(key = artistId) { parametersOf(artistId) }, playbackController: PlaybackController = koinInject(), ) { - val artist by viewModel.artist.collectAsStateWithLifecycle(initialValue = null) - val artistName by viewModel.artistName.collectAsStateWithLifecycle(initialValue = null) - val albums by viewModel.albums.collectAsStateWithLifecycle(initialValue = null) + val artist by viewModel.artist.collectAsStateWithLifecycle() + val artistName by viewModel.artistName.collectAsStateWithLifecycle() + val albums by viewModel.albums.collectAsStateWithLifecycle() val bioSnapshots = remember(viewModel) { viewModel.artistBio.map { BioSnapshot(known = true, present = it != null, biography = it?.biography) } diff --git a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt index 26829258..748f739a 100644 --- a/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt +++ b/app/src/main/kotlin/com/subtracks/ui/library/ArtistDetailViewModel.kt @@ -17,7 +17,6 @@ import com.subtracks.ui.components.ArtistArt import com.subtracks.ui.components.artistArt import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -27,7 +26,6 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -41,23 +39,27 @@ class ArtistDetailViewModel( ) : ViewModel() { private val sourceId = libraryRepository.activeSourceId.filterNotNull() - val artist: Flow = sourceId.flatMapLatest { libraryRepository.artist(it, artistId) } + val artist: StateFlow = + sourceId + .flatMapLatest { libraryRepository.artist(it, artistId) } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) - val artistName: Flow = - sourceId.flatMapLatest { id -> - combine( - libraryRepository.artist(id, artistId), - libraryRepository.artistName(id, artistId), - ) { loaded, fromSongs -> loaded?.name?.takeIf { it.isNotBlank() } ?: fromSongs } - } + val artistName: StateFlow = + sourceId + .flatMapLatest { id -> + combine( + libraryRepository.artist(id, artistId), + libraryRepository.artistName(id, artistId), + ) { loaded, fromSongs -> loaded?.name?.takeIf { it.isNotBlank() } ?: fromSongs } + }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) val offline: StateFlow = sourceRepository.offline - val albums: Flow = + val albums: StateFlow = combine(sourceId, sourceRepository.offline) { id, offline -> id to offline } .flatMapLatest { (id, offline) -> libraryRepository.artistAlbums(id, artistId, offline) } .map { it } - .onStart { emit(null) } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) val downloadStatus: StateFlow = sourceId @@ -69,7 +71,10 @@ class ArtistDetailViewModel( .flatMapLatest { downloadRepository.statuses(it, DownloadList.Album) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap()) - val artistBio: Flow = sourceId.flatMapLatest { libraryRepository.artistBio(it, artistId) } + val artistBio: StateFlow = + sourceId + .flatMapLatest { libraryRepository.artistBio(it, artistId) } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) private val _bioLoading = MutableStateFlow(true) val bioLoading: StateFlow = _bioLoading