diff --git a/config/detekt.yml b/config/detekt.yml
index cee9db0a4..aa3156a5d 100644
--- a/config/detekt.yml
+++ b/config/detekt.yml
@@ -8,6 +8,9 @@ complexity:
- "Composable"
TooManyFunctions:
active: false
+exceptions:
+ TooGenericExceptionCaught:
+ active: false
formatting:
ImportOrdering:
active: false
diff --git a/data/database/config/baseline.xml b/data/database/config/baseline.xml
index e5bc929a5..b51355e95 100644
--- a/data/database/config/baseline.xml
+++ b/data/database/config/baseline.xml
@@ -8,7 +8,5 @@
ReturnCount:ToRelationDatabaseModel.kt$fun RelationMusicBrainzModel.toRelationDatabaseModel( entityId: String, order: Int, ): RelationWithOrder?
SwallowedException:ArtistCreditDao.kt$ArtistCreditDao$ex: Exception
SwallowedException:CollectionEntityDao.kt$CollectionEntityDao$ex: Exception
- TooGenericExceptionCaught:ArtistCreditDao.kt$ArtistCreditDao$ex: Exception
- TooGenericExceptionCaught:CollectionEntityDao.kt$CollectionEntityDao$ex: Exception
diff --git a/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/ArtistDao.kt b/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/ArtistDao.kt
index 3f96be163..573d08fbd 100644
--- a/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/ArtistDao.kt
+++ b/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/ArtistDao.kt
@@ -46,6 +46,10 @@ class ArtistDao(
).executeAsOneOrNull()
}
+ fun delete(artistId: String) {
+ transacter.delete(artistId)
+ }
+
private fun toArtistScaffoldModel(
id: String,
name: String,
diff --git a/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/RelationDao.kt b/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/RelationDao.kt
index 0c7e9f365..9cbd6285c 100644
--- a/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/RelationDao.kt
+++ b/data/database/src/commonMain/kotlin/ly/david/musicsearch/data/database/dao/RelationDao.kt
@@ -63,6 +63,12 @@ class RelationDao(
},
)
+ fun deleteRelationshipsExcludingUrlsByEntity(
+ entityId: String,
+ ) {
+ transacter.deleteRelationshipsExcludingUrlsByEntity(entityId)
+ }
+
fun getEntityUrlRelationships(
entityId: String,
): List {
@@ -73,6 +79,12 @@ class RelationDao(
).executeAsList()
}
+ fun deleteUrlRelationshipsByEntity(
+ entityId: String,
+ ) {
+ transacter.deleteUrlRelationshipssByEntity(entityId)
+ }
+
private fun toRelationListItemModel(
linkedEntityId: String,
linkedEntity: MusicBrainzEntity,
@@ -93,12 +105,6 @@ class RelationDao(
additionalInfo = additionalInfo,
)
- fun deleteRelationshipsExcludingUrlsByEntity(
- entityId: String,
- ) {
- transacter.deleteRelationshipsExcludingUrlsByEntity(entityId)
- }
-
fun getCountOfEachRelationshipType(entityId: String): Flow> =
transacter.countOfEachRelationshipType(entityId)
.asFlow()
diff --git a/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/artist.sq b/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/artist.sq
index 5765e8502..6ec0f35db 100644
--- a/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/artist.sq
+++ b/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/artist.sq
@@ -34,3 +34,7 @@ SELECT
FROM artist a
LEFT JOIN mbid_image mi ON mi.mbid = a.id
WHERE id = :artistId;
+
+delete:
+DELETE FROM artist
+WHERE id = :artistId;
diff --git a/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/relation.sq b/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/relation.sq
index ddbbc9dbc..a5d3a8e64 100644
--- a/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/relation.sq
+++ b/data/database/src/commonMain/sqldelight/ly.david.musicsearch.data.database/relation.sq
@@ -66,6 +66,10 @@ attributes LIKE :query OR additional_info LIKE :query)
ORDER BY linked_entity, label, `order`
LIMIT :limit OFFSET :offset;
+deleteRelationshipsExcludingUrlsByEntity:
+DELETE FROM relation
+WHERE entity_id = :entityId AND linked_entity != "url";
+
getEntityUrlRelationships:
SELECT
`linked_entity_id`,
@@ -82,9 +86,9 @@ WHERE entity_id = :entityId AND linked_entity = "url" AND
attributes LIKE :query OR additional_info LIKE :query)
ORDER BY linked_entity, label, `order`;
-deleteRelationshipsExcludingUrlsByEntity:
+deleteUrlRelationshipssByEntity:
DELETE FROM relation
-WHERE entity_id = :entityId AND linked_entity != "url";
+WHERE entity_id = :entityId AND linked_entity = "url";
countOfEachRelationshipType:
SELECT linked_entity, count(entity_id) AS entity_count
diff --git a/data/musicbrainz/config/baseline.xml b/data/musicbrainz/config/baseline.xml
index bf416f8b1..2487b2112 100644
--- a/data/musicbrainz/config/baseline.xml
+++ b/data/musicbrainz/config/baseline.xml
@@ -9,6 +9,5 @@
MaxLineLength:RelationshipHeaders.kt$"7fd5fbc0-fbf4-4d04-be23-417d50a4dc30" to ("holds phonographic copyright (℗) for" to "phonographic copyright (℗) by")
MaxLineLength:RelationshipHeaders.kt$"fc399d47-23a7-4c28-bfcf-0607a562b644" to ("transliterated/translated track listings" to "transliterated/translated track listing of")
MaxLineLength:RelationshipHeaders.kt$"fd841726-ba3c-47f7-af8e-6734ab6243ff" to ("holds phonographic copyright (℗) for" to "phonographic copyright (℗) by")
- TooGenericExceptionCaught:Logout.kt$Logout$ex: Exception
diff --git a/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/RelationRepositoryImpl.kt b/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/RelationRepositoryImpl.kt
index 52ac690be..5c9eaa4da 100644
--- a/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/RelationRepositoryImpl.kt
+++ b/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/RelationRepositoryImpl.kt
@@ -201,6 +201,10 @@ class RelationRepositoryImpl(
entityId = entityId,
)
+ override fun deleteUrlRelationshipsByEntity(entityId: String) {
+ relationDao.deleteUrlRelationshipsByEntity(entityId)
+ }
+
override fun getCountOfEachRelationshipType(entityId: String): Flow> =
relationDao.getCountOfEachRelationshipType(entityId).map {
it.map { countOfEachRelationshipType: CountOfEachRelationshipType ->
diff --git a/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/artist/ArtistRepositoryImpl.kt b/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/artist/ArtistRepositoryImpl.kt
index 604a2cd42..fef08b3d3 100644
--- a/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/artist/ArtistRepositoryImpl.kt
+++ b/data/repository/src/commonMain/kotlin/ly/david/musicsearch/data/repository/artist/ArtistRepositoryImpl.kt
@@ -1,9 +1,9 @@
package ly.david.musicsearch.data.repository.artist
-import ly.david.musicsearch.data.musicbrainz.models.core.ArtistMusicBrainzModel
-import ly.david.musicsearch.data.musicbrainz.api.MusicBrainzApi
import ly.david.musicsearch.core.models.artist.ArtistScaffoldModel
import ly.david.musicsearch.data.database.dao.ArtistDao
+import ly.david.musicsearch.data.musicbrainz.api.MusicBrainzApi
+import ly.david.musicsearch.data.musicbrainz.models.core.ArtistMusicBrainzModel
import ly.david.musicsearch.data.repository.internal.toRelationWithOrderList
import ly.david.musicsearch.domain.artist.ArtistRepository
import ly.david.musicsearch.domain.relation.RelationRepository
@@ -14,11 +14,23 @@ class ArtistRepositoryImpl(
private val relationRepository: RelationRepository,
) : ArtistRepository {
- override suspend fun lookupArtist(artistId: String): ArtistScaffoldModel {
+ override suspend fun lookupArtist(
+ artistId: String,
+ forceRefresh: Boolean,
+ ): ArtistScaffoldModel {
+ if (forceRefresh) {
+ relationRepository.deleteUrlRelationshipsByEntity(artistId)
+ artistDao.delete(artistId)
+ }
+
val artistScaffoldModel = artistDao.getArtistForDetails(artistId)
val urlRelations = relationRepository.getEntityUrlRelationships(artistId)
val hasUrlsBeenSavedForEntity = relationRepository.hasUrlsBeenSavedFor(artistId)
- if (artistScaffoldModel != null && hasUrlsBeenSavedForEntity) {
+ if (
+ artistScaffoldModel != null &&
+ hasUrlsBeenSavedForEntity &&
+ !forceRefresh
+ ) {
return artistScaffoldModel.copy(
urls = urlRelations,
)
@@ -26,7 +38,10 @@ class ArtistRepositoryImpl(
val artistMusicBrainzModel = musicBrainzApi.lookupArtist(artistId)
cache(artistMusicBrainzModel)
- return lookupArtist(artistId)
+ return lookupArtist(
+ artistId = artistId,
+ forceRefresh = false,
+ )
}
private fun cache(artist: ArtistMusicBrainzModel) {
diff --git a/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/artist/ArtistRepository.kt b/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/artist/ArtistRepository.kt
index a58867943..9c48ce2ae 100644
--- a/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/artist/ArtistRepository.kt
+++ b/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/artist/ArtistRepository.kt
@@ -3,5 +3,8 @@ package ly.david.musicsearch.domain.artist
import ly.david.musicsearch.core.models.artist.ArtistScaffoldModel
interface ArtistRepository {
- suspend fun lookupArtist(artistId: String): ArtistScaffoldModel
+ suspend fun lookupArtist(
+ artistId: String,
+ forceRefresh: Boolean,
+ ): ArtistScaffoldModel
}
diff --git a/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/relation/RelationRepository.kt b/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/relation/RelationRepository.kt
index 4861b300f..b05ac1032 100644
--- a/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/relation/RelationRepository.kt
+++ b/domain/src/commonMain/kotlin/ly/david/musicsearch/domain/relation/RelationRepository.kt
@@ -30,5 +30,9 @@ interface RelationRepository {
entityId: String,
): List
+ fun deleteUrlRelationshipsByEntity(
+ entityId: String,
+ )
+
fun getCountOfEachRelationshipType(entityId: String): Flow>
}
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/area/AreaUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/area/AreaUi.kt
index 097fbae91..449e43036 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/area/AreaUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/area/AreaUi.kt
@@ -170,18 +170,18 @@ internal fun AreaUi(
when (state.tabs[page]) {
AreaTab.DETAILS -> {
DetailsWithErrorHandling(
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(AreaUiEvent.ForceRefresh)
},
scaffoldModel = state.area,
) {
AreaDetailsUi(
area = it,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistPresenter.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistPresenter.kt
index d2e628d7f..2bfe92584 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistPresenter.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistPresenter.kt
@@ -19,7 +19,6 @@ import ly.david.musicsearch.core.models.artist.ArtistScaffoldModel
import ly.david.musicsearch.core.models.getNameWithDisambiguation
import ly.david.musicsearch.core.models.history.LookupHistory
import ly.david.musicsearch.core.models.network.MusicBrainzEntity
-import ly.david.musicsearch.data.common.network.RecoverableNetworkException
import ly.david.musicsearch.data.spotify.ArtistImageRepository
import ly.david.musicsearch.domain.artist.ArtistRepository
import ly.david.musicsearch.domain.history.usecase.IncrementLookupHistory
@@ -61,6 +60,7 @@ internal class ArtistPresenter(
@Composable
override fun present(): ArtistUiState {
var title by rememberSaveable { mutableStateOf(screen.title.orEmpty()) }
+ var isLoading by rememberSaveable { mutableStateOf(true) }
var isError by rememberSaveable { mutableStateOf(false) }
var recordedHistory by rememberSaveable { mutableStateOf(false) }
var query by rememberSaveable { mutableStateOf("") }
@@ -87,14 +87,18 @@ internal class ArtistPresenter(
LaunchedEffect(forceRefreshDetails) {
try {
- val artistScaffoldModel = repository.lookupArtist(screen.id)
+ isLoading = true
+ val artistScaffoldModel = repository.lookupArtist(
+ artistId = screen.id,
+ forceRefresh = forceRefreshDetails,
+ )
if (title.isEmpty()) {
title = artistScaffoldModel.getNameWithDisambiguation()
}
artist = artistScaffoldModel
imageUrl = fetchArtistImage(artistScaffoldModel)
isError = false
- } catch (ex: RecoverableNetworkException) {
+ } catch (ex: Exception) {
logger.e(ex)
isError = true
}
@@ -109,6 +113,8 @@ internal class ArtistPresenter(
)
recordedHistory = true
}
+ isLoading = false
+ forceRefreshDetails = false
}
LaunchedEffect(
@@ -221,6 +227,7 @@ internal class ArtistPresenter(
return ArtistUiState(
title = title,
+ isLoading = isLoading,
isError = isError,
artist = artist,
imageUrl = imageUrl,
@@ -257,6 +264,7 @@ internal class ArtistPresenter(
@Stable
internal data class ArtistUiState(
val title: String,
+ val isLoading: Boolean,
val isError: Boolean,
val artist: ArtistScaffoldModel?,
val imageUrl: String,
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistUi.kt
index 774b9a4c4..c1adccd20 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/artist/ArtistUi.kt
@@ -152,19 +152,19 @@ internal fun ArtistUi(
when (state.tabs[page]) {
ArtistTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
+ showLoading = state.isLoading,
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(ArtistUiEvent.ForceRefresh)
},
scaffoldModel = state.artist,
) { artist ->
ArtistDetailsUi(
artist = artist,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
imageUrl = state.imageUrl,
lazyListState = detailsLazyListState,
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/event/EventUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/event/EventUi.kt
index e3126b5ce..067b98130 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/event/EventUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/event/EventUi.kt
@@ -118,19 +118,18 @@ internal fun EventUi(
when (state.tabs[page]) {
EventTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(EventUiEvent.ForceRefresh)
},
scaffoldModel = state.event,
) { event ->
EventDetailsUi(
event = event,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/genre/GenreUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/genre/GenreUi.kt
index 9d92ac937..040608409 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/genre/GenreUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/genre/GenreUi.kt
@@ -68,11 +68,12 @@ internal fun GenreUi(
},
) { innerPadding ->
DetailsWithErrorHandling(
+ modifier = Modifier.padding(innerPadding),
showError = isError,
- onRetryClick = onRetryClick,
+ onRefresh = onRetryClick,
scaffoldModel = genre,
) {
- FullScreenContent(modifier = Modifier.padding(innerPadding)) {
+ FullScreenContent {
Text(
modifier = Modifier.padding(16.dp),
textAlign = TextAlign.Center,
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/instrument/InstrumentUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/instrument/InstrumentUi.kt
index 3f4ef657c..dd1f61845 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/instrument/InstrumentUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/instrument/InstrumentUi.kt
@@ -118,19 +118,18 @@ internal fun InstrumentUi(
when (state.tabs[page]) {
InstrumentTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(InstrumentUiEvent.ForceRefresh)
},
scaffoldModel = state.instrument,
) { instrument ->
InstrumentDetailsUi(
instrument = instrument,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/label/LabelUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/label/LabelUi.kt
index 1441fac15..d415a52e3 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/label/LabelUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/label/LabelUi.kt
@@ -131,19 +131,18 @@ internal fun LabelUi(
when (state.tabs[page]) {
LabelTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(LabelUiEvent.ForceRefresh)
},
scaffoldModel = state.label,
) { label ->
LabelDetailsUi(
label = label,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/place/PlaceUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/place/PlaceUi.kt
index 3921364e7..30f5b3879 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/place/PlaceUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/place/PlaceUi.kt
@@ -115,19 +115,18 @@ internal fun PlaceUi(
when (state.tabs[page]) {
PlaceTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(PlaceUiEvent.ForceRefresh)
},
scaffoldModel = state.place,
) { place ->
PlaceDetailsUi(
place = place,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/recording/RecordingUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/recording/RecordingUi.kt
index e8d0bd98d..381cbb67c 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/recording/RecordingUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/recording/RecordingUi.kt
@@ -153,19 +153,18 @@ internal fun RecordingUi(
when (state.tabs[page]) {
RecordingTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(RecordingUiEvent.ForceRefresh)
},
scaffoldModel = state.recording,
) { recording ->
RecordingDetailsUi(
recording = recording,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/release/ReleaseUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/release/ReleaseUi.kt
index 730586505..78160dfa5 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/release/ReleaseUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/release/ReleaseUi.kt
@@ -154,19 +154,18 @@ internal fun ReleaseUi(
when (state.tabs[page]) {
ReleaseTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(ReleaseUiEvent.ForceRefresh)
},
scaffoldModel = state.release,
) { release ->
ReleaseDetailsUi(
release = release,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
imageUrl = state.imageUrl,
lazyListState = detailsLazyListState,
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/releasegroup/ReleaseGroupUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/releasegroup/ReleaseGroupUi.kt
index 1092d0fb9..9b102a150 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/releasegroup/ReleaseGroupUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/releasegroup/ReleaseGroupUi.kt
@@ -160,19 +160,18 @@ internal fun ReleaseGroupUi(
when (state.tabs[page]) {
ReleaseGroupTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(ReleaseGroupUiEvent.ForceRefresh)
},
scaffoldModel = state.releaseGroup,
) { releaseGroup ->
ReleaseGroupDetailsUi(
releaseGroup = releaseGroup,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
imageUrl = state.imageUrl,
lazyListState = detailsLazyListState,
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/series/SeriesUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/series/SeriesUi.kt
index 16099e32c..fb6bb8c24 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/series/SeriesUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/series/SeriesUi.kt
@@ -118,19 +118,18 @@ internal fun SeriesUi(
when (state.tabs[page]) {
SeriesTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(SeriesUiEvent.ForceRefresh)
},
scaffoldModel = state.series,
) { series ->
SeriesDetailsUi(
series = series,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/work/WorkUi.kt b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/work/WorkUi.kt
index 3ab6e3fa3..152f6cb10 100644
--- a/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/work/WorkUi.kt
+++ b/shared/feature/details/src/commonMain/kotlin/ly/david/musicsearch/shared/feature/details/work/WorkUi.kt
@@ -117,19 +117,18 @@ internal fun WorkUi(
when (state.tabs[page]) {
WorkTab.DETAILS -> {
DetailsWithErrorHandling(
- modifier = Modifier.padding(innerPadding),
+ modifier = Modifier
+ .padding(innerPadding)
+ .fillMaxSize()
+ .nestedScroll(scrollBehavior.nestedScrollConnection),
showError = state.isError,
- onRetryClick = {
+ onRefresh = {
eventSink(WorkUiEvent.ForceRefresh)
},
scaffoldModel = state.work,
) { work ->
WorkDetailsUi(
work = work,
- modifier = Modifier
- .padding(innerPadding)
- .fillMaxSize()
- .nestedScroll(scrollBehavior.nestedScrollConnection),
filterText = state.query,
lazyListState = detailsLazyListState,
onItemClick = { entity, id, title ->
diff --git a/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NIGHT].png b/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NIGHT].png
index c161cffe8..741f55156 100644
--- a/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NIGHT].png
+++ b/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NIGHT].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:087b8ee70f655fef1bfce6274d70fd65af3ca5c5d70e2f681e0b02a8cb09b163
-size 26005
+oid sha256:6c16288326235f57c02d34f04f5084d9e75f81ca433b837ee8907be5529b5e29
+size 26098
diff --git a/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NOTNIGHT].png b/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NOTNIGHT].png
index 29ed24af5..5cc1e1e45 100644
--- a/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NOTNIGHT].png
+++ b/shared/feature/details/src/test/snapshots/images/ly.david.musicsearch.shared.feature.details.area_AreaUiTest_detailsError[NOTNIGHT].png
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:7a93e41c88a0d5e75c1b1d00496d3ffff293bd59ed56a610cf776841d7de4e00
-size 26231
+oid sha256:833bd5dc002ab5730cdbf2db1db11705edb0f3ebb22a9bfea518ca8d1f9a0b9d
+size 26452
diff --git a/ui/common/config/baseline.xml b/ui/common/config/baseline.xml
index 1c42077f1..0727ee58d 100644
--- a/ui/common/config/baseline.xml
+++ b/ui/common/config/baseline.xml
@@ -3,8 +3,6 @@
MagicNumber:DotsFlashing.kt$4
- TooGenericExceptionCaught:ReleaseGroupsByEntityPresenter.kt$ReleaseGroupsByEntityPresenter$ex: Exception
- TooGenericExceptionCaught:ReleasesByEntityPresenter.kt$ReleasesByEntityPresenter$ex: Exception
UnstableCollections:ExposedDropdownMenuBox.kt$List<MusicBrainzEntity>
UnstableCollections:MultipleChoiceDialog.kt$List<String>
UnstableCollections:TabsBar.kt$List<String>
diff --git a/ui/common/src/commonMain/kotlin/ly/david/ui/common/fullscreen/DetailsWithErrorHandling.kt b/ui/common/src/commonMain/kotlin/ly/david/ui/common/fullscreen/DetailsWithErrorHandling.kt
index ef084a7dc..6284e032e 100644
--- a/ui/common/src/commonMain/kotlin/ly/david/ui/common/fullscreen/DetailsWithErrorHandling.kt
+++ b/ui/common/src/commonMain/kotlin/ly/david/ui/common/fullscreen/DetailsWithErrorHandling.kt
@@ -1,32 +1,57 @@
package ly.david.ui.common.fullscreen
+import androidx.compose.foundation.layout.Box
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
/**
* For displaying a [detailsScreen], showing a loading indicator when [scaffoldModel] is null,
- * handling errors when [showError], and delegating retry with [onRetryClick].
+ * handling errors when [showError], and delegating retry with [onRefresh].
+ * Supports pull to refresh, delegating to [onRefresh].
*/
+@OptIn(ExperimentalMaterialApi::class)
@Composable
fun DetailsWithErrorHandling(
- showError: Boolean,
- onRetryClick: () -> Unit,
scaffoldModel: T?,
+ onRefresh: () -> Unit,
modifier: Modifier = Modifier,
+ showLoading: Boolean = false,
+ showError: Boolean = false,
detailsScreen: @Composable ((T) -> Unit),
) {
- when {
- showError -> {
- FullScreenErrorWithRetry(
- modifier = modifier,
- onClick = onRetryClick,
- )
- }
- scaffoldModel == null -> {
- FullScreenLoadingIndicator(modifier = modifier)
- }
- else -> {
- detailsScreen(scaffoldModel)
+ val refreshState = rememberPullRefreshState(
+ refreshing = showLoading,
+ onRefresh = { onRefresh() },
+ )
+ Box(
+ modifier = modifier.pullRefresh(refreshState),
+ ) {
+ when {
+ showError -> {
+ FullScreenErrorWithRetry(
+ onClick = onRefresh,
+ )
+ }
+
+ scaffoldModel == null -> {
+ FullScreenLoadingIndicator()
+ }
+
+ else -> {
+ detailsScreen(scaffoldModel)
+
+ PullRefreshIndicator(
+ refreshing = showLoading,
+ state = refreshState,
+ modifier = Modifier
+ .align(Alignment.TopCenter),
+ )
+ }
}
}
}