Skip to content

Commit

Permalink
fix: fix filtering tracks by a release
Browse files Browse the repository at this point in the history
fixes #353
  • Loading branch information
David Ly committed Aug 16, 2023
1 parent 922fc13 commit d3b450f
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 118 deletions.
16 changes: 12 additions & 4 deletions app/src/main/java/ly/david/mbjc/ui/release/ReleaseScaffold.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch
import ly.david.data.domain.listitem.ListItemModel
import ly.david.data.network.MusicBrainzEntity
import ly.david.mbjc.ui.release.details.ReleaseDetailsScreen
import ly.david.mbjc.ui.release.stats.ReleaseStatsScreen
import ly.david.mbjc.ui.release.tracks.TracksInReleaseScreen
import ly.david.mbjc.ui.release.tracks.TracksByReleaseScreen
import ly.david.ui.common.EntityIcon
import ly.david.ui.common.fullscreen.DetailsWithErrorHandling
import ly.david.ui.common.relation.RelationsScreen
Expand Down Expand Up @@ -150,8 +153,10 @@ internal fun ReleaseScaffold(
val detailsLazyListState = rememberLazyListState()

val tracksLazyListState = rememberLazyListState()
var pagedTracksFlow: Flow<PagingData<ListItemModel>> by remember { mutableStateOf(emptyFlow()) }
val tracksLazyPagingItems: LazyPagingItems<ListItemModel> =
rememberFlowWithLifecycleStarted(viewModel.pagedTracks).collectAsLazyPagingItems()
rememberFlowWithLifecycleStarted(pagedTracksFlow)
.collectAsLazyPagingItems()

val relationsLazyListState = rememberLazyListState()
val relationsLazyPagingItems =
Expand Down Expand Up @@ -188,14 +193,17 @@ internal fun ReleaseScaffold(
}

ReleaseTab.TRACKS -> {
TracksInReleaseScreen(
TracksByReleaseScreen(
releaseId = releaseId,
filterText = filterText,
lazyListState = tracksLazyListState,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHostState = snackbarHostState,
lazyListState = tracksLazyListState,
lazyPagingItems = tracksLazyPagingItems,
onPagedTracksFlowChange = { pagedTracksFlow = it },
onRecordingClick = { id, title ->
onItemClick(MusicBrainzEntity.RECORDING, id, title)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,13 @@ package ly.david.mbjc.ui.release

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingData
import androidx.paging.PagingSource
import androidx.paging.cachedIn
import androidx.paging.insertSeparators
import androidx.paging.map
import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.IOException
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import ly.david.data.common.transformThisIfNotNullOrEmpty
import ly.david.data.coverart.ReleaseImageManager
import ly.david.data.coverart.api.CoverArtArchiveApiService
import ly.david.data.domain.listitem.ListItemModel
import ly.david.data.domain.listitem.ListSeparator
import ly.david.data.domain.listitem.TrackListItemModel
import ly.david.data.domain.listitem.toTrackListItemModel
import ly.david.data.domain.paging.LookupEntityRemoteMediator
import ly.david.data.domain.paging.MusicBrainzPagingConfig
import ly.david.data.domain.release.ReleaseRepository
import ly.david.data.domain.release.ReleaseScaffoldModel
import ly.david.data.getDisplayNames
Expand All @@ -38,11 +17,6 @@ import ly.david.data.image.ImageUrlSaver
import ly.david.data.network.MusicBrainzEntity
import ly.david.data.room.history.LookupHistoryDao
import ly.david.data.room.history.RecordLookupHistory
import ly.david.data.room.release.ReleaseDao
import ly.david.data.room.release.tracks.MediumDao
import ly.david.data.room.release.tracks.MediumRoomModel
import ly.david.data.room.release.tracks.TrackDao
import ly.david.data.room.release.tracks.TrackForListItem
import ly.david.ui.common.MusicBrainzEntityViewModel
import ly.david.ui.common.paging.IRelationsList
import ly.david.ui.common.paging.RelationsList
Expand All @@ -51,9 +25,6 @@ import timber.log.Timber

@HiltViewModel
internal class ReleaseScaffoldViewModel @Inject constructor(
private val releaseDao: ReleaseDao,
private val mediumDao: MediumDao,
private val trackDao: TrackDao,
override val lookupHistoryDao: LookupHistoryDao,
override val imageUrlSaver: ImageUrlSaver,
override val coverArtArchiveApiService: CoverArtArchiveApiService,
Expand All @@ -65,17 +36,6 @@ internal class ReleaseScaffoldViewModel @Inject constructor(
IRelationsList by relationsList,
ReleaseImageManager {

private data class ViewModelState(
val releaseId: String = "",
val query: String = "",
)

private val releaseId: MutableStateFlow<String> = MutableStateFlow("")
override val query: MutableStateFlow<String> = MutableStateFlow("")
private val tracksParamState: Flow<ViewModelState> = combine(releaseId, query) { releaseId, query ->
ViewModelState(releaseId, query)
}.distinctUntilChanged()

private var recordedLookup = false
override val entity: MusicBrainzEntity = MusicBrainzEntity.RELEASE
override val title = MutableStateFlow("")
Expand All @@ -90,65 +50,6 @@ internal class ReleaseScaffoldViewModel @Inject constructor(
relationsList.repository = repository
}

private fun loadTracks(releaseId: String) {
this.releaseId.value = releaseId
}

@OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
val pagedTracks: Flow<PagingData<ListItemModel>> =
tracksParamState.filterNot { it.releaseId.isEmpty() }
.flatMapLatest { (releaseId, query) ->
Pager(
config = MusicBrainzPagingConfig.pagingConfig,
remoteMediator = LookupEntityRemoteMediator(
hasEntityBeenStored = { hasReleaseTracksBeenStored(releaseId) },
lookupEntity = { repository.lookupRelease(releaseId) },
deleteLocalEntity = { releaseDao.deleteReleaseById(releaseId) }
),
pagingSourceFactory = {
getPagingSource(releaseId, query)
}
).flow.map { pagingData ->
pagingData
.map(TrackForListItem::toTrackListItemModel)
.insertSeparators { before: TrackListItemModel?, after: TrackListItemModel? ->
if (before?.mediumId != after?.mediumId && after != null) {
val medium: MediumRoomModel =
mediumDao.getMediumForTrack(after.id) ?: return@insertSeparators null

ListSeparator(
id = "${medium.id}",
text = medium.format.orEmpty() +
(medium.position?.toString() ?: "").transformThisIfNotNullOrEmpty { " $it" } +
medium.title.transformThisIfNotNullOrEmpty { " ($it)" }
)
} else {
null
}
}
}
}
.distinctUntilChanged()
.cachedIn(viewModelScope)

private suspend fun hasReleaseTracksBeenStored(releaseId: String): Boolean {
val roomRelease = releaseDao.getReleaseWithFormatTrackCounts(releaseId)
return !roomRelease?.formatTrackCounts.isNullOrEmpty()
}

private fun getPagingSource(releaseId: String, query: String): PagingSource<Int, TrackForListItem> = when {
query.isEmpty() -> {
trackDao.getTracksInRelease(releaseId)
}

else -> {
trackDao.getTracksInReleaseFiltered(
releaseId = releaseId,
query = "%$query%"
)
}
}

fun loadDataForTab(
releaseId: String,
selectedTab: ReleaseTab,
Expand Down Expand Up @@ -189,9 +90,8 @@ internal class ReleaseScaffoldViewModel @Inject constructor(
}
}

ReleaseTab.TRACKS -> loadTracks(releaseId)
ReleaseTab.RELATIONSHIPS -> loadRelations(releaseId)
ReleaseTab.STATS -> {
else -> {
// Not handled here.
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.PagingData
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import ly.david.data.domain.listitem.ListItemModel
import ly.david.data.domain.listitem.ListSeparator
Expand All @@ -20,6 +23,33 @@ import ly.david.ui.common.release.TrackListItem
import ly.david.ui.core.preview.DefaultPreviews
import ly.david.ui.core.theme.PreviewTheme

@Composable
internal fun TracksByReleaseScreen(
releaseId: String,
filterText: String,
lazyPagingItems: LazyPagingItems<ListItemModel>,
modifier: Modifier = Modifier,
snackbarHostState: SnackbarHostState = SnackbarHostState(),
lazyListState: LazyListState = rememberLazyListState(),
onRecordingClick: (String, String) -> Unit = { _, _ -> },
onPagedTracksFlowChange: (Flow<PagingData<ListItemModel>>) -> Unit = {},
viewModel: TracksByReleaseViewModel = hiltViewModel(),
) {
LaunchedEffect(key1 = Unit) {
viewModel.loadTracks(releaseId)
onPagedTracksFlowChange(viewModel.pagedTracks)
}
viewModel.updateQuery(filterText)

TracksByReleaseScreen(
lazyPagingItems = lazyPagingItems,
modifier = modifier,
snackbarHostState = snackbarHostState,
lazyListState = lazyListState,
onRecordingClick = onRecordingClick,
)
}

/**
* Main screen for Release lookup. Shows all tracks in all media in this release.
*
Expand All @@ -28,7 +58,7 @@ import ly.david.ui.core.theme.PreviewTheme
*/
@OptIn(ExperimentalFoundationApi::class)
@Composable
internal fun TracksInReleaseScreen(
private fun TracksByReleaseScreen(
lazyPagingItems: LazyPagingItems<ListItemModel>,
modifier: Modifier = Modifier,
snackbarHostState: SnackbarHostState = SnackbarHostState(),
Expand Down Expand Up @@ -61,6 +91,7 @@ internal fun TracksInReleaseScreen(
}
}

// region Previews
@DefaultPreviews
@Composable
internal fun PreviewTracksInReleaseScreen() {
Expand Down Expand Up @@ -95,9 +126,10 @@ internal fun PreviewTracksInReleaseScreen() {
)
)

TracksInReleaseScreen(
TracksByReleaseScreen(
lazyPagingItems = items.collectAsLazyPagingItems()
)
}
}
}
// endregion
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package ly.david.mbjc.ui.release.tracks

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.ExperimentalPagingApi
import androidx.paging.Pager
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.insertSeparators
import androidx.paging.map
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import ly.david.data.common.transformThisIfNotNullOrEmpty
import ly.david.data.domain.listitem.ListItemModel
import ly.david.data.domain.listitem.ListSeparator
import ly.david.data.domain.listitem.TrackListItemModel
import ly.david.data.domain.listitem.toTrackListItemModel
import ly.david.data.domain.paging.LookupEntityRemoteMediator
import ly.david.data.domain.paging.MusicBrainzPagingConfig
import ly.david.data.domain.release.ReleaseRepository
import ly.david.data.room.release.ReleaseDao
import ly.david.data.room.release.tracks.MediumDao
import ly.david.data.room.release.tracks.MediumRoomModel
import ly.david.data.room.release.tracks.TrackDao
import ly.david.data.room.release.tracks.TrackForListItem

@HiltViewModel
internal class TracksByReleaseViewModel @Inject constructor(
private val repository: ReleaseRepository,
private val releaseDao: ReleaseDao,
private val mediumDao: MediumDao,
private val trackDao: TrackDao,
) : ViewModel() {

private data class ViewModelState(
val releaseId: String = "",
val query: String = "",
)

private val releaseId: MutableStateFlow<String> = MutableStateFlow("")
private val query: MutableStateFlow<String> = MutableStateFlow("")
private val tracksParamState: Flow<ViewModelState> = combine(releaseId, query) { releaseId, query ->
ViewModelState(releaseId, query)
}.distinctUntilChanged()

fun loadTracks(releaseId: String) {
this.releaseId.value = releaseId
}

fun updateQuery(query: String) {
this.query.value = query
}

@OptIn(ExperimentalCoroutinesApi::class, ExperimentalPagingApi::class)
val pagedTracks: Flow<PagingData<ListItemModel>> =
tracksParamState.filterNot { it.releaseId.isEmpty() }
.flatMapLatest { (releaseId, query) ->
Pager(
config = MusicBrainzPagingConfig.pagingConfig,
remoteMediator = LookupEntityRemoteMediator(
hasEntityBeenStored = { hasReleaseTracksBeenStored(releaseId) },
lookupEntity = { repository.lookupRelease(releaseId) },
deleteLocalEntity = { releaseDao.deleteReleaseById(releaseId) }
),
pagingSourceFactory = {
trackDao.getTracksByRelease(
releaseId = releaseId,
query = "%$query%",
)
}
).flow.map { pagingData ->
pagingData
.map(TrackForListItem::toTrackListItemModel)
.insertSeparators { before: TrackListItemModel?, after: TrackListItemModel? ->
if (before?.mediumId != after?.mediumId && after != null) {
val medium: MediumRoomModel =
mediumDao.getMediumForTrack(after.id) ?: return@insertSeparators null

ListSeparator(
id = "${medium.id}",
text = medium.format.orEmpty() +
(medium.position?.toString() ?: "").transformThisIfNotNullOrEmpty { " $it" } +
medium.title.transformThisIfNotNullOrEmpty { " ($it)" }
)
} else {
null
}
}
}
}
.distinctUntilChanged()
.cachedIn(viewModelScope)

private suspend fun hasReleaseTracksBeenStored(releaseId: String): Boolean {
val roomRelease = releaseDao.getReleaseWithFormatTrackCounts(releaseId)
return !roomRelease?.formatTrackCounts.isNullOrEmpty()
}
}
Loading

0 comments on commit d3b450f

Please sign in to comment.