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 45e63d43..2d77ab61 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 cc8e8436..d694069b 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 00000000..1bbf12ce
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 1e20233a..f00ffc65 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -1151,6 +1151,7 @@
Rotten Tomatoes
TMDB
Trakt
+ MyAnimeList
Unknown
Amber
Crimson
diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt
index f81af882..703b88c4 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/sync/SyncManager.kt
@@ -427,7 +427,7 @@ object SyncManager {
continue
}
- TraktAuthRepository.ensureLoaded()
+ TraktAuthRepository.ensureLoaded(profileId)
TraktSettingsRepository.ensureLoaded()
val traktAuthenticated = TraktAuthRepository.isAuthenticated.value
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 bbf2e723..65da8ae6 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/library/LibraryRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt
index 06c102f8..cb9fbebe 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/library/LibraryRepository.kt
@@ -151,7 +151,7 @@ object LibraryRepository {
TraktSettingsRepository.onProfileChanged()
if (!loadFromDisk(profileId)) return
- TraktAuthRepository.onProfileChanged()
+ TraktAuthRepository.onProfileChanged(profileId)
TraktLibraryRepository.onProfileChanged()
if (TraktAuthRepository.isAuthenticated.value) {
TraktLibraryRepository.preloadListTabsAsync()
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 b1ca1c1e..1316e372 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 e9002303..31435d39 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 6f48eb80..4a7f44e2 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 c1a6f51e..ff9cd57f 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/profiles/ProfileRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt
index c1c7ca36..c7e1faa0 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileRepository.kt
@@ -155,7 +155,7 @@ object ProfileRepository {
persist()
WatchedRepository.onProfileChanged(profileIndex)
TraktSettingsRepository.onProfileChanged()
- TraktAuthRepository.onProfileChanged()
+ TraktAuthRepository.onProfileChanged(profileIndex)
LibraryRepository.onProfileChanged(profileIndex)
LibraryDisplaySettingsRepository.onProfileChanged()
WatchProgressRepository.onProfileChanged(profileIndex)
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 3a7a2c40..49842bf3 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 20fa9eb5..a9e3308e 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
@@ -48,33 +48,37 @@ object TraktAuthRepository {
val isAuthenticated: StateFlow = _isAuthenticated.asStateFlow()
private var hasLoaded = false
+ private var currentProfileId: Int = 1
+ private var profileGeneration: Long = 0L
private var authState = TraktAuthState()
- fun ensureLoaded() {
- if (hasLoaded) return
- loadFromDisk()
+ fun ensureLoaded(profileId: Int = ProfileRepository.activeProfileId) {
+ if (hasLoaded && currentProfileId == profileId) return
+ loadFromDisk(profileId)
}
- fun onProfileChanged() {
- loadFromDisk()
+ fun onProfileChanged(profileId: Int) {
+ loadFromDisk(profileId)
}
fun clearLocalState() {
hasLoaded = false
+ currentProfileId = 1
+ profileGeneration += 1L
authState = TraktAuthState()
publish()
}
- 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
@@ -85,7 +89,7 @@ object TraktAuthRepository {
pendingAuthorizationState = oauthState,
pendingAuthorizationStartedAtMillis = TraktPlatformClock.nowEpochMs(),
)
- persist()
+ persist(profileId)
publish(
statusMessage = localizedString(Res.string.trakt_complete_sign_in_browser),
errorMessage = null,
@@ -94,21 +98,21 @@ object TraktAuthRepository {
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) {
@@ -116,7 +120,8 @@ object TraktAuthRepository {
}
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)
) {
@@ -124,15 +129,15 @@ object TraktAuthRepository {
}
scope.launch {
- completeAuthorizationFromCallback(callbackUrl)
+ completeAuthorizationFromCallback(callbackUrl, profileId)
}
}
- 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()
@@ -145,9 +150,9 @@ object TraktAuthRepository {
)
}
- 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",
@@ -166,19 +171,19 @@ object TraktAuthRepository {
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) }
@@ -189,7 +194,7 @@ object TraktAuthRepository {
if (parsedUrl == null) {
clearPendingAuthorization()
- persist()
+ persist(profileId)
publish(
isLoading = false,
errorMessage = localizedString(Res.string.trakt_invalid_callback),
@@ -202,7 +207,7 @@ object TraktAuthRepository {
val errorDescription = parsedUrl.parameters["error_description"]
?: localizedString(Res.string.trakt_authorization_denied)
clearPendingAuthorization()
- persist()
+ persist(profileId)
publish(
isLoading = false,
errorMessage = errorDescription,
@@ -213,7 +218,7 @@ object TraktAuthRepository {
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),
@@ -225,7 +230,7 @@ object TraktAuthRepository {
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),
@@ -233,10 +238,10 @@ object TraktAuthRepository {
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,
@@ -259,7 +264,7 @@ object TraktAuthRepository {
if (response == null) {
clearPendingAuthorization()
- persist()
+ persist(profileId)
publish(isLoading = false, errorMessage = localizedString(Res.string.trakt_sign_in_complete_failed))
return
}
@@ -270,7 +275,7 @@ object TraktAuthRepository {
if (parsed == null) {
clearPendingAuthorization()
- persist()
+ persist(profileId)
publish(isLoading = false, errorMessage = localizedString(Res.string.trakt_invalid_token_response))
return
}
@@ -284,8 +289,8 @@ object TraktAuthRepository {
pendingAuthorizationState = null,
pendingAuthorizationStartedAtMillis = null,
)
- persist()
- refreshUserSettings()
+ persist(profileId)
+ refreshUserSettings(profileId)
publish(
isLoading = false,
statusMessage = localizedString(Res.string.trakt_connected_status),
@@ -293,7 +298,7 @@ object TraktAuthRepository {
)
}
- private suspend fun disconnect() {
+ private suspend fun disconnect(profileId: Int = currentProfileId) {
publish(isLoading = true, errorMessage = null)
val token = authState.accessToken?.takeIf { it.isNotBlank() }
@@ -317,9 +322,9 @@ object TraktAuthRepository {
}
}
- TraktCredentialSync.deleteRemote()
+ TraktCredentialSync.deleteRemote(profileId)
authState = TraktAuthState()
- persist()
+ persist(profileId)
publish(
isLoading = false,
statusMessage = localizedString(Res.string.trakt_disconnected_status),
@@ -327,9 +332,8 @@ object TraktAuthRepository {
)
}
- 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
@@ -391,14 +395,14 @@ object TraktAuthRepository {
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,
@@ -407,9 +411,11 @@ object TraktAuthRepository {
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 {
@@ -460,8 +466,8 @@ object TraktAuthRepository {
)
}
- 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 {
@@ -486,6 +492,7 @@ object TraktAuthRepository {
return nowSeconds >= (expiresAtSeconds - 60)
}
+ private fun localizedString(resource: StringResource): String = runBlocking { getString(resource) }
}
internal enum class TraktTokenRefreshResponseAction {
@@ -549,4 +556,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 3527be20..12cb2420 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,6 +1,6 @@
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)
}
diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt
index 27ee2076..cb2cae03 100644
--- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt
+++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watched/WatchedRepository.kt
@@ -270,7 +270,7 @@ object WatchedRepository {
)
suspend fun pullFromServer(profileId: Int) {
- TraktAuthRepository.ensureLoaded()
+ TraktAuthRepository.ensureLoaded(profileId)
TraktSettingsRepository.ensureLoaded()
refreshForSource(
profileId = profileId,
@@ -283,7 +283,7 @@ object WatchedRepository {
}
suspend fun forceSnapshotRefreshFromServer(profileId: Int) {
- TraktAuthRepository.ensureLoaded()
+ TraktAuthRepository.ensureLoaded(profileId)
TraktSettingsRepository.ensureLoaded()
refreshForSource(
profileId = profileId,
@@ -300,7 +300,7 @@ object WatchedRepository {
source: WatchProgressSource,
forceSnapshot: Boolean = true,
): Boolean {
- TraktAuthRepository.ensureLoaded()
+ TraktAuthRepository.ensureLoaded(profileId)
TraktSettingsRepository.ensureLoaded()
if (ProfileRepository.activeProfileId != profileId) {
log.d { "Skipping watched refresh for inactive profile $profileId" }
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 af708049..80b095dd 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
@@ -51,6 +51,7 @@ internal actual object PlatformLocalAccountDataCleaner {
"mdblist_use_trakt",
"mdblist_use_letterboxd",
"mdblist_use_audience",
+ "mdblist_use_mal",
"trakt_auth_payload",
"trakt_library_payload",
"trakt_settings_payload",
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 d05e79f1..3c429720 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 3ccbaff4..916fedb0 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,10 +6,10 @@ 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))
}
}