Merge branch 'feature/kitsu' of github.com:KotatsuApp/Kotatsu into devel
commit
d0ee185d2e
@ -0,0 +1,22 @@
|
|||||||
|
package org.koitharu.kotatsu.scrobbling.kitsu.data
|
||||||
|
|
||||||
|
import okhttp3.Authenticator
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.Route
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Provider
|
||||||
|
|
||||||
|
class KitsuAuthenticator @Inject constructor(
|
||||||
|
@ScrobblerType(ScrobblerService.KITSU) private val storage: ScrobblerStorage,
|
||||||
|
private val repositoryProvider: Provider<KitsuRepository>,
|
||||||
|
) : Authenticator {
|
||||||
|
|
||||||
|
override fun authenticate(route: Route?, response: Response): Request? {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
package org.koitharu.kotatsu.scrobbling.kitsu.data
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||||
|
|
||||||
|
private const val JSON = "application/json"
|
||||||
|
|
||||||
|
class KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor {
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val sourceRequest = chain.request()
|
||||||
|
val request = sourceRequest.newBuilder()
|
||||||
|
request.header(CommonHeaders.CONTENT_TYPE, JSON)
|
||||||
|
request.header(CommonHeaders.ACCEPT, JSON)
|
||||||
|
if (!sourceRequest.url.pathSegments.contains("oauth")) {
|
||||||
|
storage.accessToken?.let {
|
||||||
|
request.header(CommonHeaders.AUTHORIZATION, "Bearer $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chain.proceed(request.build())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,92 @@
|
|||||||
|
package org.koitharu.kotatsu.scrobbling.kitsu.data
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
import org.koitharu.kotatsu.parsers.util.await
|
||||||
|
import org.koitharu.kotatsu.parsers.util.parseJson
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
||||||
|
|
||||||
|
private const val BASE_WEB_URL = "https://kitsu.io"
|
||||||
|
|
||||||
|
class KitsuRepository(
|
||||||
|
@ApplicationContext context: Context,
|
||||||
|
private val okHttp: OkHttpClient,
|
||||||
|
private val storage: ScrobblerStorage,
|
||||||
|
private val db: MangaDatabase,
|
||||||
|
) : ScrobblerRepository {
|
||||||
|
|
||||||
|
private val clientId = context.getString(R.string.kitsu_clientId)
|
||||||
|
private val clientSecret = context.getString(R.string.kitsu_clientSecret)
|
||||||
|
|
||||||
|
override val oauthUrl: String = "kotatsu+kitsu://auth"
|
||||||
|
|
||||||
|
override val isAuthorized: Boolean
|
||||||
|
get() = storage.accessToken != null
|
||||||
|
|
||||||
|
override val cachedUser: ScrobblerUser?
|
||||||
|
get() {
|
||||||
|
return storage.user
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun authorize(code: String?) {
|
||||||
|
val body = FormBody.Builder()
|
||||||
|
if (code != null) {
|
||||||
|
body.add("grant_type", "password")
|
||||||
|
body.add("username", "test@test")
|
||||||
|
body.add("password", "test")
|
||||||
|
} else {
|
||||||
|
body.add("grant_type", "refresh_token")
|
||||||
|
body.add("refresh_token", checkNotNull(storage.refreshToken))
|
||||||
|
}
|
||||||
|
val request = Request.Builder()
|
||||||
|
.post(body.build())
|
||||||
|
.url("${BASE_WEB_URL}/api/oauth/token")
|
||||||
|
val response = okHttp.newCall(request.build()).await().parseJson()
|
||||||
|
storage.accessToken = response.getString("access_token")
|
||||||
|
storage.refreshToken = response.getString("refresh_token")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun loadUser(): ScrobblerUser {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun logout() {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unregister(mangaId: Long) {
|
||||||
|
return db.getScrobblingDao().delete(ScrobblerService.KITSU.id, mangaId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
package org.koitharu.kotatsu.scrobbling.kitsu.domain
|
||||||
|
|
||||||
|
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||||
|
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||||
|
import org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class KitsuScrobbler @Inject constructor(
|
||||||
|
private val repository: KitsuRepository,
|
||||||
|
db: MangaDatabase,
|
||||||
|
) : Scrobbler(db, ScrobblerService.KITSU, repository) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
statuses[ScrobblingStatus.PLANNED] = "planned"
|
||||||
|
statuses[ScrobblingStatus.READING] = "current"
|
||||||
|
statuses[ScrobblingStatus.COMPLETED] = "completed"
|
||||||
|
statuses[ScrobblingStatus.ON_HOLD] = "on_hold"
|
||||||
|
statuses[ScrobblingStatus.DROPPED] = "dropped"
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateScrobblingInfo(
|
||||||
|
mangaId: Long,
|
||||||
|
rating: Float,
|
||||||
|
status: ScrobblingStatus?,
|
||||||
|
comment: String?
|
||||||
|
) {
|
||||||
|
val entity = db.getScrobblingDao().find(scrobblerService.id, mangaId)
|
||||||
|
requireNotNull(entity) { "Scrobbling info for manga $mangaId not found" }
|
||||||
|
repository.updateRate(
|
||||||
|
rateId = entity.id,
|
||||||
|
mangaId = entity.mangaId,
|
||||||
|
rating = rating,
|
||||||
|
status = statuses[status],
|
||||||
|
comment = comment,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
package org.koitharu.kotatsu.scrobbling.kitsu.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.graphics.Insets
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||||
|
import org.koitharu.kotatsu.databinding.ActivityKitsuAuthBinding
|
||||||
|
|
||||||
|
class KitsuAuthActivity : BaseActivity<ActivityKitsuAuthBinding>() {
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(ActivityKitsuAuthBinding.inflate(layoutInflater))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWindowInsetsChanged(insets: Insets) {
|
||||||
|
val basePadding = resources.getDimensionPixelOffset(R.dimen.screen_padding)
|
||||||
|
viewBinding.root.setPadding(
|
||||||
|
basePadding + insets.left,
|
||||||
|
basePadding + insets.top,
|
||||||
|
basePadding + insets.right,
|
||||||
|
basePadding + insets.bottom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newIntent(context: Context) = Intent(context, KitsuAuthActivity::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M1.429,5.441a12.478,12.478 0,0 0,1.916 2.056c0.011,0.011 0.022,0.011 0.022,0.022 0.452,0.387 1.313,0.947 1.937,1.173 0,0 3.886,1.496 4.091,1.582a1.4,1.4 0,0 0,0.237 0.075,0.694 0.694,0 0,0 0.808,-0.549c0.011,-0.065 0.022,-0.172 0.022,-0.248L10.462,5.161c0.011,-0.667 -0.205,-1.679 -0.398,-2.239 0,-0.011 -0.011,-0.022 -0.011,-0.032A11.979,11.979 0,0 0,8.824 0.36L8.781,0.285a0.697,0.697 0,0 0,-0.958 -0.162c-0.054,0.032 -0.086,0.075 -0.129,0.119L7.608,0.36a4.743,4.743 0,0 0,-0.786 3.412,8.212 8.212,0 0,0 -0.775,0.463c-0.043,0.032 -0.42,0.291 -0.71,0.56A4.803,4.803 0,0 0,1.87 4.3c-0.043,0.011 -0.097,0.021 -0.14,0.032 -0.054,0.022 -0.107,0.043 -0.151,0.076a0.702,0.702 0,0 0,-0.193 0.958l0.043,0.075zM8.222,1.07c0.366,0.614 0.678,1.249 0.925,1.917 -0.495,0.086 -0.98,0.215 -1.453,0.388a3.918,3.918 0,0 1,0.528 -2.305zM4.658,5.463a7.467,7.467 0,0 0,-0.893 1.216,11.68 11.68,0 0,1 -1.453,-1.55 3.825,3.825 0,0 1,2.346 0.334zM17.706,5.161a7.673,7.673 0,0 0,-2.347 -0.474,7.583 7.583,0 0,0 -3.811,0.818l-0.215,0.108v3.918c0,0.054 0,0.258 -0.032,0.431a1.535,1.535 0,0 1,-0.646 0.98,1.545 1.545,0 0,1 -1.152,0.247 2.618,2.618 0,0 1,-0.409 -0.118,747.6 747.6,0 0,1 -3.402,-1.313 8.9,8.9 0,0 0,-0.323 -0.129,30.597 30.597,0 0,0 -3.822,3.832l-0.075,0.086a0.698,0.698 0,0 0,0.538 1.098,0.676 0.676,0 0,0 0.42,-0.118c0.011,-0.011 0.022,-0.022 0.043,-0.032 1.313,-0.947 2.756,-1.712 4.284,-2.325a0.7,0.7 0,0 1,0.818 0.13,0.704 0.704,0 0,1 0.054,0.915l-0.237,0.388a20.277,20.277 0,0 0,-1.97 4.306l-0.032,0.129a0.646,0.646 0,0 0,0.108 0.538,0.713 0.713,0 0,0 0.549,0.301 0.657,0.657 0,0 0,0.42 -0.118c0.054,-0.043 0.108,-0.086 0.151,-0.14l0.043,-0.065a18.95,18.95 0,0 1,1.765 -2.153,20.156 20.156,0 0,1 10.797,-6.018c0.032,-0.011 0.065,-0.011 0.097,-0.011 0.237,0.011 0.42,0.215 0.409,0.452a0.424,0.424 0,0 1,-0.344 0.398c-3.908,0.829 -10.948,5.469 -8.483,12.208 0.043,0.108 0.075,0.172 0.129,0.269a0.71,0.71 0,0 0,0.538 0.301,0.742 0.742,0 0,0 0.657,-0.398c0.398,-0.754 1.152,-1.593 3.326,-2.497 6.061,-2.508 7.062,-6.093 7.17,-8.364v-0.129a7.716,7.716 0,0 0,-5.016 -7.451zM11.623,22.923c-0.56,-1.669 -0.506,-3.283 0.151,-4.823 1.26,2.035 3.456,2.207 3.456,2.207 -2.25,0.937 -3.133,1.863 -3.607,2.616z"
|
||||||
|
android:fillColor="#000000"/>
|
||||||
|
</vector>
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="@dimen/screen_padding">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:drawablePadding="16dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="@string/kitsu"
|
||||||
|
android:textAppearance="?textAppearanceHeadline5"
|
||||||
|
app:drawableTint="?colorPrimary"
|
||||||
|
app:drawableTopCompat="@drawable/ic_kitsu"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView_subtitle"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentTop="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:text="@string/email_password_enter_hint"
|
||||||
|
android:textAppearance="?textAppearanceSubtitle1" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/layout_email"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/textView_subtitle"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
app:errorIconDrawable="@null"
|
||||||
|
app:hintEnabled="false">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/edit_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="emailAddress"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textEmailAddress"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:hint="Email" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/layout_password"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/layout_email"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
app:endIconMode="password_toggle"
|
||||||
|
app:errorIconDrawable="@null"
|
||||||
|
app:hintEnabled="false">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/edit_password"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:autofillHints="password"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:inputType="textPassword"
|
||||||
|
android:maxLength="24"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:hint="Password" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_cancel"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentStart="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:text="@android:string/cancel" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button_done"
|
||||||
|
style="@style/Widget.Material3.Button.TonalButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:layout_alignParentBottom="true"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text="@string/done"
|
||||||
|
tools:ignore="RelativeOverlap" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/layout_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
Loading…
Reference in New Issue