Unify scrobblers implementation
This commit is contained in:
@@ -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(
|
||||
|
||||
@@ -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<AniListRepository>,
|
||||
) : Authenticator {
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<ScrobblerManga> {
|
||||
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
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<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(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Preference>(KEY_USER) ?: return
|
||||
pref.isSelectable = user == null
|
||||
pref.title = user?.nickname ?: getString(R.string.sign_in)
|
||||
|
||||
@@ -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<AniListUser?>()
|
||||
val user = MutableLiveData<ScrobblerUser?>()
|
||||
|
||||
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
|
||||
|
||||
@@ -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 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<ScrobblerMangaInfo>()
|
||||
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? {
|
||||
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) {
|
||||
|
||||
@@ -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 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<ShikimoriRepository>,
|
||||
) : Authenticator {
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<ScrobblerManga> {
|
||||
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
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<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(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Preference>(KEY_USER) ?: return
|
||||
pref.isSelectable = user == null
|
||||
pref.title = user?.nickname ?: getString(R.string.sign_in)
|
||||
|
||||
@@ -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<ShikimoriUser?>()
|
||||
val user = MutableLiveData<ScrobblerUser?>()
|
||||
|
||||
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
|
||||
|
||||
@@ -201,7 +201,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
|
||||
private fun bindShikimoriSummary() {
|
||||
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 {
|
||||
getString(R.string.disabled)
|
||||
}
|
||||
@@ -209,7 +209,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
|
||||
|
||||
private fun bindAniListSummary() {
|
||||
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 {
|
||||
getString(R.string.disabled)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user