diff --git a/CHANGELOG.md b/CHANGELOG.md index b4cd20c26..3a978f5b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Display authors and description in Shikimori search results ([@MajorTanya](https://github.com/MajorTanya)) ([#3499](https://github.com/mihonapp/mihon/pull/3499)) - Invalidate download cache after backup restore ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3096](https://github.com/mihonapp/mihon/pull/3096)) +### Improved +- Show usernames in Tracking settings ([@MajorTanya](https://github.com/MajorTanya)) ([#3533](https://github.com/mihonapp/mihon/pull/3533)) + ### Fixed - Fix Shikimori tracking not working ([@MajorTanya](https://github.com/MajorTanya)) ([#3497](https://github.com/mihonapp/mihon/pull/3497)) - Fix crash trying to select text in notes screen ([@AntsyLich](https://github.com/AntsyLich)) ([#3516](https://github.com/mihonapp/mihon/pull/3516)) diff --git a/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt b/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt index 032036b4a..e31fb0e68 100644 --- a/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt +++ b/app/src/main/java/eu/kanade/domain/track/service/TrackPreferences.kt @@ -16,6 +16,11 @@ class TrackPreferences( "", ) + fun trackDisplayUsername(tracker: Tracker) = preferenceStore.getString( + Preference.privateKey("pref_mangasync_displayname_${tracker.id}"), + "", + ) + fun trackPassword(tracker: Tracker) = preferenceStore.getString( Preference.privateKey("pref_mangasync_password_${tracker.id}"), "", diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt index 9e9ffb342..fb6508d55 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt @@ -171,7 +171,7 @@ internal fun PreferenceItem( } TrackingPreferenceWidget( tracker = item.tracker, - checked = isLoggedIn, + isLoggedIn = isLoggedIn, onClick = { if (isLoggedIn) item.logout() else item.login() }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt index 81362b66c..417008b5c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TrackingPreferenceWidget.kt @@ -2,6 +2,7 @@ package eu.kanade.presentation.more.settings.widget import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -15,6 +16,8 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import eu.kanade.presentation.more.settings.LocalPreferenceHighlighted import eu.kanade.presentation.track.components.TrackLogoIcon @@ -26,7 +29,7 @@ import tachiyomi.presentation.core.i18n.stringResource fun TrackingPreferenceWidget( modifier: Modifier = Modifier, tracker: Tracker, - checked: Boolean, + isLoggedIn: Boolean, onClick: (() -> Unit)? = null, ) { val highlighted = LocalPreferenceHighlighted.current @@ -39,16 +42,29 @@ fun TrackingPreferenceWidget( verticalAlignment = Alignment.CenterVertically, ) { TrackLogoIcon(tracker) - Text( - text = tracker.name, - modifier = Modifier - .weight(1f) - .padding(horizontal = 16.dp), - maxLines = 1, - style = MaterialTheme.typography.titleLarge, - fontSize = TitleFontSize, - ) - if (checked) { + Column( + modifier = Modifier.weight(1f), + ) { + Text( + text = tracker.name, + modifier = Modifier.padding(horizontal = 16.dp), + maxLines = 1, + style = MaterialTheme.typography.titleLarge, + fontSize = TitleFontSize, + fontWeight = FontWeight.Medium, + ) + val displayName = tracker.getDisplayUsername() + if (isLoggedIn && displayName.isNotBlank()) { + Text( + text = displayName, + modifier = Modifier.padding(horizontal = 16.dp), + maxLines = 1, + style = MaterialTheme.typography.bodyMedium, + overflow = TextOverflow.Ellipsis, + ) + } + } + if (isLoggedIn) { Icon( imageVector = Icons.Outlined.Done, modifier = Modifier diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt index ef5411a0b..57d775acf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt @@ -68,6 +68,10 @@ abstract class BaseTracker( override fun getUsername() = trackPreferences.trackUsername(this).get() + override fun getDisplayUsername(): String = trackPreferences.trackDisplayUsername(this).get() + + override fun saveDisplayUsername(displayName: String) = trackPreferences.trackDisplayUsername(this).set(displayName) + override fun getPassword() = trackPreferences.trackPassword(this).get() override fun saveCredentials(username: String, password: String) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt index f016dca0a..9f345b2a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt @@ -65,6 +65,10 @@ interface Tracker { fun getPassword(): String + fun getDisplayUsername(): String + + fun saveDisplayUsername(displayName: String) + fun saveCredentials(username: String, password: String) // TODO: move this to an interactor, and update all trackers based on common data diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt index 67205f6e2..69abe41c4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/Anilist.kt @@ -213,9 +213,10 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker { try { val oauth = api.createOAuth(token) interceptor.setAuth(oauth) - val (username, scoreType) = api.getCurrentUser() - scorePreference.set(scoreType) - saveCredentials(username.toString(), oauth.accessToken) + val currentUser = api.getCurrentUser() + scorePreference.set(currentUser.mediaListOptions.scoreFormat) + saveDisplayUsername(currentUser.name) + saveCredentials(currentUser.id.toString(), oauth.accessToken) } catch (e: Throwable) { logout() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 72fb07cf5..c0d8dd7b6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserListMangaQueryResult +import eu.kanade.tachiyomi.data.track.anilist.dto.ALUserViewerData import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess @@ -285,12 +286,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { return ALOAuth(token, "Bearer", System.currentTimeMillis() + 31536000000, 31536000000) } - suspend fun getCurrentUser(): Pair { + suspend fun getCurrentUser(): ALUserViewerData { return withIOContext { val query = """ |query User { |Viewer { |id + |name |mediaListOptions { |scoreFormat |} @@ -310,10 +312,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { ) .awaitSuccess() .parseAs() - .let { - val viewer = it.data.viewer - Pair(viewer.id, viewer.mediaListOptions.scoreFormat) - } + .data.viewer } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUser.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUser.kt index 39507a0d5..6387a8d44 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUser.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/dto/ALUser.kt @@ -17,6 +17,7 @@ data class ALUserViewer( @Serializable data class ALUserViewerData( val id: Int, + val name: String, val mediaListOptions: ALUserListOptions, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index 3766e5956..1c8fd8c7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -106,8 +106,9 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { // Users can set a 'username' (not nickname) once which effectively // replaces the stringified ID in certain queries. // If no username is set, the API returns the user ID as a strings - val username = api.getUsername() - saveCredentials(username, oauth.accessToken) + val currentUser = api.getCurrentUser() + saveDisplayUsername(currentUser.nickname?.takeIf { it.isNotBlank() } ?: currentUser.username) + saveCredentials(currentUser.username, oauth.accessToken) } catch (_: Throwable) { logout() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt index e372489be..1212232dd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/BangumiApi.kt @@ -153,13 +153,12 @@ class BangumiApi( } } - suspend fun getUsername(): String { + suspend fun getCurrentUser(): BGMUser { return withIOContext { with(json) { authClient.newCall(GET("$API_URL/v0/me")) .awaitSuccess() .parseAs() - .username } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/dto/BGMUser.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/dto/BGMUser.kt index 70bb96ee9..b168e3bc6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/dto/BGMUser.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/dto/BGMUser.kt @@ -6,4 +6,5 @@ import kotlinx.serialization.Serializable // Incomplete DTO with only our needed attributes data class BGMUser( val username: String, + val nickname: String?, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt index 00510c1f6..91dbd6d63 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/hikka/Hikka.kt @@ -144,6 +144,7 @@ class Hikka(id: Long) : BaseTracker(id, "Hikka"), DeletableTracker { val oauth = api.accessToken(reference) interceptor.setAuth(oauth) val user = api.getCurrentUser() + saveDisplayUsername(user.username) saveCredentials(user.reference, oauth.accessToken) } catch (_: Throwable) { logout() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt index ae7749cdd..1fab07fdf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/Kitsu.kt @@ -127,8 +127,9 @@ class Kitsu(id: Long) : BaseTracker(id, "Kitsu"), DeletableTracker { override suspend fun login(username: String, password: String) { val token = api.login(username, password) interceptor.newAuth(token) - val userId = api.getCurrentUser() - saveCredentials(username, userId) + val currentUser = api.getCurrentUser() + saveDisplayUsername(currentUser.attributes.name) + saveCredentials(username, currentUser.id) } override fun logout() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt index 446808704..a58129037 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/KitsuApi.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuCurrentUserResult import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuListSearchResult import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuOAuth import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuSearchResult +import eu.kanade.tachiyomi.data.track.kitsu.dto.KitsuUser import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.GET @@ -227,7 +228,7 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) } } - suspend fun getCurrentUser(): String { + suspend fun getCurrentUser(): KitsuUser { return withIOContext { val url = "${BASE_URL}users".toUri().buildUpon() .encodedQuery("filter[self]=true") @@ -237,7 +238,6 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) .awaitSuccess() .parseAs() .data[0] - .id } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/dto/KitsuUser.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/dto/KitsuUser.kt index d02337651..075931372 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/dto/KitsuUser.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/kitsu/dto/KitsuUser.kt @@ -10,4 +10,10 @@ data class KitsuCurrentUserResult( @Serializable data class KitsuUser( val id: String, + val attributes: KitsuUserAttributes, +) + +@Serializable +data class KitsuUserAttributes( + val name: String, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt index a77fa5dbb..3edde1b29 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdates.kt @@ -107,8 +107,10 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker override suspend fun login(username: String, password: String) { val authenticated = api.authenticate(username, password) - saveCredentials(authenticated.uid.toString(), authenticated.sessionToken) interceptor.newAuth(authenticated.sessionToken) + val currentUser = api.getCurrentUser() + saveDisplayUsername(currentUser.username) + saveCredentials(authenticated.uid.toString(), authenticated.sessionToken) } fun restoreSession(): String? { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt index ad4669c30..b2f1d408a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/MangaUpdatesApi.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.READING_LIST import eu.kanade.tachiyomi.data.track.mangaupdates.MangaUpdates.Companion.WISH_LIST import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUContext +import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUCurrentUser import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MUListItem import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MULoginResponse import eu.kanade.tachiyomi.data.track.mangaupdates.dto.MURating @@ -190,6 +191,14 @@ class MangaUpdatesApi( } } + suspend fun getCurrentUser(): MUCurrentUser { + return with(json) { + authClient.newCall(GET("$BASE_URL/v1/account/profile")) + .awaitSuccess() + .parseAs() + } + } + companion object { private const val BASE_URL = "https://api.mangaupdates.com" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/MUCurrentUser.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/MUCurrentUser.kt new file mode 100644 index 000000000..f81cb6ea2 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/mangaupdates/dto/MUCurrentUser.kt @@ -0,0 +1,8 @@ +package eu.kanade.tachiyomi.data.track.mangaupdates.dto + +import kotlinx.serialization.Serializable + +@Serializable +data class MUCurrentUser( + val username: String, +) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt index 929727272..c4baf1599 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeList.kt @@ -137,6 +137,7 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker { val oauth = api.getAccessToken(authCode) interceptor.setAuth(oauth) val username = api.getCurrentUser() + saveDisplayUsername(username) saveCredentials(username, oauth.accessToken) } catch (e: Throwable) { logout() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt index a49d0b898..32a5e6eab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/Shikimori.kt @@ -122,7 +122,8 @@ class Shikimori(id: Long) : BaseTracker(id, "Shikimori"), DeletableTracker { val oauth = api.accessToken(code) interceptor.newAuth(oauth) val user = api.getCurrentUser() - saveCredentials(user.toString(), oauth.accessToken) + saveDisplayUsername(user.nickname) + saveCredentials(user.id, oauth.accessToken) } catch (e: Throwable) { logout() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt index c0b826ae3..e5edf7e7e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/ShikimoriApi.kt @@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.shikimori.dto.SMAddMangaResponse import eu.kanade.tachiyomi.data.track.shikimori.dto.SMOAuth import eu.kanade.tachiyomi.data.track.shikimori.dto.SMSearchResult +import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUser import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserListResult import eu.kanade.tachiyomi.data.track.shikimori.dto.SMUserResult import eu.kanade.tachiyomi.network.DELETE @@ -171,12 +172,13 @@ class ShikimoriApi( } } - suspend fun getCurrentUser(): Int { + suspend fun getCurrentUser(): SMUser { return with(json) { val query = """ |{ |currentUser { |id + |nickname |} |} """.trimMargin() @@ -191,8 +193,7 @@ class ShikimoriApi( ) .awaitSuccess() .parseAs() - .data.currentUser.id - .toInt() + .data.currentUser } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/dto/SMUser.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/dto/SMUser.kt index 6b7492176..98a2c0db2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/dto/SMUser.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/shikimori/dto/SMUser.kt @@ -15,4 +15,5 @@ data class SMCurrentUser( @Serializable data class SMUser( val id: String, + val nickname: String, ) diff --git a/app/src/main/java/eu/kanade/test/DummyTracker.kt b/app/src/main/java/eu/kanade/test/DummyTracker.kt index bf4355a16..c4840b2bf 100644 --- a/app/src/main/java/eu/kanade/test/DummyTracker.kt +++ b/app/src/main/java/eu/kanade/test/DummyTracker.kt @@ -81,6 +81,10 @@ data class DummyTracker( override fun getUsername(): String = "username" + override fun getDisplayUsername(): String = "UserName" + + override fun saveDisplayUsername(displayName: String): Unit = Unit + override fun getPassword(): String = "passw0rd" override fun saveCredentials(username: String, password: String) = Unit