diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt index 9afbf1854..87f75e515 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/ScrobblingModule.kt @@ -1,8 +1,10 @@ package org.koitharu.kotatsu.scrobbling +import android.content.Context import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import dagger.multibindings.ElementsIntoSet import okhttp3.OkHttpClient @@ -10,13 +12,14 @@ import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.scrobbling.anilist.data.AniListAuthenticator import org.koitharu.kotatsu.scrobbling.anilist.data.AniListInterceptor import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository -import org.koitharu.kotatsu.scrobbling.anilist.data.AniListStorage import org.koitharu.kotatsu.scrobbling.anilist.domain.AniListScrobbler +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage import org.koitharu.kotatsu.scrobbling.domain.Scrobbler +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerType import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriAuthenticator import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriInterceptor import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository -import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriStorage import org.koitharu.kotatsu.scrobbling.shikimori.domain.ShikimoriScrobbler import javax.inject.Singleton @@ -27,7 +30,7 @@ object ScrobblingModule { @Provides @Singleton fun provideShikimoriRepository( - storage: ShikimoriStorage, + @ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage, database: MangaDatabase, authenticator: ShikimoriAuthenticator, ): ShikimoriRepository { @@ -41,7 +44,7 @@ object ScrobblingModule { @Provides @Singleton fun provideAniListRepository( - storage: AniListStorage, + @ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage, database: MangaDatabase, authenticator: AniListAuthenticator, ): AniListRepository { @@ -52,6 +55,20 @@ object ScrobblingModule { return AniListRepository(okHttp, storage, database) } + @Provides + @Singleton + @ScrobblerType(ScrobblerService.ANILIST) + fun provideAniListStorage( + @ApplicationContext context: Context, + ): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.ANILIST) + + @Provides + @Singleton + @ScrobblerType(ScrobblerService.SHIKIMORI) + fun provideShikimoriStorage( + @ApplicationContext context: Context, + ): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.SHIKIMORI) + @Provides @ElementsIntoSet fun provideScrobblers( diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListAuthenticator.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListAuthenticator.kt index 5a32ee6e7..5fb3040d5 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListAuthenticator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListAuthenticator.kt @@ -7,11 +7,14 @@ import okhttp3.Response import okhttp3.Route import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.network.CommonHeaders +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerType import javax.inject.Inject import javax.inject.Provider class AniListAuthenticator @Inject constructor( - private val storage: AniListStorage, + @ScrobblerType(ScrobblerService.ANILIST) private val storage: ScrobblerStorage, private val repositoryProvider: Provider, ) : Authenticator { diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListInterceptor.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListInterceptor.kt index 70f124ee6..9812283fb 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListInterceptor.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListInterceptor.kt @@ -3,10 +3,11 @@ package org.koitharu.kotatsu.scrobbling.anilist.data import okhttp3.Interceptor import okhttp3.Response import org.koitharu.kotatsu.core.network.CommonHeaders +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage private const val JSON = "application/json" -class AniListInterceptor(private val storage: AniListStorage) : Interceptor { +class AniListInterceptor(private val storage: ScrobblerStorage) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val sourceRequest = chain.request() diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt index 320fba9a8..2bff9c655 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListRepository.kt @@ -15,13 +15,13 @@ import org.koitharu.kotatsu.parsers.util.await import org.koitharu.kotatsu.parsers.util.json.getStringOrNull import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.parseJson -import org.koitharu.kotatsu.parsers.util.parseJsonArray -import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl -import org.koitharu.kotatsu.scrobbling.anilist.data.model.AniListUser +import org.koitharu.kotatsu.scrobbling.data.ScrobblerRepository +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser import org.koitharu.kotatsu.utils.ext.toRequestBody private const val REDIRECT_URI = "kotatsu://anilist-auth" @@ -31,18 +31,18 @@ private const val MANGA_PAGE_SIZE = 10 class AniListRepository( private val okHttp: OkHttpClient, - private val storage: AniListStorage, + private val storage: ScrobblerStorage, private val db: MangaDatabase, -) { +) : ScrobblerRepository { - val oauthUrl: String + override val oauthUrl: String get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.ANILIST_CLIENT_ID}&" + "redirect_uri=${REDIRECT_URI}&response_type=code" - val isAuthorized: Boolean + override val isAuthorized: Boolean get() = storage.accessToken != null - suspend fun authorize(code: String?) { + override suspend fun authorize(code: String?) { val body = FormBody.Builder() body.add("client_id", BuildConfig.ANILIST_CLIENT_ID) body.add("client_secret", BuildConfig.ANILIST_CLIENT_SECRET) @@ -62,7 +62,7 @@ class AniListRepository( storage.refreshToken = response.getString("refresh_token") } - suspend fun loadUser(): AniListUser { + override suspend fun loadUser(): ScrobblerUser { val response = query( """ AniChartUser { @@ -80,57 +80,61 @@ class AniListRepository( return AniListUser(jo).also { storage.user = it } } - fun getCachedUser(): AniListUser? { - return storage.user - } + override val cachedUser: ScrobblerUser? + get() { + return storage.user + } - suspend fun unregister(mangaId: Long) { + override suspend fun unregister(mangaId: Long) { return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId) } - fun logout() { + override fun logout() { storage.clear() } - suspend fun findManga(query: String, offset: Int): List { + override suspend fun findManga(query: String, offset: Int): List { val page = offset / MANGA_PAGE_SIZE - val pageOffset = offset % MANGA_PAGE_SIZE - val url = BASE_URL.toHttpUrl().newBuilder() - .addPathSegment("api") - .addPathSegment("mangas") - .addEncodedQueryParameter("page", (page + 1).toString()) - .addEncodedQueryParameter("limit", MANGA_PAGE_SIZE.toString()) - .addEncodedQueryParameter("censored", false.toString()) - .addQueryParameter("search", query) - .build() - val request = Request.Builder().url(url).get().build() - val response = okHttp.newCall(request).await().parseJsonArray() - val list = response.mapJSON { ScrobblerManga(it) } - return if (pageOffset != 0) list.drop(pageOffset) else list + val response = query( + """ + Page(page: $page, perPage: ${MANGA_PAGE_SIZE}) { + media(type: MANGA, isAdult: true, sort: SEARCH_MATCH, search: "${JSONObject.quote(query)}") { + id + title { + userPreferred + native + } + coverImage { + medium + } + siteUrl + } + } + """.trimIndent(), + ) + val data = response.getJSONObject("data").getJSONObject("Page").getJSONArray("media") + return data.mapJSON { ScrobblerManga(it) } } - suspend fun createRate(mangaId: Long, shikiMangaId: Long) { - val user = getCachedUser() ?: loadUser() - val payload = JSONObject() - payload.put( - "user_rate", - JSONObject().apply { - put("target_id", shikiMangaId) - put("target_type", "Manga") - put("user_id", user.id) - }, + override suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) { + val response = query( + """ + mutation { + SaveMediaListEntry(mediaId: $scrobblerMangaId) { + id + mediaId + status + notes + scoreRaw + progress + } + } + """.trimIndent(), ) - val url = BASE_URL.toHttpUrl().newBuilder() - .addPathSegment("api") - .addPathSegment("v2") - .addPathSegment("user_rates") - .build() - val request = Request.Builder().url(url).post(payload.toRequestBody()).build() - val response = okHttp.newCall(request).await().parseJson() saveRate(response, mangaId) } - suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) { + override suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) { val payload = JSONObject() payload.put( "user_rate", @@ -149,7 +153,7 @@ class AniListRepository( saveRate(response, mangaId) } - suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) { + override suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) { val payload = JSONObject() payload.put( "user_rate", @@ -174,12 +178,23 @@ class AniListRepository( saveRate(response, mangaId) } - suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { - val request = Request.Builder() - .get() - .url("${BASE_URL}api/mangas/$id") - val response = okHttp.newCall(request.build()).await().parseJson() - return ScrobblerMangaInfo(response) + override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { + val response = query( + """ + Media(id: $id) { + id + title { + userPreferred + } + coverImage { + large + } + description + siteUrl + } + """.trimIndent(), + ) + return ScrobblerMangaInfo(response.getJSONObject("data").getJSONObject("Media")) } private suspend fun saveRate(json: JSONObject, mangaId: Long) { @@ -187,29 +202,39 @@ class AniListRepository( scrobbler = ScrobblerService.SHIKIMORI.id, id = json.getInt("id"), mangaId = mangaId, - targetId = json.getLong("target_id"), + targetId = json.getLong("mediaId"), status = json.getString("status"), - chapter = json.getInt("chapters"), - comment = json.getString("text"), - rating = json.getDouble("score").toFloat() / 10f, + chapter = json.getInt("progress"), + comment = json.getString("notes"), + rating = json.getDouble("scoreRaw").toFloat() / 100f, ) db.scrobblingDao.insert(entity) } - private fun ScrobblerManga(json: JSONObject) = ScrobblerManga( - id = json.getLong("id"), - name = json.getString("name"), - altName = json.getStringOrNull("russian"), - cover = json.getJSONObject("image").getString("preview").toAbsoluteUrl("shikimori.one"), - url = json.getString("url").toAbsoluteUrl("shikimori.one"), - ) + private fun ScrobblerManga(json: JSONObject): ScrobblerManga { + val title = json.getJSONObject("title") + return ScrobblerManga( + id = json.getLong("id"), + name = title.getString("userPreferred"), + altName = title.getStringOrNull("native"), + cover = json.getJSONObject("coverImage").getString("medium"), + url = json.getString("siteUrl"), + ) + } private fun ScrobblerMangaInfo(json: JSONObject) = ScrobblerMangaInfo( id = json.getLong("id"), - name = json.getString("name"), - cover = json.getJSONObject("image").getString("preview").toAbsoluteUrl("shikimori.one"), - url = json.getString("url").toAbsoluteUrl("shikimori.one"), - descriptionHtml = json.getString("description_html"), + name = json.getJSONObject("title").getString("userPreferred"), + cover = json.getJSONObject("coverImage").getString("large"), + url = json.getString("siteUrl"), + descriptionHtml = json.getString("description"), + ) + + private fun AniListUser(json: JSONObject) = ScrobblerUser( + id = json.getLong("id"), + nickname = json.getString("name"), + avatar = json.getJSONObject("avatar").getString("medium"), + service = ScrobblerService.ANILIST, ) private suspend fun query(query: String): JSONObject { diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListStorage.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListStorage.kt deleted file mode 100644 index 09c918d08..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/AniListStorage.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.koitharu.kotatsu.scrobbling.anilist.data - -import android.content.Context -import androidx.core.content.edit -import dagger.hilt.android.qualifiers.ApplicationContext -import org.json.JSONObject -import org.koitharu.kotatsu.scrobbling.anilist.data.model.AniListUser -import javax.inject.Inject -import javax.inject.Singleton - -private const val PREF_NAME = "anilist" -private const val KEY_ACCESS_TOKEN = "access_token" -private const val KEY_REFRESH_TOKEN = "refresh_token" -private const val KEY_USER = "user" - -@Singleton -class AniListStorage @Inject constructor(@ApplicationContext context: Context) { - - private val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - - var accessToken: String? - get() = prefs.getString(KEY_ACCESS_TOKEN, null) - set(value) = prefs.edit { putString(KEY_ACCESS_TOKEN, value) } - - var refreshToken: String? - get() = prefs.getString(KEY_REFRESH_TOKEN, null) - set(value) = prefs.edit { putString(KEY_REFRESH_TOKEN, value) } - - var user: AniListUser? - get() = prefs.getString(KEY_USER, null)?.let { - AniListUser(JSONObject(it)) - } - set(value) = prefs.edit { - putString(KEY_USER, value?.toJson()?.toString()) - } - - fun clear() = prefs.edit { - clear() - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/model/AniListUser.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/model/AniListUser.kt deleted file mode 100644 index 24897ee3f..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/data/model/AniListUser.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.koitharu.kotatsu.scrobbling.anilist.data.model - -import org.json.JSONObject - -class AniListUser( - val id: Long, - val nickname: String, - val avatar: String, -) { - - constructor(json: JSONObject) : this( - id = json.getLong("id"), - nickname = json.getString("name"), - avatar = json.getJSONObject("avatar").getString("medium"), - ) - - fun toJson() = JSONObject().apply { - put("id", id) - put("name", nickname) - put("avatar", JSONObject().apply { put("medium", avatar) }) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as AniListUser - - if (id != other.id) return false - if (nickname != other.nickname) return false - if (avatar != other.avatar) return false - - return true - } - - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + nickname.hashCode() - result = 31 * result + avatar.hashCode() - return result - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/domain/AniListScrobbler.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/domain/AniListScrobbler.kt index ebb935c52..4c5da448f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/domain/AniListScrobbler.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/domain/AniListScrobbler.kt @@ -1,11 +1,8 @@ package org.koitharu.kotatsu.scrobbling.anilist.domain import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository import org.koitharu.kotatsu.scrobbling.domain.Scrobbler -import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga -import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus import javax.inject.Inject @@ -17,7 +14,7 @@ private const val RATING_MAX = 10f class AniListScrobbler @Inject constructor( private val repository: AniListRepository, db: MangaDatabase, -) : Scrobbler(db, ScrobblerService.ANILIST) { +) : Scrobbler(db, ScrobblerService.ANILIST, repository) { init { statuses[ScrobblingStatus.PLANNED] = "planned" @@ -28,22 +25,6 @@ class AniListScrobbler @Inject constructor( statuses[ScrobblingStatus.DROPPED] = "dropped" } - override val isAvailable: Boolean - get() = repository.isAuthorized - - override suspend fun findManga(query: String, offset: Int): List { - return repository.findManga(query, offset) - } - - override suspend fun linkManga(mangaId: Long, targetId: Long) { - repository.createRate(mangaId, targetId) - } - - override suspend fun scrobble(mangaId: Long, chapter: MangaChapter) { - val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return - repository.updateRate(entity.id, entity.mangaId, chapter) - } - override suspend fun updateScrobblingInfo( mangaId: Long, rating: Float, @@ -60,12 +41,4 @@ class AniListScrobbler @Inject constructor( comment = comment, ) } - - override suspend fun unregisterScrobbling(mangaId: Long) { - repository.unregister(mangaId) - } - - override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { - return repository.getMangaInfo(id) - } } diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsFragment.kt index a062fddc5..5a98b7a81 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsFragment.kt @@ -11,7 +11,7 @@ import coil.transform.CircleCropTransformation import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment -import org.koitharu.kotatsu.scrobbling.anilist.data.model.AniListUser +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser import org.koitharu.kotatsu.utils.PreferenceIconTarget import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.enqueueWith @@ -52,7 +52,7 @@ class AniListSettingsFragment : BasePreferenceFragment(R.string.anilist) { } } - private fun onUserChanged(user: AniListUser?) { + private fun onUserChanged(user: ScrobblerUser?) { val pref = findPreference(KEY_USER) ?: return pref.isSelectable = user == null pref.title = user?.nickname ?: getString(R.string.sign_in) diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsViewModel.kt index 9bec3f34d..554e5a219 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/anilist/ui/AniListSettingsViewModel.kt @@ -7,7 +7,7 @@ import dagger.assisted.AssistedInject import kotlinx.coroutines.Dispatchers import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository -import org.koitharu.kotatsu.scrobbling.anilist.data.model.AniListUser +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser class AniListSettingsViewModel @AssistedInject constructor( private val repository: AniListRepository, @@ -17,7 +17,7 @@ class AniListSettingsViewModel @AssistedInject constructor( val authorizationUrl: String get() = repository.oauthUrl - val user = MutableLiveData() + val user = MutableLiveData() init { if (authCode != null) { @@ -36,7 +36,7 @@ class AniListSettingsViewModel @AssistedInject constructor( private fun loadUser() = launchJob(Dispatchers.Default) { val userModel = if (repository.isAuthorized) { - repository.getCachedUser()?.let(user::postValue) + repository.cachedUser?.let(user::postValue) repository.loadUser() } else { null diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/data/ScrobblerRepository.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/data/ScrobblerRepository.kt new file mode 100644 index 000000000..08310ae25 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/data/ScrobblerRepository.kt @@ -0,0 +1,33 @@ +package org.koitharu.kotatsu.scrobbling.data + +import org.koitharu.kotatsu.parsers.model.MangaChapter +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser + +interface ScrobblerRepository { + + val oauthUrl: String + + val isAuthorized: Boolean + + val cachedUser: ScrobblerUser? + + suspend fun authorize(code: String?) + + suspend fun loadUser(): ScrobblerUser + + fun logout() + + suspend fun unregister(mangaId: Long) + + suspend fun findManga(query: String, offset: Int): List + + suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo + + suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) + + suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) + + suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) +} diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/data/ScrobblerStorage.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/data/ScrobblerStorage.kt new file mode 100644 index 000000000..68b1c4efc --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/data/ScrobblerStorage.kt @@ -0,0 +1,54 @@ +package org.koitharu.kotatsu.scrobbling.data + +import android.content.Context +import androidx.core.content.edit +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser + +private const val KEY_ACCESS_TOKEN = "access_token" +private const val KEY_REFRESH_TOKEN = "refresh_token" +private const val KEY_USER = "user" + +class ScrobblerStorage(context: Context, service: ScrobblerService) { + + private val prefs = context.getSharedPreferences(service.name, Context.MODE_PRIVATE) + + var accessToken: String? + get() = prefs.getString(KEY_ACCESS_TOKEN, null) + set(value) = prefs.edit { putString(KEY_ACCESS_TOKEN, value) } + + var refreshToken: String? + get() = prefs.getString(KEY_REFRESH_TOKEN, null) + set(value) = prefs.edit { putString(KEY_REFRESH_TOKEN, value) } + + var user: ScrobblerUser? + get() = prefs.getString(KEY_USER, null)?.let { + val lines = it.lines() + if (lines.size != 4) { + return@let null + } + ScrobblerUser( + id = lines[0].toLong(), + nickname = lines[1], + avatar = lines[2], + service = ScrobblerService.valueOf(lines[3]), + ) + } + set(value) = prefs.edit { + if (value == null) { + remove(KEY_USER) + return@edit + } + val str = buildString { + appendLine(value.id) + appendLine(value.nickname) + appendLine(value.avatar) + appendLine(value.service.name) + } + putString(KEY_USER, str) + } + + fun clear() = prefs.edit { + clear() + } +} diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt index 1db58e204..54f9ac504 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/Scrobbler.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.parsers.model.MangaChapter +import org.koitharu.kotatsu.scrobbling.data.ScrobblerRepository import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo @@ -21,18 +22,27 @@ import java.util.EnumMap abstract class Scrobbler( protected val db: MangaDatabase, val scrobblerService: ScrobblerService, + private val repository: ScrobblerRepository, ) { private val infoCache = LongSparseArray() protected val statuses = EnumMap(ScrobblingStatus::class.java) - abstract val isAvailable: Boolean + val isAvailable: Boolean + get() = repository.isAuthorized - abstract suspend fun findManga(query: String, offset: Int): List + suspend fun findManga(query: String, offset: Int): List { + return repository.findManga(query, offset) + } - abstract suspend fun linkManga(mangaId: Long, targetId: Long) + suspend fun linkManga(mangaId: Long, targetId: Long) { + repository.createRate(mangaId, targetId) + } - abstract suspend fun scrobble(mangaId: Long, chapter: MangaChapter) + suspend fun scrobble(mangaId: Long, chapter: MangaChapter) { + val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return + repository.updateRate(entity.id, entity.mangaId, chapter) + } suspend fun getScrobblingInfoOrNull(mangaId: Long): ScrobblingInfo? { val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return null @@ -46,9 +56,13 @@ abstract class Scrobbler( .map { it?.toScrobblingInfo(mangaId) } } - abstract suspend fun unregisterScrobbling(mangaId: Long) + suspend fun unregisterScrobbling(mangaId: Long) { + repository.unregister(mangaId) + } - protected abstract suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo + protected suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { + return repository.getMangaInfo(id) + } private suspend fun ScrobblingEntity.toScrobblingInfo(mangaId: Long): ScrobblingInfo? { val mangaInfo = infoCache.getOrElse(targetId) { diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/model/ScrobblerType.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/model/ScrobblerType.kt new file mode 100644 index 000000000..e2d372edb --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/model/ScrobblerType.kt @@ -0,0 +1,8 @@ +package org.koitharu.kotatsu.scrobbling.domain.model + +import javax.inject.Qualifier + +@Qualifier +annotation class ScrobblerType( + val service: ScrobblerService +) diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/model/ShikimoriUser.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/model/ScrobblerUser.kt similarity index 56% rename from app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/model/ShikimoriUser.kt rename to app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/model/ScrobblerUser.kt index 79ecfa6c5..92ecce0df 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/model/ShikimoriUser.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/domain/model/ScrobblerUser.kt @@ -1,34 +1,22 @@ -package org.koitharu.kotatsu.scrobbling.shikimori.data.model +package org.koitharu.kotatsu.scrobbling.domain.model -import org.json.JSONObject - -class ShikimoriUser( +class ScrobblerUser( val id: Long, val nickname: String, val avatar: String, + val service: ScrobblerService, ) { - constructor(json: JSONObject) : this( - id = json.getLong("id"), - nickname = json.getString("nickname"), - avatar = json.getString("avatar"), - ) - - fun toJson() = JSONObject().apply { - put("id", id) - put("nickname", nickname) - put("avatar", avatar) - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as ShikimoriUser + other as ScrobblerUser if (id != other.id) return false if (nickname != other.nickname) return false if (avatar != other.avatar) return false + if (service != other.service) return false return true } @@ -37,6 +25,7 @@ class ShikimoriUser( var result = id.hashCode() result = 31 * result + nickname.hashCode() result = 31 * result + avatar.hashCode() + result = 31 * result + service.hashCode() return result } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriAuthenticator.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriAuthenticator.kt index 7b683cf1d..dbaad2cc7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriAuthenticator.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriAuthenticator.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.scrobbling.shikimori.data -import javax.inject.Inject -import javax.inject.Provider import kotlinx.coroutines.runBlocking import okhttp3.Authenticator import okhttp3.Request @@ -9,9 +7,14 @@ import okhttp3.Response import okhttp3.Route import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.network.CommonHeaders +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerType +import javax.inject.Inject +import javax.inject.Provider class ShikimoriAuthenticator @Inject constructor( - private val storage: ShikimoriStorage, + @ScrobblerType(ScrobblerService.SHIKIMORI) private val storage: ScrobblerStorage, private val repositoryProvider: Provider, ) : Authenticator { diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriInterceptor.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriInterceptor.kt index d0be7d23c..466382960 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriInterceptor.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriInterceptor.kt @@ -4,10 +4,11 @@ import okhttp3.Interceptor import okhttp3.Response import okio.IOException import org.koitharu.kotatsu.core.network.CommonHeaders +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage private const val USER_AGENT_SHIKIMORI = "Kotatsu" -class ShikimoriInterceptor(private val storage: ShikimoriStorage) : Interceptor { +class ShikimoriInterceptor(private val storage: ScrobblerStorage) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val sourceRequest = chain.request() diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt index 2267994ec..66de790c2 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriRepository.kt @@ -14,11 +14,13 @@ import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.parseJson import org.koitharu.kotatsu.parsers.util.parseJsonArray import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl +import org.koitharu.kotatsu.scrobbling.data.ScrobblerRepository +import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService -import org.koitharu.kotatsu.scrobbling.shikimori.data.model.ShikimoriUser +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser import org.koitharu.kotatsu.utils.ext.toRequestBody private const val REDIRECT_URI = "kotatsu://shikimori-auth" @@ -27,18 +29,18 @@ private const val MANGA_PAGE_SIZE = 10 class ShikimoriRepository( private val okHttp: OkHttpClient, - private val storage: ShikimoriStorage, + private val storage: ScrobblerStorage, private val db: MangaDatabase, -) { +) : ScrobblerRepository { - val oauthUrl: String + override val oauthUrl: String get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.SHIKIMORI_CLIENT_ID}&" + "redirect_uri=$REDIRECT_URI&response_type=code&scope=" - val isAuthorized: Boolean + override val isAuthorized: Boolean get() = storage.accessToken != null - suspend fun authorize(code: String?) { + override suspend fun authorize(code: String?) { val body = FormBody.Builder() body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID) body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET) @@ -58,7 +60,7 @@ class ShikimoriRepository( storage.refreshToken = response.getString("refresh_token") } - suspend fun loadUser(): ShikimoriUser { + override suspend fun loadUser(): ScrobblerUser { val request = Request.Builder() .get() .url("${BASE_URL}api/users/whoami") @@ -66,19 +68,20 @@ class ShikimoriRepository( return ShikimoriUser(response).also { storage.user = it } } - fun getCachedUser(): ShikimoriUser? { - return storage.user - } + override val cachedUser: ScrobblerUser? + get() { + return storage.user + } - suspend fun unregister(mangaId: Long) { + override suspend fun unregister(mangaId: Long) { return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId) } - fun logout() { + override fun logout() { storage.clear() } - suspend fun findManga(query: String, offset: Int): List { + override suspend fun findManga(query: String, offset: Int): List { val page = offset / MANGA_PAGE_SIZE val pageOffset = offset % MANGA_PAGE_SIZE val url = BASE_URL.toHttpUrl().newBuilder() @@ -95,8 +98,8 @@ class ShikimoriRepository( return if (pageOffset != 0) list.drop(pageOffset) else list } - suspend fun createRate(mangaId: Long, shikiMangaId: Long) { - val user = getCachedUser() ?: loadUser() + override suspend fun createRate(mangaId: Long, shikiMangaId: Long) { + val user = cachedUser ?: loadUser() val payload = JSONObject() payload.put( "user_rate", @@ -116,7 +119,7 @@ class ShikimoriRepository( saveRate(response, mangaId) } - suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) { + override suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) { val payload = JSONObject() payload.put( "user_rate", @@ -135,7 +138,7 @@ class ShikimoriRepository( saveRate(response, mangaId) } - suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) { + override suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) { val payload = JSONObject() payload.put( "user_rate", @@ -160,7 +163,7 @@ class ShikimoriRepository( saveRate(response, mangaId) } - suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { + override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { val request = Request.Builder() .get() .url("${BASE_URL}api/mangas/$id") @@ -197,4 +200,11 @@ class ShikimoriRepository( url = json.getString("url").toAbsoluteUrl("shikimori.one"), descriptionHtml = json.getString("description_html"), ) + + private fun ShikimoriUser(json: JSONObject) = ScrobblerUser( + id = json.getLong("id"), + nickname = json.getString("nickname"), + avatar = json.getString("avatar"), + service = ScrobblerService.SHIKIMORI, + ) } diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriStorage.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriStorage.kt deleted file mode 100644 index a3cd84e85..000000000 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/data/ShikimoriStorage.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.koitharu.kotatsu.scrobbling.shikimori.data - -import android.content.Context -import androidx.core.content.edit -import dagger.hilt.android.qualifiers.ApplicationContext -import org.json.JSONObject -import org.koitharu.kotatsu.scrobbling.shikimori.data.model.ShikimoriUser -import javax.inject.Inject -import javax.inject.Singleton - -private const val PREF_NAME = "shikimori" -private const val KEY_ACCESS_TOKEN = "access_token" -private const val KEY_REFRESH_TOKEN = "refresh_token" -private const val KEY_USER = "user" - -@Singleton -class ShikimoriStorage @Inject constructor(@ApplicationContext context: Context) { - - private val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - - var accessToken: String? - get() = prefs.getString(KEY_ACCESS_TOKEN, null) - set(value) = prefs.edit { putString(KEY_ACCESS_TOKEN, value) } - - var refreshToken: String? - get() = prefs.getString(KEY_REFRESH_TOKEN, null) - set(value) = prefs.edit { putString(KEY_REFRESH_TOKEN, value) } - - var user: ShikimoriUser? - get() = prefs.getString(KEY_USER, null)?.let { - ShikimoriUser(JSONObject(it)) - } - set(value) = prefs.edit { - putString(KEY_USER, value?.toJson()?.toString()) - } - - fun clear() = prefs.edit { - clear() - } -} diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/domain/ShikimoriScrobbler.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/domain/ShikimoriScrobbler.kt index bf69e3fa4..5c73c0532 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/domain/ShikimoriScrobbler.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/domain/ShikimoriScrobbler.kt @@ -1,15 +1,12 @@ package org.koitharu.kotatsu.scrobbling.shikimori.domain -import javax.inject.Inject -import javax.inject.Singleton import org.koitharu.kotatsu.core.db.MangaDatabase -import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.scrobbling.domain.Scrobbler -import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga -import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository +import javax.inject.Inject +import javax.inject.Singleton private const val RATING_MAX = 10f @@ -17,7 +14,7 @@ private const val RATING_MAX = 10f class ShikimoriScrobbler @Inject constructor( private val repository: ShikimoriRepository, db: MangaDatabase, -) : Scrobbler(db, ScrobblerService.SHIKIMORI) { +) : Scrobbler(db, ScrobblerService.SHIKIMORI, repository) { init { statuses[ScrobblingStatus.PLANNED] = "planned" @@ -28,22 +25,6 @@ class ShikimoriScrobbler @Inject constructor( statuses[ScrobblingStatus.DROPPED] = "dropped" } - override val isAvailable: Boolean - get() = repository.isAuthorized - - override suspend fun findManga(query: String, offset: Int): List { - return repository.findManga(query, offset) - } - - override suspend fun linkManga(mangaId: Long, targetId: Long) { - repository.createRate(mangaId, targetId) - } - - override suspend fun scrobble(mangaId: Long, chapter: MangaChapter) { - val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return - repository.updateRate(entity.id, entity.mangaId, chapter) - } - override suspend fun updateScrobblingInfo( mangaId: Long, rating: Float, @@ -60,12 +41,4 @@ class ShikimoriScrobbler @Inject constructor( comment = comment, ) } - - override suspend fun unregisterScrobbling(mangaId: Long) { - repository.unregister(mangaId) - } - - override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { - return repository.getMangaInfo(id) - } } diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsFragment.kt index 03fd7d882..da45cf4ed 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsFragment.kt @@ -9,14 +9,14 @@ import coil.ImageLoader import coil.request.ImageRequest import coil.transform.CircleCropTransformation import dagger.hilt.android.AndroidEntryPoint -import javax.inject.Inject import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.ui.BasePreferenceFragment -import org.koitharu.kotatsu.scrobbling.shikimori.data.model.ShikimoriUser +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser import org.koitharu.kotatsu.utils.PreferenceIconTarget import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.withArgs +import javax.inject.Inject @AndroidEntryPoint class ShikimoriSettingsFragment : BasePreferenceFragment(R.string.shikimori) { @@ -47,11 +47,12 @@ class ShikimoriSettingsFragment : BasePreferenceFragment(R.string.shikimori) { viewModel.logout() true } + else -> super.onPreferenceTreeClick(preference) } } - private fun onUserChanged(user: ShikimoriUser?) { + private fun onUserChanged(user: ScrobblerUser?) { val pref = findPreference(KEY_USER) ?: return pref.isSelectable = user == null pref.title = user?.nickname ?: getString(R.string.sign_in) diff --git a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsViewModel.kt index 091bdcea3..2e689e673 100644 --- a/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/scrobbling/shikimori/ui/ShikimoriSettingsViewModel.kt @@ -6,8 +6,8 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.Dispatchers import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository -import org.koitharu.kotatsu.scrobbling.shikimori.data.model.ShikimoriUser class ShikimoriSettingsViewModel @AssistedInject constructor( private val repository: ShikimoriRepository, @@ -17,7 +17,7 @@ class ShikimoriSettingsViewModel @AssistedInject constructor( val authorizationUrl: String get() = repository.oauthUrl - val user = MutableLiveData() + val user = MutableLiveData() init { if (authCode != null) { @@ -36,7 +36,7 @@ class ShikimoriSettingsViewModel @AssistedInject constructor( private fun loadUser() = launchJob(Dispatchers.Default) { val userModel = if (repository.isAuthorized) { - repository.getCachedUser()?.let(user::postValue) + repository.cachedUser?.let(user::postValue) repository.loadUser() } else { null diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt index 936aa2028..d625be3ea 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/HistorySettingsFragment.kt @@ -201,7 +201,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach private fun bindShikimoriSummary() { findPreference(AppSettings.KEY_SHIKIMORI)?.summary = if (shikimoriRepository.isAuthorized) { - getString(R.string.logged_in_as, shikimoriRepository.getCachedUser()?.nickname) + getString(R.string.logged_in_as, shikimoriRepository.cachedUser?.nickname) } else { getString(R.string.disabled) } @@ -209,7 +209,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach private fun bindAniListSummary() { findPreference(AppSettings.KEY_ANILIST)?.summary = if (aniListRepository.isAuthorized) { - getString(R.string.logged_in_as, aniListRepository.getCachedUser()?.nickname) + getString(R.string.logged_in_as, aniListRepository.cachedUser?.nickname) } else { getString(R.string.disabled) }