Manga repository authorization support

This commit is contained in:
Koitharu
2021-09-08 07:27:25 +03:00
parent c4585c81e1
commit 593624fdb9
20 changed files with 312 additions and 78 deletions

View File

@@ -67,6 +67,23 @@ open class MangaLoaderContext(
})
}
fun getCookies(domain: String): List<Cookie> {
val url = HttpUrl.Builder()
.scheme(SCHEME_HTTP)
.host(domain)
.build()
return cookieJar.loadForRequest(url)
}
fun copyCookies(oldDomain: String, newDomain: String) {
val url = HttpUrl.Builder()
.scheme(SCHEME_HTTP)
.host(oldDomain)
val cookies = cookieJar.loadForRequest(url.build())
url.host(newDomain)
cookieJar.saveFromResponse(url.build(), cookies)
}
private companion object {
private const val SCHEME_HTTP = "http"

View File

@@ -38,6 +38,7 @@ abstract class BaseFragment<B : ViewBinding> : Fragment(), OnApplyWindowInsetsLi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lastInsets = Insets.NONE
ViewCompat.setOnApplyWindowInsetsListener(view, this)
}

View File

@@ -1,10 +1,8 @@
package org.koitharu.kotatsu.browser
import android.graphics.Bitmap
import android.webkit.WebResourceResponse
import android.webkit.WebView
import okhttp3.OkHttpClient
import okhttp3.Request
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koitharu.kotatsu.core.network.WebViewClientCompat
@@ -27,19 +25,4 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClientCompat
super.onPageCommitVisible(view, url)
callback.onTitleChanged(view.title.orEmpty(), url)
}
override fun shouldInterceptRequestCompat(view: WebView, url: String): WebResourceResponse? {
return runCatching {
val request = Request.Builder()
.url(url)
.build()
val response = okHttp.newCall(request).execute()
val ct = response.body?.contentType()
WebResourceResponse(
"${ct?.type}/${ct?.subtype}",
ct?.charset()?.name() ?: "utf-8",
response.body?.byteStream()
)
}.getOrNull()
}
}

View File

@@ -5,6 +5,6 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class MangaFilter(
val sortOrder: SortOrder,
val tag: MangaTag?
val sortOrder: SortOrder?,
val tag: MangaTag?,
) : Parcelable

View File

@@ -0,0 +1,8 @@
package org.koitharu.kotatsu.core.parser
interface MangaRepositoryAuthProvider {
val authUrl: String
fun isAuthorized(): Boolean
}

View File

@@ -27,5 +27,6 @@ interface SourceSettings {
const val KEY_DOMAIN = "domain"
const val KEY_USE_SSL = "ssl"
const val KEY_AUTH = "auth"
}
}

View File

@@ -7,7 +7,6 @@ import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaTag
import org.koitharu.kotatsu.core.model.SortOrder
import java.util.*
import kotlin.collections.ArrayList
class FilterAdapter(
sortOrders: List<SortOrder> = emptyList(),
@@ -19,7 +18,7 @@ class FilterAdapter(
private val sortOrders = ArrayList<SortOrder>(sortOrders)
private val tags = ArrayList(Collections.singletonList(null) + tags)
private var currentState = state ?: MangaFilter(sortOrders.first(), null)
private var currentState = state ?: MangaFilter(sortOrders.firstOrNull(), null)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType) {
VIEW_TYPE_SORT -> FilterSortHolder(parent).apply {

View File

@@ -61,7 +61,7 @@ class LocalListViewModel(
launchLoadingJob(Dispatchers.Default) {
try {
listError.value = null
mangaList.value = repository.getList(0)
mangaList.value = repository.getList(0, tags = null)
} catch (e: Throwable) {
listError.value = e
}

View File

@@ -3,16 +3,19 @@ package org.koitharu.kotatsu.reader.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import androidx.core.graphics.Insets
import androidx.core.view.updatePadding
import androidx.fragment.app.commit
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.databinding.ActivitySettingsSimpleBinding
import org.koitharu.kotatsu.settings.MainSettingsFragment
import org.koitharu.kotatsu.settings.NetworkSettingsFragment
import org.koitharu.kotatsu.settings.ReaderSettingsFragment
import org.koitharu.kotatsu.settings.SourceSettingsFragment
class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
@@ -25,6 +28,9 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
R.id.container, when (intent?.action) {
Intent.ACTION_MANAGE_NETWORK_USAGE -> NetworkSettingsFragment()
ACTION_READER -> ReaderSettingsFragment()
ACTION_SOURCE -> SourceSettingsFragment.newInstance(
intent.getParcelableExtra(EXTRA_SOURCE) ?: MangaSource.LOCAL
)
else -> MainSettingsFragment()
}
)
@@ -43,9 +49,17 @@ class SimpleSettingsActivity : BaseActivity<ActivitySettingsSimpleBinding>() {
private const val ACTION_READER =
"${BuildConfig.APPLICATION_ID}.action.MANAGE_READER_SETTINGS"
private const val ACTION_SOURCE =
"${BuildConfig.APPLICATION_ID}.action.MANAGE_SOURCE_SETTINGS"
private const val EXTRA_SOURCE = "source"
fun newReaderSettingsIntent(context: Context) =
Intent(context, SimpleSettingsActivity::class.java)
.setAction(ACTION_READER)
fun newSourceSettingsIntent(context: Context, source: MangaSource) =
Intent(context, SimpleSettingsActivity::class.java)
.setAction(ACTION_SOURCE)
.putExtra(EXTRA_SOURCE, source as Parcelable)
}
}

View File

@@ -1,10 +1,15 @@
package org.koitharu.kotatsu.remotelist.ui
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaFilter
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.list.ui.MangaListFragment
import org.koitharu.kotatsu.reader.ui.SimpleSettingsActivity
import org.koitharu.kotatsu.utils.ext.parcelableArgument
import org.koitharu.kotatsu.utils.ext.withArgs
@@ -29,6 +34,26 @@ class RemoteListFragment : MangaListFragment() {
super.onFilterChanged(filter)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.opt_list_remote, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.action_source_settings -> {
startActivity(
SimpleSettingsActivity.newSourceSettingsIntent(
context ?: return false,
source,
)
)
true
}
else -> super.onOptionsItemSelected(item)
}
}
companion object {
private const val ARG_SOURCE = "provider"

View File

@@ -90,6 +90,9 @@ class RemoteListViewModel(
}
hasNextPage.value = list.isNotEmpty()
} catch (e: Throwable) {
if (BuildConfig.DEBUG) {
e.printStackTrace()
}
listError.value = e
}
}

View File

@@ -12,7 +12,6 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.*
import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import java.util.*
class SearchViewModel(
private val repository: MangaRepository,
@@ -74,6 +73,7 @@ class SearchViewModel(
listError.value = null
val list = repository.getList(
offset = if (append) mangaList.value?.size ?: 0 else 0,
tags = null,
query = query
)
if (!append) {

View File

@@ -76,15 +76,7 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
true
}
AppSettings.KEY_COOKIES_CLEAR -> {
viewLifecycleScope.launch {
val cookieJar = get<AndroidCookieJar>()
cookieJar.clear()
Snackbar.make(
listView ?: return@launch,
R.string.cookies_cleared,
Snackbar.LENGTH_SHORT
).show()
}
clearCookies()
true
}
AppSettings.KEY_SEARCH_HISTORY_CLEAR -> {
@@ -144,4 +136,22 @@ class HistorySettingsFragment : BasePreferenceFragment(R.string.history_and_cach
}
}.show()
}
private fun clearCookies() {
AlertDialog.Builder(context ?: return)
.setTitle(R.string.clear_cookies)
.setMessage(R.string.text_clear_cookies_prompt)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.clear) { _, _ ->
viewLifecycleScope.launch {
val cookieJar = get<AndroidCookieJar>()
cookieJar.clear()
Snackbar.make(
listView ?: return@launch,
R.string.cookies_cleared,
Snackbar.LENGTH_SHORT
).show()
}
}.show()
}
}

