diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.android.kt index 45e63d432..2d77ab611 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.android.kt @@ -22,6 +22,7 @@ actual object MdbListSettingsStorage { private const val useTraktKey = "mdblist_use_trakt" private const val useLetterboxdKey = "mdblist_use_letterboxd" private const val useAudienceKey = "mdblist_use_audience" + private const val useMalKey = "mdblist_use_mal" private val syncKeys = listOf( enabledKey, apiKey, @@ -32,6 +33,7 @@ actual object MdbListSettingsStorage { useTraktKey, useLetterboxdKey, useAudienceKey, + useMalKey, ) private var preferences: SharedPreferences? = null @@ -98,6 +100,12 @@ actual object MdbListSettingsStorage { saveBoolean(useAudienceKey, enabled) } + actual fun loadUseMal(): Boolean? = loadBoolean(useMalKey) + + actual fun saveUseMal(enabled: Boolean) { + saveBoolean(useMalKey, enabled) + } + private fun loadBoolean(key: String): Boolean? = preferences?.let { sharedPreferences -> val scopedKey = ProfileScopedKey.of(key) @@ -125,6 +133,7 @@ actual object MdbListSettingsStorage { loadUseTrakt()?.let { put(useTraktKey, encodeSyncBoolean(it)) } loadUseLetterboxd()?.let { put(useLetterboxdKey, encodeSyncBoolean(it)) } loadUseAudience()?.let { put(useAudienceKey, encodeSyncBoolean(it)) } + loadUseMal()?.let { put(useMalKey, encodeSyncBoolean(it)) } } actual fun replaceFromSyncPayload(payload: JsonObject) { @@ -141,5 +150,6 @@ actual object MdbListSettingsStorage { payload.decodeSyncBoolean(useTraktKey)?.let(::saveUseTrakt) payload.decodeSyncBoolean(useLetterboxdKey)?.let(::saveUseLetterboxd) payload.decodeSyncBoolean(useAudienceKey)?.let(::saveUseAudience) + payload.decodeSyncBoolean(useMalKey)?.let(::saveUseMal) } } diff --git a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.android.kt b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.android.kt index ea40727a4..07d766ea4 100644 --- a/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.android.kt +++ b/composeApp/src/androidMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.android.kt @@ -14,13 +14,13 @@ internal actual object TraktAuthStorage { preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE) } - actual fun loadPayload(): String? = - preferences?.getString(ProfileScopedKey.of(payloadKey), null) + actual fun loadPayload(profileId: Int): String? = + preferences?.getString(ProfileScopedKey.of(payloadKey, profileId), null) - actual fun savePayload(payload: String) { + actual fun savePayload(profileId: Int, payload: String) { preferences ?.edit() - ?.putString(ProfileScopedKey.of(payloadKey), payload) + ?.putString(ProfileScopedKey.of(payloadKey, profileId), payload) ?.apply() } diff --git a/composeApp/src/commonMain/composeResources/drawable/rating_mal.png b/composeApp/src/commonMain/composeResources/drawable/rating_mal.png new file mode 100644 index 000000000..1bbf12ce0 Binary files /dev/null and b/composeApp/src/commonMain/composeResources/drawable/rating_mal.png differ diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 2ca4fd4db..93f36faa9 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -1204,6 +1204,7 @@ Rotten Tomatoes TMDB Trakt + MyAnimeList Unknown Amber Crimson diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt index bbf2e7233..65da8ae65 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/details/components/DetailMetaInfo.kt @@ -50,6 +50,7 @@ import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_AUDIENCE import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_IMDB import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_LETTERBOXD import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_METACRITIC +import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_MAL import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_TMDB import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_TOMATOES import com.nuvio.app.features.mdblist.MdbListMetadataService.PROVIDER_TRAKT @@ -358,22 +359,6 @@ private val ratingVisuals = listOf( valueColor = Color(0xFF01B4E4), format = ::formatWhole, ), - RatingVisuals( - source = PROVIDER_TOMATOES, - displayName = "Rotten Tomatoes", - logo = Res.drawable.rating_rotten_tomatoes, - logoWidth = 16.dp, - valueColor = Color(0xFFFA320A), - format = ::formatPercent, - ), - RatingVisuals( - source = PROVIDER_METACRITIC, - displayName = "Metacritic", - logo = Res.drawable.rating_metacritic, - logoWidth = 16.dp, - valueColor = Color(0xFFFFCC33), - format = ::formatWhole, - ), RatingVisuals( source = PROVIDER_TRAKT, displayName = "Trakt", @@ -390,6 +375,22 @@ private val ratingVisuals = listOf( valueColor = Color(0xFF00E054), format = ::formatOneDecimal, ), + RatingVisuals( + source = PROVIDER_MAL, + displayName = "MyAnimeList", + logo = Res.drawable.rating_mal, + logoWidth = 16.dp, + valueColor = Color(0xFF2E51A2), + format = ::formatOneDecimal, + ), + RatingVisuals( + source = PROVIDER_TOMATOES, + displayName = "Rotten Tomatoes", + logo = Res.drawable.rating_rotten_tomatoes, + logoWidth = 16.dp, + valueColor = Color(0xFFFA320A), + format = ::formatPercent, + ), RatingVisuals( source = PROVIDER_AUDIENCE, displayName = runBlocking { getString(Res.string.rating_audience_score) }, @@ -398,6 +399,14 @@ private val ratingVisuals = listOf( valueColor = Color(0xFFFA320A), format = ::formatPercent, ), + RatingVisuals( + source = PROVIDER_METACRITIC, + displayName = "Metacritic", + logo = Res.drawable.rating_metacritic, + logoWidth = 16.dp, + valueColor = Color(0xFFFFCC33), + format = ::formatWhole, + ), ) private fun formatOneDecimal(value: Double): String { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListMetadataService.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListMetadataService.kt index b1ca1c1e1..1316e3720 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListMetadataService.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListMetadataService.kt @@ -23,6 +23,7 @@ object MdbListMetadataService { const val PROVIDER_TRAKT = "trakt" const val PROVIDER_LETTERBOXD = "letterboxd" const val PROVIDER_AUDIENCE = "audience" + const val PROVIDER_MAL = "mal" val PROVIDER_PRIORITY_ORDER = listOf( PROVIDER_IMDB, @@ -32,6 +33,7 @@ object MdbListMetadataService { PROVIDER_TRAKT, PROVIDER_LETTERBOXD, PROVIDER_AUDIENCE, + PROVIDER_MAL, ) private val log = Logger.withTag("MdbListMetadata") diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettings.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettings.kt index e9002303e..31435d398 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettings.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettings.kt @@ -10,6 +10,7 @@ data class MdbListSettings( val useTrakt: Boolean = true, val useLetterboxd: Boolean = true, val useAudience: Boolean = true, + val useMal: Boolean = true, ) { val hasApiKey: Boolean get() = apiKey.isNotBlank() @@ -23,6 +24,7 @@ data class MdbListSettings( MdbListMetadataService.PROVIDER_TRAKT -> useTrakt MdbListMetadataService.PROVIDER_LETTERBOXD -> useLetterboxd MdbListMetadataService.PROVIDER_AUDIENCE -> useAudience + MdbListMetadataService.PROVIDER_MAL -> useMal else -> false } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsRepository.kt index 6f48eb800..4a7f44e27 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsRepository.kt @@ -19,6 +19,7 @@ object MdbListSettingsRepository { private var useTrakt = true private var useLetterboxd = true private var useAudience = true + private var useMal = true fun ensureLoaded() { if (hasLoaded) return @@ -88,6 +89,10 @@ object MdbListSettingsRepository { useAudience = value MdbListSettingsStorage.saveUseAudience(value) } else return + MdbListMetadataService.PROVIDER_MAL -> if (useMal != value) { + useMal = value + MdbListSettingsStorage.saveUseMal(value) + } else return else -> return } publish() @@ -105,6 +110,7 @@ object MdbListSettingsRepository { useTrakt = MdbListSettingsStorage.loadUseTrakt() ?: true useLetterboxd = MdbListSettingsStorage.loadUseLetterboxd() ?: true useAudience = MdbListSettingsStorage.loadUseAudience() ?: true + useMal = MdbListSettingsStorage.loadUseMal() ?: true publish() } @@ -119,6 +125,7 @@ object MdbListSettingsRepository { useTrakt = useTrakt, useLetterboxd = useLetterboxd, useAudience = useAudience, + useMal = useMal, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.kt index c1a6f51e1..ff9cd57fd 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.kt @@ -21,6 +21,8 @@ internal expect object MdbListSettingsStorage { fun saveUseLetterboxd(enabled: Boolean) fun loadUseAudience(): Boolean? fun saveUseAudience(enabled: Boolean) + fun loadUseMal(): Boolean? + fun saveUseMal(enabled: Boolean) fun exportToSyncPayload(): JsonObject fun replaceFromSyncPayload(payload: JsonObject) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/MdbListSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/MdbListSettingsPage.kt index 3a7a2c40a..49842bf34 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/MdbListSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/MdbListSettingsPage.kt @@ -34,6 +34,7 @@ import nuvio.composeapp.generated.resources.settings_mdb_section_title import nuvio.composeapp.generated.resources.source_audience_score import nuvio.composeapp.generated.resources.source_imdb import nuvio.composeapp.generated.resources.source_letterboxd +import nuvio.composeapp.generated.resources.source_mal import nuvio.composeapp.generated.resources.source_metacritic import nuvio.composeapp.generated.resources.source_rotten_tomatoes import nuvio.composeapp.generated.resources.source_tmdb @@ -117,6 +118,7 @@ private fun ProviderRows( MdbListMetadataService.PROVIDER_TRAKT to Res.string.source_trakt, MdbListMetadataService.PROVIDER_LETTERBOXD to Res.string.source_letterboxd, MdbListMetadataService.PROVIDER_AUDIENCE to Res.string.source_audience_score, + MdbListMetadataService.PROVIDER_MAL to Res.string.source_mal, ) providers.forEachIndexed { index, (providerId, providerLabelRes) -> diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthRepository.kt index c1f503d28..8214c706c 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthRepository.kt @@ -74,19 +74,31 @@ object TraktAuthRepository : TrackingAuthProvider { } private var hasLoaded = false + private var currentProfileId: Int = 1 + private var profileGeneration: Long = 0L private var authState = TraktAuthState() override fun ensureLoaded() { - if (hasLoaded) return - loadFromDisk() + ensureLoaded(ProfileRepository.activeProfileId) } override fun onProfileChanged() { - loadFromDisk() + onProfileChanged(ProfileRepository.activeProfileId) + } + + fun ensureLoaded(profileId: Int) { + if (hasLoaded && currentProfileId == profileId) return + loadFromDisk(profileId) + } + + fun onProfileChanged(profileId: Int) { + loadFromDisk(profileId) } override fun clearLocalState() { hasLoaded = false + currentProfileId = 1 + profileGeneration += 1L authState = TraktAuthState() publish() } @@ -95,16 +107,16 @@ object TraktAuthRepository : TrackingAuthProvider { TraktAuthStorage.removeProfile(profileId) } - fun snapshot(): TraktAuthUiState { - ensureLoaded() + fun snapshot(profileId: Int = ProfileRepository.activeProfileId): TraktAuthUiState { + ensureLoaded(profileId) return _uiState.value } fun hasRequiredCredentials(): Boolean = TraktConfig.CLIENT_ID.isNotBlank() && TraktConfig.CLIENT_SECRET.isNotBlank() - fun onConnectRequested(): String? { - ensureLoaded() + fun onConnectRequested(profileId: Int = ProfileRepository.activeProfileId): String? { + ensureLoaded(profileId) if (!hasRequiredCredentials()) { publish(errorMessage = localizedString(Res.string.trakt_missing_credentials)) return null @@ -115,7 +127,7 @@ object TraktAuthRepository : TrackingAuthProvider { pendingAuthorizationState = oauthState, pendingAuthorizationStartedAtMillis = TraktPlatformClock.nowEpochMs(), ) - persist() + persist(profileId) publish( statusMessage = localizedString(Res.string.trakt_complete_sign_in_browser), errorMessage = null, @@ -124,21 +136,21 @@ object TraktAuthRepository : TrackingAuthProvider { return buildAuthorizationUrl(oauthState) } - fun pendingAuthorizationUrl(): String? { - ensureLoaded() + fun pendingAuthorizationUrl(profileId: Int = ProfileRepository.activeProfileId): String? { + ensureLoaded(profileId) val oauthState = authState.pendingAuthorizationState ?: return null return buildAuthorizationUrl(oauthState) } - fun onCancelAuthorization() { - ensureLoaded() + fun onCancelAuthorization(profileId: Int = ProfileRepository.activeProfileId) { + ensureLoaded(profileId) clearPendingAuthorization() - persist() + persist(profileId) publish(statusMessage = null, errorMessage = null) } - fun onCancelDeviceFlow() { - onCancelAuthorization() + fun onCancelDeviceFlow(profileId: Int = ProfileRepository.activeProfileId) { + onCancelAuthorization(profileId) } fun onAuthLaunchFailed(reason: String) { @@ -146,7 +158,8 @@ object TraktAuthRepository : TrackingAuthProvider { } fun onAuthCallbackReceived(callbackUrl: String) { - ensureLoaded() + val profileId = ProfileRepository.activeProfileId + ensureLoaded(profileId) if (!callbackUrl.startsWith("${TraktConfig.REDIRECT_URI}?", ignoreCase = true) && !callbackUrl.equals(TraktConfig.REDIRECT_URI, ignoreCase = true) ) { @@ -154,7 +167,7 @@ object TraktAuthRepository : TrackingAuthProvider { } scope.launch { - completeAuthorizationFromCallback(callbackUrl) + completeAuthorizationFromCallback(callbackUrl, profileId) } } @@ -167,11 +180,11 @@ object TraktAuthRepository : TrackingAuthProvider { private fun isTraktAuthCallback(url: String): Boolean = url == TraktConfig.REDIRECT_URI || url.startsWith("${TraktConfig.REDIRECT_URI}?") - suspend fun authorizedHeaders(): Map? { - ensureLoaded() + suspend fun authorizedHeaders(profileId: Int = currentProfileId): Map? { + ensureLoaded(profileId) if (!authState.isAuthenticated) return null - val hasValidToken = refreshTokenIfNeeded(force = false) + val hasValidToken = refreshTokenIfNeeded(force = false, profileId = profileId) if (!hasValidToken) return null val accessToken = authState.accessToken?.trim().orEmpty() @@ -184,9 +197,9 @@ object TraktAuthRepository : TrackingAuthProvider { ) } - suspend fun refreshUserSettings(): String? { - ensureLoaded() - val headers = authorizedHeaders() ?: return null + suspend fun refreshUserSettings(profileId: Int = currentProfileId): String? { + ensureLoaded(profileId) + val headers = authorizedHeaders(profileId) ?: return null val response = runCatching { httpGetTextWithHeaders( url = "$BASE_URL/users/settings", @@ -205,19 +218,19 @@ object TraktAuthRepository : TrackingAuthProvider { username = parsed.user?.username, userSlug = parsed.user?.ids?.slug, ) - persist() + persist(profileId) publish() return authState.username } - fun onDisconnectRequested() { - ensureLoaded() + fun onDisconnectRequested(profileId: Int = currentProfileId) { + ensureLoaded(profileId) scope.launch { - disconnect() + disconnect(profileId) } } - private suspend fun completeAuthorizationFromCallback(callbackUrl: String) { + private suspend fun completeAuthorizationFromCallback(callbackUrl: String, profileId: Int = currentProfileId) { publish(isLoading = true, errorMessage = null) val parsedUrl = runCatching { Url(callbackUrl) } @@ -228,7 +241,7 @@ object TraktAuthRepository : TrackingAuthProvider { if (parsedUrl == null) { clearPendingAuthorization() - persist() + persist(profileId) publish( isLoading = false, errorMessage = localizedString(Res.string.trakt_invalid_callback), @@ -241,7 +254,7 @@ object TraktAuthRepository : TrackingAuthProvider { val errorDescription = parsedUrl.parameters["error_description"] ?: localizedString(Res.string.trakt_authorization_denied) clearPendingAuthorization() - persist() + persist(profileId) publish( isLoading = false, errorMessage = errorDescription, @@ -252,7 +265,7 @@ object TraktAuthRepository : TrackingAuthProvider { val code = parsedUrl.parameters["code"].orEmpty().trim() if (code.isBlank()) { clearPendingAuthorization() - persist() + persist(profileId) publish( isLoading = false, errorMessage = localizedString(Res.string.trakt_missing_auth_code), @@ -264,7 +277,7 @@ object TraktAuthRepository : TrackingAuthProvider { val callbackState = parsedUrl.parameters["state"].orEmpty().trim() if (!expectedState.isNullOrBlank() && callbackState != expectedState) { clearPendingAuthorization() - persist() + persist(profileId) publish( isLoading = false, errorMessage = localizedString(Res.string.trakt_invalid_callback_state), @@ -272,10 +285,10 @@ object TraktAuthRepository : TrackingAuthProvider { return } - exchangeAuthorizationCode(code) + exchangeAuthorizationCode(code, profileId) } - private suspend fun exchangeAuthorizationCode(code: String) { + private suspend fun exchangeAuthorizationCode(code: String, profileId: Int = currentProfileId) { val body = json.encodeToString( TraktAuthorizationCodeRequest( code = code, @@ -298,7 +311,7 @@ object TraktAuthRepository : TrackingAuthProvider { if (response == null) { clearPendingAuthorization() - persist() + persist(profileId) publish(isLoading = false, errorMessage = localizedString(Res.string.trakt_sign_in_complete_failed)) return } @@ -309,7 +322,7 @@ object TraktAuthRepository : TrackingAuthProvider { if (parsed == null) { clearPendingAuthorization() - persist() + persist(profileId) publish(isLoading = false, errorMessage = localizedString(Res.string.trakt_invalid_token_response)) return } @@ -323,8 +336,8 @@ object TraktAuthRepository : TrackingAuthProvider { pendingAuthorizationState = null, pendingAuthorizationStartedAtMillis = null, ) - persist() - refreshUserSettings() + persist(profileId) + refreshUserSettings(profileId) publish( isLoading = false, statusMessage = localizedString(Res.string.trakt_connected_status), @@ -332,7 +345,7 @@ object TraktAuthRepository : TrackingAuthProvider { ) } - private suspend fun disconnect() { + private suspend fun disconnect(profileId: Int = currentProfileId) { publish(isLoading = true, errorMessage = null) val token = authState.accessToken?.takeIf { it.isNotBlank() } @@ -356,9 +369,9 @@ object TraktAuthRepository : TrackingAuthProvider { } } - TraktCredentialSync.deleteRemote() + TraktCredentialSync.deleteRemote(profileId) authState = TraktAuthState() - persist() + persist(profileId) publish( isLoading = false, statusMessage = localizedString(Res.string.trakt_disconnected_status), @@ -366,9 +379,8 @@ object TraktAuthRepository : TrackingAuthProvider { ) } - private suspend fun refreshTokenIfNeeded(force: Boolean): Boolean = refreshMutex.withLock { + private suspend fun refreshTokenIfNeeded(force: Boolean, profileId: Int = currentProfileId): Boolean = refreshMutex.withLock { if (!hasRequiredCredentials()) return@withLock false - val profileId = ProfileRepository.activeProfileId val refreshToken = authState.refreshToken?.takeIf { it.isNotBlank() } ?: return@withLock false @@ -430,14 +442,14 @@ object TraktAuthRepository : TrackingAuthProvider { createdAt = parsed.createdAt, expiresIn = parsed.expiresIn, ) - persist() + persist(profileId) publish() true } private suspend fun invalidateCredentials(profileId: Int) { authState = TraktAuthState() - persist() + persist(profileId) publish( isLoading = false, statusMessage = null, @@ -446,9 +458,11 @@ object TraktAuthRepository : TrackingAuthProvider { TraktCredentialSync.deleteRemote(profileId) } - private fun loadFromDisk() { + private fun loadFromDisk(profileId: Int) { + currentProfileId = profileId + profileGeneration += 1L hasLoaded = true - val payload = TraktAuthStorage.loadPayload().orEmpty().trim() + val payload = TraktAuthStorage.loadPayload(profileId).orEmpty().trim() authState = if (payload.isBlank()) { TraktAuthState() } else { @@ -499,8 +513,8 @@ object TraktAuthRepository : TrackingAuthProvider { ) } - private fun persist() { - TraktAuthStorage.savePayload(json.encodeToString(authState)) + private fun persist(profileId: Int = currentProfileId) { + TraktAuthStorage.savePayload(profileId, json.encodeToString(authState)) } private fun buildAuthorizationUrl(state: String): String { @@ -525,6 +539,7 @@ object TraktAuthRepository : TrackingAuthProvider { return nowSeconds >= (expiresAtSeconds - 60) } + private fun localizedString(resource: StringResource): String = runBlocking { getString(resource) } } internal enum class TraktTokenRefreshResponseAction { @@ -588,4 +603,3 @@ private data class TraktUserDto( private data class TraktUserIdsDto( val slug: String? = null, ) - private fun localizedString(resource: StringResource): String = runBlocking { getString(resource) } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.kt index 5337d208a..d0d788708 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.kt @@ -1,7 +1,7 @@ package com.nuvio.app.features.trakt internal expect object TraktAuthStorage { - fun loadPayload(): String? - fun savePayload(payload: String) + fun loadPayload(profileId: Int): String? + fun savePayload(profileId: Int, payload: String) fun removeProfile(profileId: Int) } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt index 9980e3bd0..7b6f649a4 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/core/storage/PlatformLocalAccountDataCleaner.ios.kt @@ -52,6 +52,7 @@ internal actual object PlatformLocalAccountDataCleaner { "mdblist_use_trakt", "mdblist_use_letterboxd", "mdblist_use_audience", + "mdblist_use_mal", "trakt_auth_payload", "simkl_auth_metadata", "simkl_sync_snapshot", diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.ios.kt index d05e79f14..3c4297207 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/mdblist/MdbListSettingsStorage.ios.kt @@ -20,6 +20,7 @@ actual object MdbListSettingsStorage { private const val useTraktKey = "mdblist_use_trakt" private const val useLetterboxdKey = "mdblist_use_letterboxd" private const val useAudienceKey = "mdblist_use_audience" + private const val useMalKey = "mdblist_use_mal" private val syncKeys = listOf( enabledKey, apiKey, @@ -30,6 +31,7 @@ actual object MdbListSettingsStorage { useTraktKey, useLetterboxdKey, useAudienceKey, + useMalKey, ) actual fun loadEnabled(): Boolean? = loadBoolean(enabledKey) @@ -87,6 +89,12 @@ actual object MdbListSettingsStorage { saveBoolean(useAudienceKey, enabled) } + actual fun loadUseMal(): Boolean? = loadBoolean(useMalKey) + + actual fun saveUseMal(enabled: Boolean) { + saveBoolean(useMalKey, enabled) + } + private fun loadBoolean(key: String): Boolean? { val defaults = NSUserDefaults.standardUserDefaults val scopedKey = ProfileScopedKey.of(key) @@ -111,6 +119,7 @@ actual object MdbListSettingsStorage { loadUseTrakt()?.let { put(useTraktKey, encodeSyncBoolean(it)) } loadUseLetterboxd()?.let { put(useLetterboxdKey, encodeSyncBoolean(it)) } loadUseAudience()?.let { put(useAudienceKey, encodeSyncBoolean(it)) } + loadUseMal()?.let { put(useMalKey, encodeSyncBoolean(it)) } } actual fun replaceFromSyncPayload(payload: JsonObject) { @@ -127,5 +136,6 @@ actual object MdbListSettingsStorage { payload.decodeSyncBoolean(useTraktKey)?.let(::saveUseTrakt) payload.decodeSyncBoolean(useLetterboxdKey)?.let(::saveUseLetterboxd) payload.decodeSyncBoolean(useAudienceKey)?.let(::saveUseAudience) + payload.decodeSyncBoolean(useMalKey)?.let(::saveUseMal) } } diff --git a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.ios.kt b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.ios.kt index 55b9a6e3f..e5dfbe1a1 100644 --- a/composeApp/src/iosMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.ios.kt +++ b/composeApp/src/iosMain/kotlin/com/nuvio/app/features/trakt/TraktAuthStorage.ios.kt @@ -6,11 +6,11 @@ import platform.Foundation.NSUserDefaults internal actual object TraktAuthStorage { private const val payloadKey = "trakt_auth_payload" - actual fun loadPayload(): String? = - NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(payloadKey)) + actual fun loadPayload(profileId: Int): String? = + NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(payloadKey, profileId)) - actual fun savePayload(payload: String) { - NSUserDefaults.standardUserDefaults.setObject(payload, forKey = ProfileScopedKey.of(payloadKey)) + actual fun savePayload(profileId: Int, payload: String) { + NSUserDefaults.standardUserDefaults.setObject(payload, forKey = ProfileScopedKey.of(payloadKey, profileId)) } actual fun removeProfile(profileId: Int) {