Unify scrobblers implementation

pull/263/head
Koitharu 4 years ago
parent 743098d0b0
commit 0c4c7489e9
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -1,8 +1,10 @@
package org.koitharu.kotatsu.scrobbling package org.koitharu.kotatsu.scrobbling
import android.content.Context
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ElementsIntoSet import dagger.multibindings.ElementsIntoSet
import okhttp3.OkHttpClient 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.AniListAuthenticator
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListInterceptor import org.koitharu.kotatsu.scrobbling.anilist.data.AniListInterceptor
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository 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.anilist.domain.AniListScrobbler
import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage
import org.koitharu.kotatsu.scrobbling.domain.Scrobbler 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.ShikimoriAuthenticator
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriInterceptor import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriInterceptor
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository 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 org.koitharu.kotatsu.scrobbling.shikimori.domain.ShikimoriScrobbler
import javax.inject.Singleton import javax.inject.Singleton
@ -27,7 +30,7 @@ object ScrobblingModule {
@Provides @Provides
@Singleton @Singleton
fun provideShikimoriRepository( fun provideShikimoriRepository(
storage: ShikimoriStorage, @ScrobblerType(ScrobblerService.SHIKIMORI) storage: ScrobblerStorage,
database: MangaDatabase, database: MangaDatabase,
authenticator: ShikimoriAuthenticator, authenticator: ShikimoriAuthenticator,
): ShikimoriRepository { ): ShikimoriRepository {
@ -41,7 +44,7 @@ object ScrobblingModule {
@Provides @Provides
@Singleton @Singleton
fun provideAniListRepository( fun provideAniListRepository(
storage: AniListStorage, @ScrobblerType(ScrobblerService.ANILIST) storage: ScrobblerStorage,
database: MangaDatabase, database: MangaDatabase,
authenticator: AniListAuthenticator, authenticator: AniListAuthenticator,
): AniListRepository { ): AniListRepository {
@ -52,6 +55,20 @@ object ScrobblingModule {
return AniListRepository(okHttp, storage, database) 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 @Provides
@ElementsIntoSet @ElementsIntoSet
fun provideScrobblers( fun provideScrobblers(

@ -7,11 +7,14 @@ import okhttp3.Response
import okhttp3.Route import okhttp3.Route
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.network.CommonHeaders 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.Inject
import javax.inject.Provider import javax.inject.Provider
class AniListAuthenticator @Inject constructor( class AniListAuthenticator @Inject constructor(
private val storage: AniListStorage, @ScrobblerType(ScrobblerService.ANILIST) private val storage: ScrobblerStorage,
private val repositoryProvider: Provider<AniListRepository>, private val repositoryProvider: Provider<AniListRepository>,
) : Authenticator { ) : Authenticator {

@ -3,10 +3,11 @@ package org.koitharu.kotatsu.scrobbling.anilist.data
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage
private const val JSON = "application/json" 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 { override fun intercept(chain: Interceptor.Chain): Response {
val sourceRequest = chain.request() val sourceRequest = chain.request()

@ -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.getStringOrNull
import org.koitharu.kotatsu.parsers.util.json.mapJSON import org.koitharu.kotatsu.parsers.util.json.mapJSON
import org.koitharu.kotatsu.parsers.util.parseJson import org.koitharu.kotatsu.parsers.util.parseJson
import org.koitharu.kotatsu.parsers.util.parseJsonArray import org.koitharu.kotatsu.scrobbling.data.ScrobblerRepository
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage
import org.koitharu.kotatsu.scrobbling.anilist.data.model.AniListUser
import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity import org.koitharu.kotatsu.scrobbling.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerUser
import org.koitharu.kotatsu.utils.ext.toRequestBody import org.koitharu.kotatsu.utils.ext.toRequestBody
private const val REDIRECT_URI = "kotatsu://anilist-auth" private const val REDIRECT_URI = "kotatsu://anilist-auth"
@ -31,18 +31,18 @@ private const val MANGA_PAGE_SIZE = 10
class AniListRepository( class AniListRepository(
private val okHttp: OkHttpClient, private val okHttp: OkHttpClient,
private val storage: AniListStorage, private val storage: ScrobblerStorage,
private val db: MangaDatabase, private val db: MangaDatabase,
) { ) : ScrobblerRepository {
val oauthUrl: String override val oauthUrl: String
get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.ANILIST_CLIENT_ID}&" + get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.ANILIST_CLIENT_ID}&" +
"redirect_uri=${REDIRECT_URI}&response_type=code" "redirect_uri=${REDIRECT_URI}&response_type=code"
val isAuthorized: Boolean override val isAuthorized: Boolean
get() = storage.accessToken != null get() = storage.accessToken != null
suspend fun authorize(code: String?) { override suspend fun authorize(code: String?) {
val body = FormBody.Builder() val body = FormBody.Builder()
body.add("client_id", BuildConfig.ANILIST_CLIENT_ID) body.add("client_id", BuildConfig.ANILIST_CLIENT_ID)
body.add("client_secret", BuildConfig.ANILIST_CLIENT_SECRET) body.add("client_secret", BuildConfig.ANILIST_CLIENT_SECRET)
@ -62,7 +62,7 @@ class AniListRepository(
storage.refreshToken = response.getString("refresh_token") storage.refreshToken = response.getString("refresh_token")
} }
suspend fun loadUser(): AniListUser { override suspend fun loadUser(): ScrobblerUser {
val response = query( val response = query(
""" """
AniChartUser { AniChartUser {
@ -80,57 +80,61 @@ class AniListRepository(
return AniListUser(jo).also { storage.user = it } return AniListUser(jo).also { storage.user = it }
} }
fun getCachedUser(): AniListUser? { override val cachedUser: ScrobblerUser?
get() {
return storage.user return storage.user
} }
suspend fun unregister(mangaId: Long) { override suspend fun unregister(mangaId: Long) {
return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId) return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId)
} }
fun logout() { override fun logout() {
storage.clear() storage.clear()
} }
suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> { override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
val page = offset / MANGA_PAGE_SIZE val page = offset / MANGA_PAGE_SIZE
val pageOffset = offset % MANGA_PAGE_SIZE val response = query(
val url = BASE_URL.toHttpUrl().newBuilder() """
.addPathSegment("api") Page(page: $page, perPage: ${MANGA_PAGE_SIZE}) {
.addPathSegment("mangas") media(type: MANGA, isAdult: true, sort: SEARCH_MATCH, search: "${JSONObject.quote(query)}") {
.addEncodedQueryParameter("page", (page + 1).toString()) id
.addEncodedQueryParameter("limit", MANGA_PAGE_SIZE.toString()) title {
.addEncodedQueryParameter("censored", false.toString()) userPreferred
.addQueryParameter("search", query) native
.build() }
val request = Request.Builder().url(url).get().build() coverImage {
val response = okHttp.newCall(request).await().parseJsonArray() medium
val list = response.mapJSON { ScrobblerManga(it) } }
return if (pageOffset != 0) list.drop(pageOffset) else list siteUrl
}
}
""".trimIndent(),
)
val data = response.getJSONObject("data").getJSONObject("Page").getJSONArray("media")
return data.mapJSON { ScrobblerManga(it) }
} }
suspend fun createRate(mangaId: Long, shikiMangaId: Long) { override suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {
val user = getCachedUser() ?: loadUser() val response = query(
val payload = JSONObject() """
payload.put( mutation {
"user_rate", SaveMediaListEntry(mediaId: $scrobblerMangaId) {
JSONObject().apply { id
put("target_id", shikiMangaId) mediaId
put("target_type", "Manga") status
put("user_id", user.id) 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) 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() val payload = JSONObject()
payload.put( payload.put(
"user_rate", "user_rate",
@ -149,7 +153,7 @@ class AniListRepository(
saveRate(response, mangaId) 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() val payload = JSONObject()
payload.put( payload.put(
"user_rate", "user_rate",
@ -174,12 +178,23 @@ class AniListRepository(
saveRate(response, mangaId) saveRate(response, mangaId)
} }
suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {
val request = Request.Builder() val response = query(
.get() """
.url("${BASE_URL}api/mangas/$id") Media(id: $id) {
val response = okHttp.newCall(request.build()).await().parseJson() id
return ScrobblerMangaInfo(response) title {
userPreferred
}
coverImage {
large
}
description
siteUrl
}
""".trimIndent(),
)
return ScrobblerMangaInfo(response.getJSONObject("data").getJSONObject("Media"))
} }
private suspend fun saveRate(json: JSONObject, mangaId: Long) { private suspend fun saveRate(json: JSONObject, mangaId: Long) {
@ -187,29 +202,39 @@ class AniListRepository(
scrobbler = ScrobblerService.SHIKIMORI.id, scrobbler = ScrobblerService.SHIKIMORI.id,
id = json.getInt("id"), id = json.getInt("id"),
mangaId = mangaId, mangaId = mangaId,
targetId = json.getLong("target_id"), targetId = json.getLong("mediaId"),
status = json.getString("status"), status = json.getString("status"),
chapter = json.getInt("chapters"), chapter = json.getInt("progress"),
comment = json.getString("text"), comment = json.getString("notes"),
rating = json.getDouble("score").toFloat() / 10f, rating = json.getDouble("scoreRaw").toFloat() / 100f,
) )
db.scrobblingDao.insert(entity) db.scrobblingDao.insert(entity)
} }
private fun ScrobblerManga(json: JSONObject) = ScrobblerManga( private fun ScrobblerManga(json: JSONObject): ScrobblerManga {
val title = json.getJSONObject("title")
return ScrobblerManga(
id = json.getLong("id"), id = json.getLong("id"),
name = json.getString("name"), name = title.getString("userPreferred"),
altName = json.getStringOrNull("russian"), altName = title.getStringOrNull("native"),
cover = json.getJSONObject("image").getString("preview").toAbsoluteUrl("shikimori.one"), cover = json.getJSONObject("coverImage").getString("medium"),
url = json.getString("url").toAbsoluteUrl("shikimori.one"), url = json.getString("siteUrl"),
) )
}
private fun ScrobblerMangaInfo(json: JSONObject) = ScrobblerMangaInfo( private fun ScrobblerMangaInfo(json: JSONObject) = ScrobblerMangaInfo(
id = json.getLong("id"), id = json.getLong("id"),
name = json.getString("name"), name = json.getJSONObject("title").getString("userPreferred"),
cover = json.getJSONObject("image").getString("preview").toAbsoluteUrl("shikimori.one"), cover = json.getJSONObject("coverImage").getString("large"),
url = json.getString("url").toAbsoluteUrl("shikimori.one"), url = json.getString("siteUrl"),
descriptionHtml = json.getString("description_html"), 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 { private suspend fun query(query: String): JSONObject {

@ -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()
}
}

@ -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
}
}

@ -1,11 +1,8 @@
package org.koitharu.kotatsu.scrobbling.anilist.domain package org.koitharu.kotatsu.scrobbling.anilist.domain
import org.koitharu.kotatsu.core.db.MangaDatabase 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.anilist.data.AniListRepository
import org.koitharu.kotatsu.scrobbling.domain.Scrobbler 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.ScrobblerService
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
import javax.inject.Inject import javax.inject.Inject
@ -17,7 +14,7 @@ private const val RATING_MAX = 10f
class AniListScrobbler @Inject constructor( class AniListScrobbler @Inject constructor(
private val repository: AniListRepository, private val repository: AniListRepository,
db: MangaDatabase, db: MangaDatabase,
) : Scrobbler(db, ScrobblerService.ANILIST) { ) : Scrobbler(db, ScrobblerService.ANILIST, repository) {
init { init {
statuses[ScrobblingStatus.PLANNED] = "planned" statuses[ScrobblingStatus.PLANNED] = "planned"
@ -28,22 +25,6 @@ class AniListScrobbler @Inject constructor(
statuses[ScrobblingStatus.DROPPED] = "dropped" statuses[ScrobblingStatus.DROPPED] = "dropped"
} }
override val isAvailable: Boolean
get() = repository.isAuthorized
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
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( override suspend fun updateScrobblingInfo(
mangaId: Long, mangaId: Long,
rating: Float, rating: Float,
@ -60,12 +41,4 @@ class AniListScrobbler @Inject constructor(
comment = comment, comment = comment,
) )
} }
override suspend fun unregisterScrobbling(mangaId: Long) {
repository.unregister(mangaId)
}
override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {
return repository.getMangaInfo(id)
}
} }

@ -11,7 +11,7 @@ import coil.transform.CircleCropTransformation
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment 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.PreferenceIconTarget
import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.enqueueWith 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<Preference>(KEY_USER) ?: return val pref = findPreference<Preference>(KEY_USER) ?: return
pref.isSelectable = user == null pref.isSelectable = user == null
pref.title = user?.nickname ?: getString(R.string.sign_in) pref.title = user?.nickname ?: getString(R.string.sign_in)

@ -7,7 +7,7 @@ import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.ui.BaseViewModel import org.koitharu.kotatsu.base.ui.BaseViewModel
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository 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( class AniListSettingsViewModel @AssistedInject constructor(
private val repository: AniListRepository, private val repository: AniListRepository,
@ -17,7 +17,7 @@ class AniListSettingsViewModel @AssistedInject constructor(
val authorizationUrl: String val authorizationUrl: String
get() = repository.oauthUrl get() = repository.oauthUrl
val user = MutableLiveData<AniListUser?>() val user = MutableLiveData<ScrobblerUser?>()
init { init {
if (authCode != null) { if (authCode != null) {
@ -36,7 +36,7 @@ class AniListSettingsViewModel @AssistedInject constructor(
private fun loadUser() = launchJob(Dispatchers.Default) { private fun loadUser() = launchJob(Dispatchers.Default) {
val userModel = if (repository.isAuthorized) { val userModel = if (repository.isAuthorized) {
repository.getCachedUser()?.let(user::postValue) repository.cachedUser?.let(user::postValue)
repository.loadUser() repository.loadUser()
} else { } else {
null null

@ -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<ScrobblerManga>
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?)
}

@ -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()
}
}

@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.model.MangaChapter 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.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo
@ -21,18 +22,27 @@ import java.util.EnumMap
abstract class Scrobbler( abstract class Scrobbler(
protected val db: MangaDatabase, protected val db: MangaDatabase,
val scrobblerService: ScrobblerService, val scrobblerService: ScrobblerService,
private val repository: ScrobblerRepository,
) { ) {
private val infoCache = LongSparseArray<ScrobblerMangaInfo>() private val infoCache = LongSparseArray<ScrobblerMangaInfo>()
protected val statuses = EnumMap<ScrobblingStatus, String>(ScrobblingStatus::class.java) protected val statuses = EnumMap<ScrobblingStatus, String>(ScrobblingStatus::class.java)
abstract val isAvailable: Boolean val isAvailable: Boolean
get() = repository.isAuthorized
abstract suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
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? { suspend fun getScrobblingInfoOrNull(mangaId: Long): ScrobblingInfo? {
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return null val entity = db.scrobblingDao.find(scrobblerService.id, mangaId) ?: return null
@ -46,9 +56,13 @@ abstract class Scrobbler(
.map { it?.toScrobblingInfo(mangaId) } .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? { private suspend fun ScrobblingEntity.toScrobblingInfo(mangaId: Long): ScrobblingInfo? {
val mangaInfo = infoCache.getOrElse(targetId) { val mangaInfo = infoCache.getOrElse(targetId) {

@ -0,0 +1,8 @@
package org.koitharu.kotatsu.scrobbling.domain.model
import javax.inject.Qualifier
@Qualifier
annotation class ScrobblerType(
val service: ScrobblerService
)

@ -1,34 +1,22 @@
package org.koitharu.kotatsu.scrobbling.shikimori.data.model package org.koitharu.kotatsu.scrobbling.domain.model
import org.json.JSONObject class ScrobblerUser(
class ShikimoriUser(
val id: Long, val id: Long,
val nickname: String, val nickname: String,
val avatar: 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 { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
other as ShikimoriUser other as ScrobblerUser
if (id != other.id) return false if (id != other.id) return false
if (nickname != other.nickname) return false if (nickname != other.nickname) return false
if (avatar != other.avatar) return false if (avatar != other.avatar) return false
if (service != other.service) return false
return true return true
} }
@ -37,6 +25,7 @@ class ShikimoriUser(
var result = id.hashCode() var result = id.hashCode()
result = 31 * result + nickname.hashCode() result = 31 * result + nickname.hashCode()
result = 31 * result + avatar.hashCode() result = 31 * result + avatar.hashCode()
result = 31 * result + service.hashCode()
return result return result
} }
} }

@ -1,7 +1,5 @@
package org.koitharu.kotatsu.scrobbling.shikimori.data package org.koitharu.kotatsu.scrobbling.shikimori.data
import javax.inject.Inject
import javax.inject.Provider
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Authenticator import okhttp3.Authenticator
import okhttp3.Request import okhttp3.Request
@ -9,9 +7,14 @@ import okhttp3.Response
import okhttp3.Route import okhttp3.Route
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.network.CommonHeaders 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( class ShikimoriAuthenticator @Inject constructor(
private val storage: ShikimoriStorage, @ScrobblerType(ScrobblerService.SHIKIMORI) private val storage: ScrobblerStorage,
private val repositoryProvider: Provider<ShikimoriRepository>, private val repositoryProvider: Provider<ShikimoriRepository>,
) : Authenticator { ) : Authenticator {

@ -4,10 +4,11 @@ import okhttp3.Interceptor
import okhttp3.Response import okhttp3.Response
import okio.IOException import okio.IOException
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.scrobbling.data.ScrobblerStorage
private const val USER_AGENT_SHIKIMORI = "Kotatsu" 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 { override fun intercept(chain: Interceptor.Chain): Response {
val sourceRequest = chain.request() val sourceRequest = chain.request()

@ -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.parseJson
import org.koitharu.kotatsu.parsers.util.parseJsonArray import org.koitharu.kotatsu.parsers.util.parseJsonArray
import org.koitharu.kotatsu.parsers.util.toAbsoluteUrl 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.data.ScrobblingEntity
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerManga
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerMangaInfo
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblerService 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 import org.koitharu.kotatsu.utils.ext.toRequestBody
private const val REDIRECT_URI = "kotatsu://shikimori-auth" private const val REDIRECT_URI = "kotatsu://shikimori-auth"
@ -27,18 +29,18 @@ private const val MANGA_PAGE_SIZE = 10
class ShikimoriRepository( class ShikimoriRepository(
private val okHttp: OkHttpClient, private val okHttp: OkHttpClient,
private val storage: ShikimoriStorage, private val storage: ScrobblerStorage,
private val db: MangaDatabase, private val db: MangaDatabase,
) { ) : ScrobblerRepository {
val oauthUrl: String override val oauthUrl: String
get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.SHIKIMORI_CLIENT_ID}&" + get() = "${BASE_URL}oauth/authorize?client_id=${BuildConfig.SHIKIMORI_CLIENT_ID}&" +
"redirect_uri=$REDIRECT_URI&response_type=code&scope=" "redirect_uri=$REDIRECT_URI&response_type=code&scope="
val isAuthorized: Boolean override val isAuthorized: Boolean
get() = storage.accessToken != null get() = storage.accessToken != null
suspend fun authorize(code: String?) { override suspend fun authorize(code: String?) {
val body = FormBody.Builder() val body = FormBody.Builder()
body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID) body.add("client_id", BuildConfig.SHIKIMORI_CLIENT_ID)
body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET) body.add("client_secret", BuildConfig.SHIKIMORI_CLIENT_SECRET)
@ -58,7 +60,7 @@ class ShikimoriRepository(
storage.refreshToken = response.getString("refresh_token") storage.refreshToken = response.getString("refresh_token")
} }
suspend fun loadUser(): ShikimoriUser { override suspend fun loadUser(): ScrobblerUser {
val request = Request.Builder() val request = Request.Builder()
.get() .get()
.url("${BASE_URL}api/users/whoami") .url("${BASE_URL}api/users/whoami")
@ -66,19 +68,20 @@ class ShikimoriRepository(
return ShikimoriUser(response).also { storage.user = it } return ShikimoriUser(response).also { storage.user = it }
} }
fun getCachedUser(): ShikimoriUser? { override val cachedUser: ScrobblerUser?
get() {
return storage.user return storage.user
} }
suspend fun unregister(mangaId: Long) { override suspend fun unregister(mangaId: Long) {
return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId) return db.scrobblingDao.delete(ScrobblerService.SHIKIMORI.id, mangaId)
} }
fun logout() { override fun logout() {
storage.clear() storage.clear()
} }
suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> { override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
val page = offset / MANGA_PAGE_SIZE val page = offset / MANGA_PAGE_SIZE
val pageOffset = offset % MANGA_PAGE_SIZE val pageOffset = offset % MANGA_PAGE_SIZE
val url = BASE_URL.toHttpUrl().newBuilder() val url = BASE_URL.toHttpUrl().newBuilder()
@ -95,8 +98,8 @@ class ShikimoriRepository(
return if (pageOffset != 0) list.drop(pageOffset) else list return if (pageOffset != 0) list.drop(pageOffset) else list
} }
suspend fun createRate(mangaId: Long, shikiMangaId: Long) { override suspend fun createRate(mangaId: Long, shikiMangaId: Long) {
val user = getCachedUser() ?: loadUser() val user = cachedUser ?: loadUser()
val payload = JSONObject() val payload = JSONObject()
payload.put( payload.put(
"user_rate", "user_rate",
@ -116,7 +119,7 @@ class ShikimoriRepository(
saveRate(response, mangaId) 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() val payload = JSONObject()
payload.put( payload.put(
"user_rate", "user_rate",
@ -135,7 +138,7 @@ class ShikimoriRepository(
saveRate(response, mangaId) 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() val payload = JSONObject()
payload.put( payload.put(
"user_rate", "user_rate",
@ -160,7 +163,7 @@ class ShikimoriRepository(
saveRate(response, mangaId) saveRate(response, mangaId)
} }
suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo { override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {
val request = Request.Builder() val request = Request.Builder()
.get() .get()
.url("${BASE_URL}api/mangas/$id") .url("${BASE_URL}api/mangas/$id")
@ -197,4 +200,11 @@ class ShikimoriRepository(
url = json.getString("url").toAbsoluteUrl("shikimori.one"), url = json.getString("url").toAbsoluteUrl("shikimori.one"),
descriptionHtml = json.getString("description_html"), 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,
)
} }

@ -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()
}
}

@ -1,15 +1,12 @@
package org.koitharu.kotatsu.scrobbling.shikimori.domain 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.core.db.MangaDatabase
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.scrobbling.domain.Scrobbler 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.ScrobblerService
import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus import org.koitharu.kotatsu.scrobbling.domain.model.ScrobblingStatus
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
import javax.inject.Inject
import javax.inject.Singleton
private const val RATING_MAX = 10f private const val RATING_MAX = 10f
@ -17,7 +14,7 @@ private const val RATING_MAX = 10f
class ShikimoriScrobbler @Inject constructor( class ShikimoriScrobbler @Inject constructor(
private val repository: ShikimoriRepository, private val repository: ShikimoriRepository,
db: MangaDatabase, db: MangaDatabase,
) : Scrobbler(db, ScrobblerService.SHIKIMORI) { ) : Scrobbler(db, ScrobblerService.SHIKIMORI, repository) {
init { init {
statuses[ScrobblingStatus.PLANNED] = "planned" statuses[ScrobblingStatus.PLANNED] = "planned"
@ -28,22 +25,6 @@ class ShikimoriScrobbler @Inject constructor(
statuses[ScrobblingStatus.DROPPED] = "dropped" statuses[ScrobblingStatus.DROPPED] = "dropped"
} }
override val isAvailable: Boolean
get() = repository.isAuthorized
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
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( override suspend fun updateScrobblingInfo(
mangaId: Long, mangaId: Long,
rating: Float, rating: Float,
@ -60,12 +41,4 @@ class ShikimoriScrobbler @Inject constructor(
comment = comment, comment = comment,
) )
} }
override suspend fun unregisterScrobbling(mangaId: Long) {
repository.unregister(mangaId)
}
override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {
return repository.getMangaInfo(id)
}
} }

@ -9,14 +9,14 @@ import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.transform.CircleCropTransformation import coil.transform.CircleCropTransformation
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment 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.PreferenceIconTarget
import org.koitharu.kotatsu.utils.ext.assistedViewModels import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.withArgs import org.koitharu.kotatsu.utils.ext.withArgs
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ShikimoriSettingsFragment : BasePreferenceFragment(R.string.shikimori) { class ShikimoriSettingsFragment : BasePreferenceFragment(R.string.shikimori) {
@ -47,11 +47,12 @@ class ShikimoriSettingsFragment : BasePreferenceFragment(R.string.shikimori) {
viewModel.logout() viewModel.logout()
true true
} }
else -> super.onPreferenceTreeClick(preference) else -> super.onPreferenceTreeClick(preference)
} }
} }
private fun onUserChanged(user: ShikimoriUser?) { private fun onUserChanged(user: ScrobblerUser?) {
val pref = findPreference<Preference>(KEY_USER) ?: return val pref = findPreference<Preference>(KEY_USER) ?: return
pref.isSelectable = user == null pref.isSelectable = user == null
pref.title = user?.nickname ?: getString(R.string.sign_in) pref.title = user?.nickname ?: getString(R.string.sign_in)

@ -6,8 +6,8 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.koitharu.kotatsu.base.ui.BaseViewModel 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.ShikimoriRepository
import org.koitharu.kotatsu.scrobbling.shikimori.data.model.ShikimoriUser
class ShikimoriSettingsViewModel @AssistedInject constructor( class ShikimoriSettingsViewModel @AssistedInject constructor(
private val repository: ShikimoriRepository, private val repository: ShikimoriRepository,
@ -17,7 +17,7 @@ class ShikimoriSettingsViewModel @AssistedInject constructor(
val authorizationUrl: String val authorizationUrl: String
get() = repository.oauthUrl get() = repository.oauthUrl
val user = MutableLiveData<ShikimoriUser?>() val user = MutableLiveData<ScrobblerUser?>()
init { init {
if (authCode != null) { if (authCode != null) {
@ -36,7 +36,7 @@ class ShikimoriSettingsViewModel @AssistedInject constructor(
private fun loadUser() = launchJob(Dispatchers.Default) { private fun loadUser() = launchJob(Dispatchers.Default) {
val userModel = if (repository.isAuthorized) { val userModel = if (repository.isAuthorized) {
repository.getCachedUser()?.let(user::postValue) repository.cachedUser?.let(user::postValue)
repository.loadUser() repository.loadUser()
} else { } else {
null null

@ -201,7 +201,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
private fun bindShikimoriSummary() { private fun bindShikimoriSummary() {
findPreference<Preference>(AppSettings.KEY_SHIKIMORI)?.summary = if (shikimoriRepository.isAuthorized) { findPreference<Preference>(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 { } else {
getString(R.string.disabled) getString(R.string.disabled)
} }
@ -209,7 +209,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
private fun bindAniListSummary() { private fun bindAniListSummary() {
findPreference<Preference>(AppSettings.KEY_ANILIST)?.summary = if (aniListRepository.isAuthorized) { findPreference<Preference>(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 { } else {
getString(R.string.disabled) getString(R.string.disabled)
} }

Loading…
Cancel
Save