library: keep detail scroll positions across navigation

This commit is contained in:
austinried
2026-10-21 10:13:52 +00:00
parent 61774a19ca
commit 2c6af4b126
4 changed files with 34 additions and 23 deletions
@@ -67,9 +67,9 @@ fun AlbumDetailRoute(
viewModel: AlbumDetailViewModel = koinViewModel(key = albumId) { parametersOf(albumId) }, viewModel: AlbumDetailViewModel = koinViewModel(key = albumId) { parametersOf(albumId) },
playbackController: PlaybackController = koinInject(), playbackController: PlaybackController = koinInject(),
) { ) {
val album by viewModel.album.collectAsStateWithLifecycle(initialValue = null) val album by viewModel.album.collectAsStateWithLifecycle()
val songs by viewModel.songs.collectAsStateWithLifecycle(initialValue = emptyList()) val songs by viewModel.songs.collectAsStateWithLifecycle()
val discs by viewModel.discs.collectAsStateWithLifecycle(initialValue = emptyList()) val discs by viewModel.discs.collectAsStateWithLifecycle()
val downloads by viewModel.downloads.collectAsStateWithLifecycle() val downloads by viewModel.downloads.collectAsStateWithLifecycle()
val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle() val downloadStatus by viewModel.downloadStatus.collectAsStateWithLifecycle()
val offline by viewModel.offline.collectAsStateWithLifecycle() val offline by viewModel.offline.collectAsStateWithLifecycle()
@@ -15,7 +15,6 @@ import com.subtracks.data.repo.LibraryRepository
import com.subtracks.data.repo.SourceRepository import com.subtracks.data.repo.SourceRepository
import com.subtracks.playback.PlaybackController import com.subtracks.playback.PlaybackController
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@@ -35,13 +34,20 @@ class AlbumDetailViewModel(
) : ViewModel() { ) : ViewModel() {
private val sourceId = libraryRepository.activeSourceId.filterNotNull() private val sourceId = libraryRepository.activeSourceId.filterNotNull()
val album: Flow<Album?> = sourceId.flatMapLatest { libraryRepository.album(it, albumId) } val album: StateFlow<Album?> =
sourceId
.flatMapLatest { libraryRepository.album(it, albumId) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
val songs: Flow<List<Song>> = val songs: StateFlow<List<Song>> =
combine(sourceId, sourceRepository.offline) { id, offline -> id to offline } combine(sourceId, sourceRepository.offline) { id, offline -> id to offline }
.flatMapLatest { (id, offline) -> libraryRepository.albumSongs(id, albumId, offline) } .flatMapLatest { (id, offline) -> libraryRepository.albumSongs(id, albumId, offline) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
val discs: Flow<List<Disc>> = sourceId.flatMapLatest { libraryRepository.albumDiscs(it, albumId) } val discs: StateFlow<List<Disc>> =
sourceId
.flatMapLatest { libraryRepository.albumDiscs(it, albumId) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
val downloads: StateFlow<Map<String, SongDownload>> = downloadRepository.states() val downloads: StateFlow<Map<String, SongDownload>> = downloadRepository.states()
@@ -191,9 +191,9 @@ fun ArtistDetailRoute(
viewModel: ArtistDetailViewModel = koinViewModel(key = artistId) { parametersOf(artistId) }, viewModel: ArtistDetailViewModel = koinViewModel(key = artistId) { parametersOf(artistId) },
playbackController: PlaybackController = koinInject(), playbackController: PlaybackController = koinInject(),
) { ) {
val artist by viewModel.artist.collectAsStateWithLifecycle(initialValue = null) val artist by viewModel.artist.collectAsStateWithLifecycle()
val artistName by viewModel.artistName.collectAsStateWithLifecycle(initialValue = null) val artistName by viewModel.artistName.collectAsStateWithLifecycle()
val albums by viewModel.albums.collectAsStateWithLifecycle(initialValue = null) val albums by viewModel.albums.collectAsStateWithLifecycle()
val bioSnapshots = val bioSnapshots =
remember(viewModel) { remember(viewModel) {
viewModel.artistBio.map { BioSnapshot(known = true, present = it != null, biography = it?.biography) } viewModel.artistBio.map { BioSnapshot(known = true, present = it != null, biography = it?.biography) }
@@ -17,7 +17,6 @@ import com.subtracks.ui.components.ArtistArt
import com.subtracks.ui.components.artistArt import com.subtracks.ui.components.artistArt
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@@ -27,7 +26,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -41,23 +39,27 @@ class ArtistDetailViewModel(
) : ViewModel() { ) : ViewModel() {
private val sourceId = libraryRepository.activeSourceId.filterNotNull() private val sourceId = libraryRepository.activeSourceId.filterNotNull()
val artist: Flow<Artist?> = sourceId.flatMapLatest { libraryRepository.artist(it, artistId) } val artist: StateFlow<Artist?> =
sourceId
.flatMapLatest { libraryRepository.artist(it, artistId) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
val artistName: Flow<String?> = val artistName: StateFlow<String?> =
sourceId.flatMapLatest { id -> sourceId
.flatMapLatest { id ->
combine( combine(
libraryRepository.artist(id, artistId), libraryRepository.artist(id, artistId),
libraryRepository.artistName(id, artistId), libraryRepository.artistName(id, artistId),
) { loaded, fromSongs -> loaded?.name?.takeIf { it.isNotBlank() } ?: fromSongs } ) { loaded, fromSongs -> loaded?.name?.takeIf { it.isNotBlank() } ?: fromSongs }
} }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
val offline: StateFlow<Boolean> = sourceRepository.offline val offline: StateFlow<Boolean> = sourceRepository.offline
val albums: Flow<ArtistAlbums?> = val albums: StateFlow<ArtistAlbums?> =
combine(sourceId, sourceRepository.offline) { id, offline -> id to offline } combine(sourceId, sourceRepository.offline) { id, offline -> id to offline }
.flatMapLatest { (id, offline) -> libraryRepository.artistAlbums(id, artistId, offline) } .flatMapLatest { (id, offline) -> libraryRepository.artistAlbums(id, artistId, offline) }
.map<ArtistAlbums, ArtistAlbums?> { it } .map<ArtistAlbums, ArtistAlbums?> { it }
.onStart { emit(null) } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
val downloadStatus: StateFlow<ListDownloadStatus> = val downloadStatus: StateFlow<ListDownloadStatus> =
sourceId sourceId
@@ -69,7 +71,10 @@ class ArtistDetailViewModel(
.flatMapLatest { downloadRepository.statuses(it, DownloadList.Album) } .flatMapLatest { downloadRepository.statuses(it, DownloadList.Album) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyMap())
val artistBio: Flow<ArtistBio?> = sourceId.flatMapLatest { libraryRepository.artistBio(it, artistId) } val artistBio: StateFlow<ArtistBio?> =
sourceId
.flatMapLatest { libraryRepository.artistBio(it, artistId) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null)
private val _bioLoading = MutableStateFlow(true) private val _bioLoading = MutableStateFlow(true)
val bioLoading: StateFlow<Boolean> = _bioLoading val bioLoading: StateFlow<Boolean> = _bioLoading