From 32449a7a15e8eda3040ba83095e3ce070912a6b2 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 22 Jul 2026 14:12:42 +0530 Subject: [PATCH] feat(tracking): expose manual provider refresh --- .../composeResources/values/strings.xml | 2 + .../app/features/library/LibraryScreen.kt | 18 ++++++++- .../features/settings/TrackingSettingsPage.kt | 40 ++++++++++++++++++- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index a4501dc07..1083f44c6 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1138,6 +1138,7 @@ Simkl did not accept the sign-in callback. Please try again. The Simkl sign-in request expired. Start again. Could not complete Simkl sign in. Please try again. + Sync now Simkl authorization expired or was revoked. Connect again. Simkl Simkl watchlist selected @@ -1651,6 +1652,7 @@ Select type Show horizontal shelves Show vertical grid + Refresh library Couldn't load library Other Oldest added diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt index 434f3c9a1..51bcf1307 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryScreen.kt @@ -84,6 +84,7 @@ import com.nuvio.app.features.home.components.HomePosterCard import com.nuvio.app.features.home.components.HomeSkeletonRow import com.nuvio.app.features.home.components.posterGridColumnCountForWidth import com.nuvio.app.features.profiles.ProfileRepository +import com.nuvio.app.features.tracking.TrackingRefreshIntent import com.nuvio.app.features.watched.WatchedRepository import com.nuvio.app.features.watching.application.WatchingState import kotlinx.coroutines.flow.Flow @@ -170,7 +171,10 @@ fun LibraryScreen( val retryLibraryLoad: () -> Unit = { NetworkStatusRepository.requestRefresh(force = true) coroutineScope.launch { - LibraryRepository.pullFromServer(ProfileRepository.activeProfileId) + LibraryRepository.pullFromServer( + profileId = ProfileRepository.activeProfileId, + refreshIntent = TrackingRefreshIntent.USER_INITIATED, + ) } } @@ -256,6 +260,18 @@ fun LibraryScreen( modifier = Modifier.padding(horizontal = 16.dp), actions = { if (sourceMode == LibraryViewMode.Saved) { + if (isRemoteSource) { + IconButton( + onClick = retryLibraryLoad, + enabled = !uiState.isLoading, + ) { + Icon( + imageVector = Icons.Rounded.Refresh, + contentDescription = stringResource(Res.string.library_refresh), + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } val targetLayout = if (displaySettings.layoutMode == LibraryLayoutMode.HORIZONTAL) { LibraryLayoutMode.VERTICAL } else { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/TrackingSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/TrackingSettingsPage.kt index 038366dac..87f386044 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/TrackingSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/TrackingSettingsPage.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue @@ -38,18 +39,21 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.nuvio.app.features.library.LibrarySourceMode import com.nuvio.app.features.profiles.ProfileRepository import com.nuvio.app.features.simkl.SimklAuthError import com.nuvio.app.features.simkl.SimklAuthRepository import com.nuvio.app.features.simkl.SimklAuthUiState import com.nuvio.app.features.simkl.SimklConnectionMode +import com.nuvio.app.features.simkl.SimklSyncRepository import com.nuvio.app.features.trakt.TraktAuthRepository import com.nuvio.app.features.trakt.TraktAuthUiState import com.nuvio.app.features.trakt.TraktConnectionMode import com.nuvio.app.features.trakt.TraktContinueWatchingDaysOptions import com.nuvio.app.features.trakt.MoreLikeThisSourcePreference import com.nuvio.app.features.tracking.TrackingProviderId +import com.nuvio.app.features.tracking.TrackingRefreshIntent import com.nuvio.app.features.tracking.TrackingSettingsRepository import com.nuvio.app.features.tracking.TrackingSettingsUiState import com.nuvio.app.features.tracking.WatchProgressSource @@ -94,6 +98,7 @@ import nuvio.composeapp.generated.resources.settings_simkl_missing_credentials import nuvio.composeapp.generated.resources.settings_simkl_open_login import nuvio.composeapp.generated.resources.settings_simkl_sign_in_description import nuvio.composeapp.generated.resources.settings_simkl_sign_in_failed +import nuvio.composeapp.generated.resources.settings_simkl_sync_now import nuvio.composeapp.generated.resources.settings_simkl_visit import nuvio.composeapp.generated.resources.tracking_library_source_simkl_selected import nuvio.composeapp.generated.resources.tracking_source_simkl @@ -781,6 +786,12 @@ private fun SimklConnectionCard( isTablet: Boolean, uiState: SimklAuthUiState, ) { + val syncState by remember { + SimklSyncRepository.ensureLoaded() + SimklSyncRepository.state + }.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() + ProviderConnectionCard( isTablet = isTablet, mode = when (uiState.mode) { @@ -801,8 +812,10 @@ private fun SimklConnectionCard( connectLabel = stringResource(Res.string.settings_simkl_connect), openLoginLabel = stringResource(Res.string.settings_simkl_open_login), disconnectLabel = stringResource(Res.string.settings_simkl_disconnect), + syncLabel = stringResource(Res.string.settings_simkl_sync_now), + isSyncing = syncState.isLoading, missingCredentialsMessage = stringResource(Res.string.settings_simkl_missing_credentials), - errorMessage = simklErrorMessage(uiState.error), + errorMessage = simklErrorMessage(uiState.error) ?: syncState.errorMessage, websiteLabel = stringResource(Res.string.settings_simkl_visit), websiteUrl = SIMKL_WEBSITE_URL, onConnectRequested = SimklAuthRepository::onConnectRequested, @@ -811,6 +824,11 @@ private fun SimklConnectionCard( ?: SimklAuthRepository.onConnectRequested() }, onCancelAuthorization = SimklAuthRepository::onCancelAuthorization, + onSyncRequested = { + scope.launch { + SimklSyncRepository.refresh(TrackingRefreshIntent.USER_INITIATED) + } + }, onDisconnect = SimklAuthRepository::onDisconnectRequested, ) } @@ -829,6 +847,8 @@ private fun ProviderConnectionCard( connectLabel: String, openLoginLabel: String, disconnectLabel: String, + syncLabel: String? = null, + isSyncing: Boolean = false, missingCredentialsMessage: String, statusMessage: String? = null, errorMessage: String? = null, @@ -837,6 +857,7 @@ private fun ProviderConnectionCard( onConnectRequested: () -> String?, onResumeAuthorization: () -> String?, onCancelAuthorization: () -> Unit, + onSyncRequested: (() -> Unit)? = null, onDisconnect: () -> Unit, ) { val uriHandler = LocalUriHandler.current @@ -871,9 +892,24 @@ private fun ProviderConnectionCard( style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.onSurfaceVariant, ) + if (syncLabel != null && onSyncRequested != null) { + Button( + onClick = onSyncRequested, + enabled = !isLoading && !isSyncing, + ) { + if (isSyncing) { + NuvioLoadingIndicator( + color = MaterialTheme.colorScheme.onPrimary, + modifier = Modifier.size(18.dp), + ) + } else { + Text(syncLabel) + } + } + } Button( onClick = onDisconnect, - enabled = !isLoading, + enabled = !isLoading && !isSyncing, colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.surfaceVariant, contentColor = MaterialTheme.colorScheme.onSurface,