feat: adding retry logic to library

This commit is contained in:
tapframe 2026-05-07 01:33:37 +05:30
parent 5a0b623773
commit 1e75f416e4
2 changed files with 39 additions and 22 deletions

View file

@ -50,6 +50,12 @@ fun LibraryScreen(
var observedOfflineState by remember { mutableStateOf(false) } var observedOfflineState by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val isTraktSource = uiState.sourceMode == LibrarySourceMode.TRAKT val isTraktSource = uiState.sourceMode == LibrarySourceMode.TRAKT
val retryLibraryLoad: () -> Unit = {
NetworkStatusRepository.requestRefresh(force = true)
coroutineScope.launch {
LibraryRepository.pullFromServer(ProfileRepository.activeProfileId)
}
}
LaunchedEffect(networkStatusUiState.condition, isTraktSource) { LaunchedEffect(networkStatusUiState.condition, isTraktSource) {
when (networkStatusUiState.condition) { when (networkStatusUiState.condition) {
@ -110,14 +116,7 @@ fun LibraryScreen(
NuvioNetworkOfflineCard( NuvioNetworkOfflineCard(
condition = networkStatusUiState.condition, condition = networkStatusUiState.condition,
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
onRetry = { onRetry = retryLibraryLoad,
NetworkStatusRepository.requestRefresh(force = true)
if (isTraktSource) {
coroutineScope.launch {
LibraryRepository.pullFromServer(ProfileRepository.activeProfileId)
}
}
},
) )
} else { } else {
HomeEmptyStateCard( HomeEmptyStateCard(
@ -128,6 +127,8 @@ fun LibraryScreen(
stringResource(Res.string.library_load_failed) stringResource(Res.string.library_load_failed)
}, },
message = uiState.errorMessage.orEmpty(), message = uiState.errorMessage.orEmpty(),
actionLabel = stringResource(Res.string.action_retry),
onActionClick = retryLibraryLoad,
) )
} }
} }
@ -139,12 +140,7 @@ fun LibraryScreen(
NuvioNetworkOfflineCard( NuvioNetworkOfflineCard(
condition = networkStatusUiState.condition, condition = networkStatusUiState.condition,
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
onRetry = { onRetry = retryLibraryLoad,
NetworkStatusRepository.requestRefresh(force = true)
coroutineScope.launch {
LibraryRepository.pullFromServer(ProfileRepository.activeProfileId)
}
},
) )
} else { } else {
HomeEmptyStateCard( HomeEmptyStateCard(

View file

@ -36,6 +36,7 @@ private const val LIST_FETCH_CONCURRENCY = 4
private const val SNAPSHOT_CACHE_TTL_MS = 60_000L private const val SNAPSHOT_CACHE_TTL_MS = 60_000L
private const val LIST_TABS_CACHE_TTL_MS = 60_000L private const val LIST_TABS_CACHE_TTL_MS = 60_000L
private const val FORCE_REFRESH_DEDUP_MS = 10_000L private const val FORCE_REFRESH_DEDUP_MS = 10_000L
private const val MAX_VISIBLE_ERROR_MESSAGE_LENGTH = 240
data class TraktLibraryUiState( data class TraktLibraryUiState(
val listTabs: List<TraktListTab> = emptyList(), val listTabs: List<TraktListTab> = emptyList(),
@ -159,21 +160,20 @@ object TraktLibraryRepository {
errorMessage = null, errorMessage = null,
) )
} }
}.onFailure { error -> }
result.exceptionOrNull()?.let { error ->
if (error is CancellationException) throw error if (error is CancellationException) throw error
log.w { "Failed to refresh Trakt library: ${error.message}" } log.w(error) { "Failed to refresh Trakt library" }
}.getOrNull() _uiState.value = _uiState.value.copy(
if (result == null) {
_uiState.value = current.copy(
isLoading = false, isLoading = false,
hasLoaded = true, hasLoaded = true,
errorMessage = getString(Res.string.trakt_library_load_failed), errorMessage = traktLibraryLoadErrorMessage(error),
) )
return return
} }
_uiState.value = result.copy( val snapshot = result.getOrThrow()
_uiState.value = snapshot.copy(
isLoading = false, isLoading = false,
hasLoaded = true, hasLoaded = true,
errorMessage = null, errorMessage = null,
@ -414,6 +414,27 @@ object TraktLibraryRepository {
TraktLibraryStorage.savePayload(json.encodeToString(payload)) TraktLibraryStorage.savePayload(json.encodeToString(payload))
} }
private suspend fun traktLibraryLoadErrorMessage(error: Throwable): String {
val fallback = getString(Res.string.trakt_library_load_failed)
val detail = error.userVisibleMessage()
return when {
detail.isBlank() -> fallback
detail.equals(fallback, ignoreCase = true) -> fallback
else -> detail
}
}
private fun Throwable.userVisibleMessage(): String {
val raw = message?.trim()?.takeIf { it.isNotBlank() }
?: toString().trim()
val firstLine = raw.lines().firstOrNull()?.trim().orEmpty()
return if (firstLine.length <= MAX_VISIBLE_ERROR_MESSAGE_LENGTH) {
firstLine
} else {
firstLine.take(MAX_VISIBLE_ERROR_MESSAGE_LENGTH).trimEnd() + "..."
}
}
private suspend fun fetchListTabs(headers: Map<String, String>): List<TraktListTab> { private suspend fun fetchListTabs(headers: Map<String, String>): List<TraktListTab> {
val watchlistTabs = listOf( val watchlistTabs = listOf(
TraktListTab( TraktListTab(