Fix some authorization issues

This commit is contained in:
Koitharu
2022-03-09 18:27:35 +02:00
parent 2947cd3038
commit 3aed24fb49
16 changed files with 270 additions and 84 deletions

View File

@@ -28,9 +28,8 @@ abstract class BaseActivity<B : ViewBinding> : AppCompatActivity(), OnApplyWindo
protected lateinit var binding: B
private set
protected val exceptionResolver by lazy(LazyThreadSafetyMode.NONE) {
ExceptionResolver(this, supportFragmentManager)
}
@Suppress("LeakingThis")
protected val exceptionResolver = ExceptionResolver(this)
private var lastInsets: Insets = Insets.NONE

View File

@@ -20,9 +20,8 @@ abstract class BaseFragment<B : ViewBinding> : Fragment(), OnApplyWindowInsetsLi
protected val binding: B
get() = checkNotNull(viewBinding)
protected val exceptionResolver by lazy(LazyThreadSafetyMode.NONE) {
ExceptionResolver(viewLifecycleOwner, childFragmentManager)
}
@Suppress("LeakingThis")
protected val exceptionResolver = ExceptionResolver(this)
private var lastInsets: Insets = Insets.NONE

View File

@@ -3,9 +3,10 @@ package org.koitharu.kotatsu.core.exceptions
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.MangaSource
class AuthRequiredException(
val url: String
val source: MangaSource,
) : RuntimeException("Authorization required"), ResolvableException {
@StringRes

View File

@@ -1,40 +1,71 @@
package org.koitharu.kotatsu.core.exceptions.resolve
import android.util.ArrayMap
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.LifecycleOwner
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.suspendCancellableCoroutine
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
import org.koitharu.kotatsu.core.exceptions.AuthRequiredException
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.utils.TaggedActivityResult
import org.koitharu.kotatsu.utils.isSuccess
import kotlin.coroutines.Continuation
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class ExceptionResolver(
private val lifecycleOwner: LifecycleOwner,
private val fm: FragmentManager
) {
class ExceptionResolver private constructor(
private val activity: FragmentActivity?,
private val fragment: Fragment?,
): ActivityResultCallback<TaggedActivityResult> {
private val continuations = ArrayMap<String, Continuation<Boolean>>(1)
private lateinit var sourceAuthContract: ActivityResultLauncher<MangaSource>
constructor(activity: FragmentActivity) : this(activity = activity, fragment = null) {
sourceAuthContract = activity.registerForActivityResult(SourceAuthActivity.Contract(), this)
}
constructor(fragment: Fragment) : this(activity = null, fragment = fragment) {
sourceAuthContract = fragment.registerForActivityResult(SourceAuthActivity.Contract(), this)
}
override fun onActivityResult(result: TaggedActivityResult?) {
result ?: return
continuations.remove(result.tag)?.resume(result.isSuccess)
}
suspend fun resolve(e: ResolvableException): Boolean = when (e) {
is CloudFlareProtectedException -> resolveCF(e.url)
is AuthRequiredException -> false //TODO
is AuthRequiredException -> resolveAuthException(e.source)
else -> false
}
private suspend fun resolveCF(url: String) = suspendCancellableCoroutine<Boolean> { cont ->
private suspend fun resolveCF(url: String): Boolean {
val dialog = CloudFlareDialog.newInstance(url)
fm.clearFragmentResult(CloudFlareDialog.TAG)
continuations[CloudFlareDialog.TAG] = cont
fm.setFragmentResultListener(CloudFlareDialog.TAG, lifecycleOwner) { key, result ->
continuations.remove(key)?.resume(result.getBoolean(CloudFlareDialog.EXTRA_RESULT))
}
dialog.show(fm, CloudFlareDialog.TAG)
cont.invokeOnCancellation {
continuations.remove(CloudFlareDialog.TAG, cont)
fm.clearFragmentResultListener(CloudFlareDialog.TAG)
dialog.dismiss()
val fm = getFragmentManager()
return suspendCancellableCoroutine { cont ->
fm.clearFragmentResult(CloudFlareDialog.TAG)
continuations[CloudFlareDialog.TAG] = cont
fm.setFragmentResultListener(CloudFlareDialog.TAG, checkNotNull(fragment ?: activity)) { key, result ->
continuations.remove(key)?.resume(result.getBoolean(CloudFlareDialog.EXTRA_RESULT))
}
dialog.show(fm, CloudFlareDialog.TAG)
cont.invokeOnCancellation {
continuations.remove(CloudFlareDialog.TAG, cont)
fm.clearFragmentResultListener(CloudFlareDialog.TAG)
dialog.dismiss()
}
}
}
private suspend fun resolveAuthException(source: MangaSource): Boolean = suspendCoroutine { cont ->
continuations[SourceAuthActivity.TAG] = cont
sourceAuthContract.launch(source)
}
private fun getFragmentManager() = checkNotNull(fragment?.childFragmentManager ?: activity?.supportFragmentManager)
}

View File

@@ -5,4 +5,6 @@ interface MangaRepositoryAuthProvider {
val authUrl: String
fun isAuthorized(): Boolean
suspend fun getUsername(): String
}

View File

@@ -61,7 +61,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
tags = runCatching {
row.selectFirst("div.genre")?.select("a")?.mapToSet {
MangaTag(
title = it.text().toTitleCase(),
title = it.text().toTagName(),
key = it.attr("href").substringAfterLast('/').urlEncoded(),
source = source
)
@@ -136,7 +136,7 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
return root.select("li.sidetag").mapToSet { li ->
val a = li.children().last() ?: throw ParseException("a is null")
MangaTag(
title = a.text().toTitleCase(),
title = a.text().toTagName(),
key = a.attr("href").substringAfterLast('/'),
source = source
)
@@ -159,4 +159,5 @@ abstract class ChanRepository(loaderContext: MangaLoaderContext) : RemoteMangaRe
else -> "favdesc"
}
private fun String.toTagName() = replace('_', ' ').toTitleCase()
}

View File

@@ -2,10 +2,13 @@ package org.koitharu.kotatsu.core.parser.site
import org.jsoup.nodes.Element
import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.AuthRequiredException
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
import java.util.*
import kotlin.math.pow
private const val DOMAIN_UNAUTHORIZED = "e-hentai.org"
@@ -17,7 +20,9 @@ class ExHentaiRepository(
override val source = MangaSource.EXHENTAI
override val sortOrders: Set<SortOrder> = emptySet()
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.NEWEST,
)
override val defaultDomain: String
get() = if (isAuthorized()) DOMAIN_AUTHORIZED else DOMAIN_UNAUTHORIZED
@@ -206,6 +211,20 @@ class ExHentaiRepository(
return false
}
override suspend fun getUsername(): String {
val doc = loaderContext.httpGet("https://forums.${DOMAIN_UNAUTHORIZED}/").parseHtml().body()
val username = doc.getElementById("userlinks")
?.getElementsByAttributeValueContaining("href", "?showuser=")
?.firstOrNull()
?.ownText()
?: if (doc.getElementById("userlinksguest") != null) {
throw AuthRequiredException(source)
} else {
throw ParseException()
}
return username
}
private fun isAuthorized(domain: String): Boolean {
val cookies = loaderContext.cookieJar.getCookies(domain).mapToSet { x -> x.name }
return authCookies.all { it in cookies }

View File

@@ -7,18 +7,22 @@ import org.koitharu.kotatsu.base.domain.MangaLoaderContext
import org.koitharu.kotatsu.core.exceptions.AuthRequiredException
import org.koitharu.kotatsu.core.exceptions.ParseException
import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
import java.text.SimpleDateFormat
import java.util.*
open class MangaLibRepository(loaderContext: MangaLoaderContext) :
RemoteMangaRepository(loaderContext) {
RemoteMangaRepository(loaderContext), MangaRepositoryAuthProvider {
override val defaultDomain = "mangalib.me"
override val source = MangaSource.MANGALIB
override val authUrl: String
get() = "https://${getDomain()}/login"
override val sortOrders: Set<SortOrder> = EnumSet.of(
SortOrder.RATING,
SortOrder.ALPHABETICAL,
@@ -153,7 +157,7 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
val fullUrl = chapter.url.withDomain()
val doc = loaderContext.httpGet(fullUrl).parseHtml()
if (doc.location().endsWith("/register")) {
throw AuthRequiredException("/login".inContextOf(doc))
throw AuthRequiredException(source)
}
val scripts = doc.head().select("script")
val pg = (doc.body().getElementById("pg")?.html() ?: parseFailed("Element #pg not found"))
@@ -212,6 +216,14 @@ open class MangaLibRepository(loaderContext: MangaLoaderContext) :
throw ParseException("Script with genres not found")
}
override fun isAuthorized(): Boolean {
return false
}
override suspend fun getUsername(): String {
TODO("Not yet implemented")
}
private fun getSortKey(sortOrder: SortOrder?) = when (sortOrder) {
SortOrder.RATING -> "desc&sort=rate"
SortOrder.ALPHABETICAL -> "asc&sort=name"

View File

@@ -170,7 +170,7 @@ class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
}
}
suspend fun getUsername(): String {
override suspend fun getUsername(): String {
val body = loaderContext.httpGet("https://${getDomain()}/").parseHtml()
.body()
return body
@@ -180,7 +180,7 @@ class NudeMoonRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposit
?.substringAfterLast('/')
?: run {
throw if (body.selectFirst("form[name=\"loginform\"]") != null) {
AuthRequiredException(authUrl)
AuthRequiredException(source)
} else {
ParseException("Cannot find username")
}

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.core.parser.site
import okhttp3.Headers
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
@@ -9,6 +10,7 @@ import org.koitharu.kotatsu.core.model.*
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.utils.ext.*
import java.net.URLDecoder
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
@@ -62,7 +64,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
.append((offset / PAGE_SIZE) + 1)
.append("&count=")
.append(PAGE_SIZE)
val content = loaderContext.httpGet(urlBuilder.toString()).parseJson()
val content = loaderContext.httpGet(urlBuilder.toString(), getApiHeaders()).parseJson()
.getJSONArray("content")
return content.map { jo ->
val url = "/manga/${jo.getString("dir")}"
@@ -95,7 +97,8 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
val slug = manga.url.find(regexLastUrlPath)
?: throw ParseException("Cannot obtain slug from ${manga.url}")
val data = loaderContext.httpGet(
url = "https://api.$domain/api/titles/$slug/"
url = "https://api.$domain/api/titles/$slug/",
headers = getApiHeaders(),
).parseJson()
val content = try {
data.getJSONObject("content")
@@ -150,7 +153,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
override suspend fun getPages(chapter: MangaChapter): List<MangaPage> {
val referer = "https://${getDomain()}/"
val content = loaderContext.httpGet(chapter.url.withDomain(subdomain = "api")).parseJson()
val content = loaderContext.httpGet(chapter.url.withDomain(subdomain = "api"), getApiHeaders()).parseJson()
.getJSONObject("content")
val pages = content.optJSONArray("pages")
if (pages == null) {
@@ -177,7 +180,7 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
override suspend fun getTags(): Set<MangaTag> {
val domain = getDomain()
val content = loaderContext.httpGet("https://api.$domain/api/forms/titles/?get=genres")
val content = loaderContext.httpGet("https://api.$domain/api/forms/titles/?get=genres", getApiHeaders())
.parseJson().getJSONObject("content").getJSONArray("genres")
return content.mapToSet { jo ->
MangaTag(
@@ -194,6 +197,23 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
}
}
override suspend fun getUsername(): String {
val jo = loaderContext.httpGet(
url = "https://api.${getDomain()}/api/users/current/",
headers = getApiHeaders(),
).parseJson()
return jo.getJSONObject("content").getString("username")
}
private fun getApiHeaders(): Headers? {
val userCookie = loaderContext.cookieJar.getCookies(getDomain()).find {
it.name == "user"
} ?: return null
val jo = JSONObject(URLDecoder.decode(userCookie.value, Charsets.UTF_8.name()))
val accessToken = jo.getStringOrNull("access_token") ?: return null
return Headers.headersOf("authorization", "bearer $accessToken")
}
private fun copyCookies() {
val domain = getDomain()
loaderContext.cookieJar.copyCookies(domain, "api.$domain")
@@ -220,7 +240,8 @@ class RemangaRepository(loaderContext: MangaLoaderContext) : RemoteMangaReposito
var page = 1
while (true) {
val content = loaderContext.httpGet(
"https://api.$domain/api/titles/chapters/?branch_id=$branchId&page=$page&count=100"
url = "https://api.$domain/api/titles/chapters/?branch_id=$branchId&page=$page&count=100",
headers = getApiHeaders(),
).parseJson().getJSONArray("content")
val len = content.length()
if (len == 0) {

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.reader.ui
import android.Manifest
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@@ -35,6 +36,7 @@ import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseFullscreenActivity
import org.koitharu.kotatsu.core.exceptions.resolve.ResolvableException
import org.koitharu.kotatsu.core.model.Manga
import org.koitharu.kotatsu.core.model.MangaChapter
import org.koitharu.kotatsu.core.model.MangaPage
@@ -216,14 +218,14 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
}
private fun onError(e: Throwable) {
val listener = ErrorDialogListener(e)
val dialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.error_occurred)
.setMessage(e.getDisplayMessage(resources))
.setPositiveButton(R.string.close, null)
if (viewModel.content.value?.pages.isNullOrEmpty()) {
dialog.setOnDismissListener {
finish()
}
.setNegativeButton(R.string.close, listener)
.setOnCancelListener(listener)
if (e is ResolvableException) {
dialog.setPositiveButton(e.resolveTextId, listener)
}
dialog.show()
}
@@ -369,6 +371,36 @@ class ReaderActivity : BaseFullscreenActivity<ActivityReaderBinding>(),
}
}
private inner class ErrorDialogListener(
private val exception: Throwable,
) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
if (which == DialogInterface.BUTTON_POSITIVE && exception is ResolvableException) {
dialog?.dismiss()
tryResolve(exception)
} else {
onCancel(dialog)
}
}
override fun onCancel(dialog: DialogInterface?) {
if (viewModel.content.value?.pages.isNullOrEmpty()) {
finishAfterTransition()
}
}
private fun tryResolve(e: ResolvableException) {
lifecycleScope.launch {
if (exceptionResolver.resolve(e)) {
viewModel.reload()
} else {
onCancel(null)
}
}
}
}
companion object {
const val ACTION_MANGA_READ = "${BuildConfig.APPLICATION_ID}.action.READ_MANGA"

View File

@@ -32,8 +32,8 @@ import org.koitharu.kotatsu.utils.ext.asLiveDataDistinct
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
class ReaderViewModel(
intent: MangaIntent,
state: ReaderState?,
private val intent: MangaIntent,
initialState: ReaderState?,
private val dataRepository: MangaDataRepository,
private val historyRepository: HistoryRepository,
private val shortcutsRepository: ShortcutsRepository,
@@ -42,7 +42,7 @@ class ReaderViewModel(
) : BaseViewModel() {
private var loadingJob: Job? = null
private val currentState = MutableStateFlow<ReaderState?>(null)
private val currentState = MutableStateFlow(initialState)
private val mangaData = MutableStateFlow(intent.manga)
private val chapters = LongSparseArray<MangaChapter>()
@@ -87,45 +87,7 @@ class ReaderViewModel(
val onZoomChanged = SingleLiveEvent<Unit>()
init {
loadingJob = launchLoadingJob(Dispatchers.Default) {
var manga = dataRepository.resolveIntent(intent)
?: throw MangaNotFoundException("Cannot find manga")
mangaData.value = manga
val repo = MangaRepository(manga.source)
manga = repo.getDetails(manga)
manga.chapters?.forEach {
chapters.put(it.id, it)
}
// determine mode
val mode =
dataRepository.getReaderMode(manga.id) ?: manga.chapters?.randomOrNull()?.let {
val pages = repo.getPages(it)
val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages)
val newMode = getReaderMode(isWebtoon)
if (isWebtoon != null) {
dataRepository.savePreferences(manga, newMode)
}
newMode
} ?: error("There are no chapters in this manga")
// obtain state
currentState.value = state ?: historyRepository.getOne(manga)?.let {
ReaderState.from(it)
} ?: ReaderState.initial(manga)
val branch = chapters[currentState.value?.chapterId ?: 0L].branch
mangaData.value = manga.copy(chapters = manga.chapters?.filter { it.branch == branch })
readerMode.postValue(mode)
val pages = loadChapter(requireNotNull(currentState.value).chapterId)
// save state
currentState.value?.let {
historyRepository.addOrUpdate(manga, it.chapterId, it.page, it.scroll)
shortcutsRepository.updateShortcuts()
}
content.postValue(ReaderContent(pages, currentState.value))
}
loadImpl()
subscribeToSettings()
}
@@ -134,6 +96,11 @@ class ReaderViewModel(
super.onCleared()
}
fun reload() {
loadingJob?.cancel()
loadImpl()
}
fun switchMode(newMode: ReaderMode) {
launchJob {
val manga = checkNotNull(mangaData.value)
@@ -219,6 +186,49 @@ class ReaderViewModel(
}
}
private fun loadImpl() {
loadingJob = launchLoadingJob(Dispatchers.Default) {
var manga = dataRepository.resolveIntent(intent)
?: throw MangaNotFoundException("Cannot find manga")
mangaData.value = manga
val repo = MangaRepository(manga.source)
manga = repo.getDetails(manga)
manga.chapters?.forEach {
chapters.put(it.id, it)
}
// determine mode
val mode =
dataRepository.getReaderMode(manga.id) ?: manga.chapters?.randomOrNull()?.let {
val pages = repo.getPages(it)
val isWebtoon = MangaUtils.determineMangaIsWebtoon(pages)
val newMode = getReaderMode(isWebtoon)
if (isWebtoon != null) {
dataRepository.savePreferences(manga, newMode)
}
newMode
} ?: error("There are no chapters in this manga")
// obtain state
if (currentState.value == null) {
currentState.value = historyRepository.getOne(manga)?.let {
ReaderState.from(it)
} ?: ReaderState.initial(manga)
}
val branch = chapters[currentState.value?.chapterId ?: 0L].branch
mangaData.value = manga.copy(chapters = manga.chapters?.filter { it.branch == branch })
readerMode.postValue(mode)
val pages = loadChapter(requireNotNull(currentState.value).chapterId)
// save state
currentState.value?.let {
historyRepository.addOrUpdate(manga, it.chapterId, it.page, it.scroll)
shortcutsRepository.updateShortcuts()
}
content.postValue(ReaderContent(pages, currentState.value))
}
}
private fun getReaderMode(isWebtoon: Boolean?) = when {
isWebtoon == true -> ReaderMode.WEBTOON
settings.isPreferRtlReader -> ReaderMode.REVERSED

View File

@@ -2,12 +2,18 @@ package org.koitharu.kotatsu.settings
import android.os.Bundle
import android.util.ArrayMap
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.AuthRequiredException
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.parser.MangaRepositoryAuthProvider
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
@@ -17,6 +23,7 @@ import org.koitharu.kotatsu.settings.utils.EditTextBindListener
import org.koitharu.kotatsu.settings.utils.EditTextDefaultSummaryProvider
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
import org.koitharu.kotatsu.utils.ext.parcelableArgument
import org.koitharu.kotatsu.utils.ext.viewLifecycleScope
import org.koitharu.kotatsu.utils.ext.withArgs
class SourceSettingsFragment : PreferenceFragmentCompat() {
@@ -51,6 +58,15 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
findPreference<Preference>(SourceSettings.KEY_AUTH)?.run {
if (isVisible) {
loadUsername(this)
}
}
}
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
SourceSettings.KEY_AUTH -> {
@@ -87,6 +103,21 @@ class SourceSettingsFragment : PreferenceFragmentCompat() {
}
}
private fun loadUsername(preference: Preference) = viewLifecycleScope.launch {
runCatching {
withContext(Dispatchers.Default) {
(repository as MangaRepositoryAuthProvider).getUsername()
}
}.onSuccess { username ->
preference.title = getString(R.string.logged_in_as, username)
}.onFailure { error ->
preference.isEnabled = error is AuthRequiredException
if (BuildConfig.DEBUG) {
error.printStackTrace()
}
}
}
companion object {
private const val EXTRA_SOURCE = "source"

View File

@@ -1,12 +1,14 @@
package org.koitharu.kotatsu.settings.sources.auth
import android.annotation.SuppressLint
import android.app.Activity
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.activity.result.contract.ActivityResultContract
import androidx.core.graphics.Insets
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
@@ -17,6 +19,7 @@ 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.TaggedActivityResult
import org.koitharu.kotatsu.utils.ext.mangaRepositoryOf
import com.google.android.material.R as materialR
@@ -61,6 +64,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
android.R.id.home -> {
binding.webView.stopLoading()
setResult(Activity.RESULT_CANCELED)
finishAfterTransition()
true
}
@@ -89,6 +93,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
binding.progressBar.isVisible = isLoading
if (!isLoading && repository.isAuthorized()) {
Toast.makeText(this, R.string.auth_complete, Toast.LENGTH_SHORT).show()
setResult(Activity.RESULT_OK)
finishAfterTransition()
}
}
@@ -103,9 +108,20 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
binding.webView.updatePadding(bottom = insets.bottom)
}
class Contract : ActivityResultContract<MangaSource, TaggedActivityResult>() {
override fun createIntent(context: Context, input: MangaSource): Intent {
return newIntent(context, input)
}
override fun parseResult(resultCode: Int, intent: Intent?): TaggedActivityResult {
return TaggedActivityResult(TAG, resultCode)
}
}
companion object {
private const val EXTRA_SOURCE = "source"
const val TAG = "SourceAuthActivity"
fun newIntent(context: Context, source: MangaSource): Intent {
return Intent(context, SourceAuthActivity::class.java)

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.utils
import android.app.Activity
class TaggedActivityResult(
val tag: String,
val result: Int,
)
val TaggedActivityResult.isSuccess: Boolean
get() = this.result == Activity.RESULT_OK

View File

@@ -265,4 +265,5 @@
<string name="only_using_wifi">Only using WiFi</string>
<string name="always">Always</string>
<string name="preload_pages">Preload pages</string>
<string name="logged_in_as">Logged in as %s</string>
</resources>