Manga repository authorization support
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,8 @@
|
||||
package org.koitharu.kotatsu.core.parser
|
||||
|
||||
interface MangaRepositoryAuthProvider {
|
||||
|
||||
val authUrl: String
|
||||
|
||||
fun isAuthorized(): Boolean
|
||||
}
|
||||
@@ -27,5 +27,6 @@ interface SourceSettings {
|
||||
|
||||
const val KEY_DOMAIN = "domain"
|
||||
const val KEY_USE_SSL = "ssl"
|
||||
const val KEY_AUTH = "auth"
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -90,6 +90,9 @@ class RemoteListViewModel(
|
||||
}
|
||||
hasNextPage.value = list.isNotEmpty()
|
||||
} catch (e: Throwable) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
listError.value = e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user