View File

@@ -9,8 +9,10 @@ import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.settings.utils.EditTextBindListener
import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
@@ -20,6 +22,7 @@ import org.koitharu.kotatsu.utils.ext.withArgs
class SourceSettingsFragment : PreferenceFragmentCompat() {
private val source by parcelableArgument<MangaSource>(EXTRA_SOURCE)
private var repository: RemoteMangaRepository? = null
override fun onResume() {
super.onResume()
@@ -29,6 +32,7 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
preferenceManager.sharedPreferencesName = source.name
val repo = mangaRepositoryOf(source) as? RemoteMangaRepository ?: return
repository = repo
addPreferencesFromResource(R.xml.pref_source)
val screen = preferenceScreen
val prefsMap = ArrayMap<String, Any>(screen.preferenceCount)
@@ -41,13 +45,32 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
initPreferenceWithDefaultValue(pref, defValue)
}
}
findPreference<Preference>(SourceSettings.KEY_AUTH)?.run {
isVisible = repo is MangaRepositoryAuthProvider
isEnabled = (repo as? MangaRepositoryAuthProvider)?.isAuthorized() == false
}
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
SourceSettings.KEY_AUTH -> {
startActivity(
SourceAuthActivity.newIntent(
context ?: return false,
source,
)
)
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
private fun initPreferenceWithDefaultValue(preference: Preference, defaultValue: Any) {
when(preference) {
when (preference) {
is EditTextPreference -> {
preference.summaryProvider = EditTextDefaultSummaryProvider(defaultValue.toString())
when(preference.key) {
when (preference.key) {
SourceSettings.KEY_DOMAIN -> preference.setOnBindEditTextListener(
EditTextBindListener(
EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI,

View File

@@ -0,0 +1,114 @@
package org.koitharu.kotatsu.settings.sources.auth
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.MenuItem
import android.widget.Toast
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserCallback
import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
private lateinit var repository: MangaRepositoryAuthProvider
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityBrowserBinding.inflate(layoutInflater))
val source = intent?.getParcelableExtra<MangaSource>(EXTRA_SOURCE)
if (source == null) {
finish()
return
}
repository = mangaRepositoryOf(source) as? MangaRepositoryAuthProvider ?: run {
Toast.makeText(
this,
getString(R.string.auth_not_supported_by, source.title),
Toast.LENGTH_SHORT
).show()
finishAfterTransition()
return
}
supportActionBar?.run {
setDisplayHomeAsUpEnabled(true)
setHomeAsUpIndicator(R.drawable.ic_cross)
}
with(binding.webView.settings) {
javaScriptEnabled = true
}
binding.webView.webViewClient = BrowserClient(this)
val url = repository.authUrl
onTitleChanged(
source.title,
getString(R.string.loading_)
)
binding.webView.loadUrl(url)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> {
binding.webView.stopLoading()
finishAfterTransition()
true
}
else -> super.onOptionsItemSelected(item)
}
override fun onBackPressed() {
if (binding.webView.canGoBack()) {
binding.webView.goBack()
} else {
super.onBackPressed()
}
}
override fun onPause() {
binding.webView.onPause()
super.onPause()
}
override fun onResume() {
super.onResume()
binding.webView.onResume()
}
override fun onLoadingStateChanged(isLoading: Boolean) {
binding.progressBar.isVisible = isLoading
if (!isLoading && repository.isAuthorized()) {
Toast.makeText(this, R.string.auth_complete, Toast.LENGTH_SHORT).show()
finishAfterTransition()
}
}
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) {
this.title = title
supportActionBar?.subtitle = subtitle
}
override fun onWindowInsetsChanged(insets: Insets) {
binding.appbar.updatePadding(top = insets.top)
binding.webView.updatePadding(bottom = insets.bottom)
}
companion object {
private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, source: MangaSource): Intent {
return Intent(context, SourceAuthActivity::class.java)
.putExtra(EXTRA_SOURCE, source as Parcelable)
}
}
}