mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-01 00:59:27 +00:00
Merge pull request #1591 from guuilp/feat/mal-rating
feat(mdblist): add MyAnimeList (MAL) rating from MDBList API
This commit is contained in:
commit
755f6ff9b8
19 changed files with 136 additions and 84 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
|
|
@ -1151,6 +1151,7 @@
|
|||
<string name="source_rotten_tomatoes">Rotten Tomatoes</string>
|
||||
<string name="source_tmdb">TMDB</string>
|
||||
<string name="source_trakt">Trakt</string>
|
||||
<string name="source_mal">MyAnimeList</string>
|
||||
<string name="subtitle_language_unknown">Unknown</string>
|
||||
<string name="theme_amber">Amber</string>
|
||||
<string name="theme_crimson">Crimson</string>
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ object SyncManager {
|
|||
continue
|
||||
}
|
||||
|
||||
TraktAuthRepository.ensureLoaded()
|
||||
TraktAuthRepository.ensureLoaded(profileId)
|
||||
TraktSettingsRepository.ensureLoaded()
|
||||
|
||||
val traktAuthenticated = TraktAuthRepository.isAuthenticated.value
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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) ->
|
||||
|
|
|
|||
|
|
@ -48,33 +48,37 @@ object TraktAuthRepository {
|
|||
val isAuthenticated: StateFlow<Boolean> = _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<String, String>? {
|
||||
ensureLoaded()
|
||||
suspend fun authorizedHeaders(profileId: Int = currentProfileId): Map<String, String>? {
|
||||
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) }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" }
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue