Compare commits

...

4 Commits

Author SHA1 Message Date
Koitharu
155af8889b Update version 2023-02-07 07:40:33 +02:00
Koitharu
61b7117b97 Allow to use own UserAgent for each manga source 2023-02-07 07:29:38 +02:00
Zakhar Timoshenko
0f4de329e5 Update parsers 2023-02-07 07:27:40 +02:00
Zakhar Timoshenko
9b290bea40 Change user agent to Chrome 2023-02-07 07:27:29 +02:00
39 changed files with 192 additions and 81 deletions

View File

@@ -15,8 +15,8 @@ android {
applicationId 'org.koitharu.kotatsu' applicationId 'org.koitharu.kotatsu'
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 33
versionCode 513 versionCode 514
versionName '4.3.2' versionName '4.3.3'
generatedDensities = [] generatedDensities = []
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -84,7 +84,7 @@ afterEvaluate {
} }
} }
dependencies { dependencies {
implementation('com.github.KotatsuApp:kotatsu-parsers:7f630184c0') { implementation('com.github.KotatsuApp:kotatsu-parsers:00abaea324') {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }

View File

@@ -26,7 +26,7 @@ fun bookmarkListAD(
binding.root.setOnLongClickListener(listener) binding.root.setOnLongClickListener(listener)
bind { bind {
binding.imageViewThumb.newImageRequest(item.imageUrl)?.run { binding.imageViewThumb.newImageRequest(item.imageUrl, item.manga.source)?.run {
referer(item.manga.publicUrl) referer(item.manga.publicUrl)
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)

View File

@@ -14,7 +14,11 @@ import org.koitharu.kotatsu.bookmarks.ui.model.BookmarksGroup
import org.koitharu.kotatsu.databinding.ItemBookmarksGroupBinding import org.koitharu.kotatsu.databinding.ItemBookmarksGroupBinding
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.clearItemDecorations
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest
import org.koitharu.kotatsu.utils.ext.referer
fun bookmarksGroupAD( fun bookmarksGroupAD(
coil: ImageLoader, coil: ImageLoader,
@@ -45,7 +49,7 @@ fun bookmarksGroupAD(
binding.recyclerView.addItemDecoration(spacingDecoration) binding.recyclerView.addItemDecoration(spacingDecoration)
selectionController.attachToRecyclerView(item.manga, binding.recyclerView) selectionController.attachToRecyclerView(item.manga, binding.recyclerView)
} }
binding.imageViewCover.newImageRequest(item.manga.coverUrl)?.run { binding.imageViewCover.newImageRequest(item.manga.coverUrl, item.manga.source)?.run {
referer(item.manga.publicUrl) referer(item.manga.publicUrl)
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)

View File

@@ -31,7 +31,7 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
} }
with(binding.webView.settings) { with(binding.webView.settings) {
javaScriptEnabled = true javaScriptEnabled = true
userAgentString = UserAgentInterceptor.userAgent userAgentString = UserAgentInterceptor.userAgentChrome
} }
binding.webView.webViewClient = BrowserClient(this) binding.webView.webViewClient = BrowserClient(this)
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar) binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)

View File

@@ -12,7 +12,9 @@ import androidx.core.view.isInvisible
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import okhttp3.Headers
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.UserAgentInterceptor import org.koitharu.kotatsu.core.network.UserAgentInterceptor
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding import org.koitharu.kotatsu.databinding.FragmentCloudflareBinding
@@ -42,7 +44,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
cacheMode = WebSettings.LOAD_DEFAULT cacheMode = WebSettings.LOAD_DEFAULT
domStorageEnabled = true domStorageEnabled = true
databaseEnabled = true databaseEnabled = true
userAgentString = UserAgentInterceptor.userAgent userAgentString = arguments?.getString(ARG_UA) ?: UserAgentInterceptor.userAgentChrome
} }
binding.webView.webViewClient = CloudFlareClient(cookieJar, this, url.orEmpty()) binding.webView.webViewClient = CloudFlareClient(cookieJar, this, url.orEmpty())
CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true) CookieManager.getInstance().setAcceptThirdPartyCookies(binding.webView, true)
@@ -92,9 +94,13 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
const val TAG = "CloudFlareDialog" const val TAG = "CloudFlareDialog"
const val EXTRA_RESULT = "result" const val EXTRA_RESULT = "result"
private const val ARG_URL = "url" private const val ARG_URL = "url"
private const val ARG_UA = "ua"
fun newInstance(url: String) = CloudFlareDialog().withArgs(1) { fun newInstance(url: String, headers: Headers?) = CloudFlareDialog().withArgs(2) {
putString(ARG_URL, url) putString(ARG_URL, url)
headers?.get(CommonHeaders.USER_AGENT)?.let {
putString(ARG_UA, it)
}
} }
} }
} }

View File

@@ -85,6 +85,7 @@ interface AppModule {
@Singleton @Singleton
fun provideOkHttpClient( fun provideOkHttpClient(
localStorageManager: LocalStorageManager, localStorageManager: LocalStorageManager,
userAgentInterceptor: UserAgentInterceptor,
cookieJar: CookieJar, cookieJar: CookieJar,
settings: AppSettings, settings: AppSettings,
): OkHttpClient { ): OkHttpClient {
@@ -97,7 +98,7 @@ interface AppModule {
dns(DoHManager(cache, settings)) dns(DoHManager(cache, settings))
cache(cache) cache(cache)
addInterceptor(GZipInterceptor()) addInterceptor(GZipInterceptor())
addInterceptor(UserAgentInterceptor()) addInterceptor(userAgentInterceptor)
addInterceptor(CloudFlareInterceptor()) addInterceptor(CloudFlareInterceptor())
}.build() }.build()
} }

View File

@@ -1,7 +1,9 @@
package org.koitharu.kotatsu.core.exceptions package org.koitharu.kotatsu.core.exceptions
import okhttp3.Headers
import okio.IOException import okio.IOException
class CloudFlareProtectedException( class CloudFlareProtectedException(
val url: String val url: String,
) : IOException("Protected by CloudFlare") val headers: Headers,
) : IOException("Protected by CloudFlare")

View File

@@ -7,6 +7,7 @@ import androidx.collection.ArrayMap
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import okhttp3.Headers
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.browser.BrowserActivity import org.koitharu.kotatsu.browser.BrowserActivity
import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog import org.koitharu.kotatsu.browser.cloudflare.CloudFlareDialog
@@ -43,7 +44,7 @@ class ExceptionResolver private constructor(
} }
suspend fun resolve(e: Throwable): Boolean = when (e) { suspend fun resolve(e: Throwable): Boolean = when (e) {
is CloudFlareProtectedException -> resolveCF(e.url) is CloudFlareProtectedException -> resolveCF(e.url, e.headers)
is AuthRequiredException -> resolveAuthException(e.source) is AuthRequiredException -> resolveAuthException(e.source)
is NotFoundException -> { is NotFoundException -> {
openInBrowser(e.url) openInBrowser(e.url)
@@ -53,8 +54,8 @@ class ExceptionResolver private constructor(
else -> false else -> false
} }
private suspend fun resolveCF(url: String): Boolean { private suspend fun resolveCF(url: String, headers: Headers): Boolean {
val dialog = CloudFlareDialog.newInstance(url) val dialog = CloudFlareDialog.newInstance(url, headers)
val fm = getFragmentManager() val fm = getFragmentManager()
return suspendCancellableCoroutine { cont -> return suspendCancellableCoroutine { cont ->
fm.clearFragmentResult(CloudFlareDialog.TAG) fm.clearFragmentResult(CloudFlareDialog.TAG)

View File

@@ -13,13 +13,17 @@ private const val SERVER_CLOUDFLARE = "cloudflare"
class CloudFlareInterceptor : Interceptor { class CloudFlareInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) val request = chain.request()
val response = chain.proceed(request)
if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) { if (response.code == HTTP_FORBIDDEN || response.code == HTTP_UNAVAILABLE) {
if (response.header(HEADER_SERVER)?.startsWith(SERVER_CLOUDFLARE) == true) { if (response.header(HEADER_SERVER)?.startsWith(SERVER_CLOUDFLARE) == true) {
response.closeQuietly() response.closeQuietly()
throw CloudFlareProtectedException(response.request.url.toString()) throw CloudFlareProtectedException(
url = response.request.url.toString(),
headers = request.headers,
)
} }
} }
return response return response
} }
} }

View File

@@ -1,24 +1,40 @@
package org.koitharu.kotatsu.core.network package org.koitharu.kotatsu.core.network
import android.os.Build import android.os.Build
import java.util.* import dagger.Lazy
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.RemoteMangaRepository
import org.koitharu.kotatsu.parsers.model.MangaSource
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
class UserAgentInterceptor : Interceptor { @Singleton
class UserAgentInterceptor @Inject constructor(
private val mangaRepositoryFactoryLazy: Lazy<MangaRepository.Factory>,
) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request() val request = chain.request()
return chain.proceed( return chain.proceed(
if (request.header(CommonHeaders.USER_AGENT) == null) { if (request.header(CommonHeaders.USER_AGENT) == null) {
request.newBuilder() request.newBuilder()
.addHeader(CommonHeaders.USER_AGENT, userAgent) .addHeader(CommonHeaders.USER_AGENT, getUserAgent(request))
.build() .build()
} else request } else request,
) )
} }
private fun getUserAgent(request: Request): String {
val source = request.tag(MangaSource::class.java) ?: return userAgent
val repository = mangaRepositoryFactoryLazy.get().create(source) as? RemoteMangaRepository
return repository?.userAgent ?: userAgent
}
companion object { companion object {
val userAgent val userAgent
@@ -28,16 +44,16 @@ class UserAgentInterceptor : Interceptor {
Build.MODEL, Build.MODEL,
Build.BRAND, Build.BRAND,
Build.DEVICE, Build.DEVICE,
Locale.getDefault().language Locale.getDefault().language,
) ) // TODO Decide what to do with this afterwards
val userAgentChrome val userAgentChrome
get() = ( get() = (
"Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) " + "Mozilla/5.0 (Linux; Android %s; %s) AppleWebKit/537.36 (KHTML, like Gecko) " +
"Chrome/100.0.4896.127 Mobile Safari/537.36" "Chrome/100.0.4896.127 Mobile Safari/537.36"
).format( ).format(
Build.VERSION.RELEASE, Build.VERSION.RELEASE,
Build.MODEL, Build.MODEL,
) )
} }
} }

View File

@@ -118,6 +118,7 @@ class ShortcutsUpdater @Inject constructor(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(manga.coverUrl) .data(manga.coverUrl)
.size(iconSize.width, iconSize.height) .size(iconSize.width, iconSize.height)
.tag(manga.source)
.precision(Precision.EXACT) .precision(Precision.EXACT)
.scale(Scale.FILL) .scale(Scale.FILL)
.build(), .build(),

View File

@@ -8,6 +8,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.currentCoroutineContext
import org.koitharu.kotatsu.core.cache.ContentCache import org.koitharu.kotatsu.core.cache.ContentCache
import org.koitharu.kotatsu.core.cache.SafeDeferred import org.koitharu.kotatsu.core.cache.SafeDeferred
import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.prefs.SourceSettings import org.koitharu.kotatsu.core.prefs.SourceSettings
import org.koitharu.kotatsu.parsers.MangaParser import org.koitharu.kotatsu.parsers.MangaParser
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
@@ -39,6 +40,9 @@ class RemoteMangaRepository(
getConfig().defaultSortOrder = value getConfig().defaultSortOrder = value
} }
val userAgent: String?
get() = parser.headers?.get(CommonHeaders.USER_AGENT)
override suspend fun getList(offset: Int, query: String): List<Manga> { override suspend fun getList(offset: Int, query: String): List<Manga> {
return parser.getList(offset, query) return parser.getList(offset, query)
} }

View File

@@ -53,7 +53,7 @@ class FaviconFetcher(
options.size.height.pxOrElse { FALLBACK_SIZE }, options.size.height.pxOrElse { FALLBACK_SIZE },
) )
val icon = checkNotNull(favicons.find(sizePx)) { "No favicons found" } val icon = checkNotNull(favicons.find(sizePx)) { "No favicons found" }
val response = loadIcon(icon.url, favicons.referer) val response = loadIcon(icon.url, repo.userAgent, favicons.referer)
val responseBody = response.requireBody() val responseBody = response.requireBody()
val source = writeToDiskCache(responseBody)?.toImageSource() ?: responseBody.toImageSource() val source = writeToDiskCache(responseBody)?.toImageSource() ?: responseBody.toImageSource()
return SourceResult( return SourceResult(
@@ -63,11 +63,14 @@ class FaviconFetcher(
) )
} }
private suspend fun loadIcon(url: String, referer: String): Response { private suspend fun loadIcon(url: String, userAgent: String?, referer: String): Response {
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.get() .get()
.header(CommonHeaders.REFERER, referer) .header(CommonHeaders.REFERER, referer)
if (userAgent != null) {
request.header(CommonHeaders.USER_AGENT, userAgent)
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
options.tags.asMap().forEach { request.tag(it.key as Class<Any>, it.value) } options.tags.asMap().forEach { request.tag(it.key as Class<Any>, it.value) }
val response = okHttpClient.newCall(request.build()).await() val response = okHttpClient.newCall(request.build()).await()

View File

@@ -254,7 +254,11 @@ class DetailsFragment :
R.id.imageView_cover -> { R.id.imageView_cover -> {
startActivity( startActivity(
ImageActivity.newIntent(v.context, manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl }), ImageActivity.newIntent(
v.context,
manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl },
manga.source,
),
scaleUpActivityOptionsOf(v).toBundle(), scaleUpActivityOptionsOf(v).toBundle(),
) )
} }
@@ -337,6 +341,7 @@ class DetailsFragment :
.target(binding.imageViewCover) .target(binding.imageViewCover)
.size(CoverSizeResolver(binding.imageViewCover)) .size(CoverSizeResolver(binding.imageViewCover))
.data(imageUrl) .data(imageUrl)
.tag(manga.source)
.crossfade(context) .crossfade(context)
.referer(manga.publicUrl) .referer(manga.publicUrl)
.lifecycle(viewLifecycleOwner) .lifecycle(viewLifecycleOwner)

View File

@@ -23,7 +23,7 @@ fun scrobblingInfoAD(
} }
bind { bind {
binding.imageViewCover.newImageRequest(item.coverUrl)?.run { binding.imageViewCover.newImageRequest(item.coverUrl /* TODO */, null)?.run {
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder) error(R.drawable.ic_error_placeholder)

View File

@@ -100,7 +100,7 @@ class ScrobblingInfoBottomSheet :
R.id.imageView_cover -> { R.id.imageView_cover -> {
val coverUrl = viewModel.scrobblingInfo.value?.getOrNull(scrobblerIndex)?.coverUrl ?: return val coverUrl = viewModel.scrobblingInfo.value?.getOrNull(scrobblerIndex)?.coverUrl ?: return
val options = scaleUpActivityOptionsOf(v) val options = scaleUpActivityOptionsOf(v)
startActivity(ImageActivity.newIntent(v.context, coverUrl), options.toBundle()) startActivity(ImageActivity.newIntent(v.context, coverUrl, null), options.toBundle())
} }
} }
} }
@@ -115,15 +115,13 @@ class ScrobblingInfoBottomSheet :
binding.ratingBar.rating = scrobbling.rating * binding.ratingBar.numStars binding.ratingBar.rating = scrobbling.rating * binding.ratingBar.numStars
binding.textViewDescription.text = scrobbling.description binding.textViewDescription.text = scrobbling.description
binding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1) binding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1)
ImageRequest.Builder(context ?: return) binding.imageViewCover.newImageRequest(scrobbling.coverUrl)?.apply {
.target(binding.imageViewCover) lifecycle(viewLifecycleOwner)
.data(scrobbling.coverUrl) placeholder(R.drawable.ic_placeholder)
.crossfade(context) fallback(R.drawable.ic_placeholder)
.lifecycle(viewLifecycleOwner) error(R.drawable.ic_error_placeholder)
.placeholder(R.drawable.ic_placeholder) enqueueWith(coil)
.fallback(R.drawable.ic_placeholder) }
.error(R.drawable.ic_error_placeholder)
.enqueueWith(coil)
} }
override fun onMenuItemClick(item: MenuItem): Boolean { override fun onMenuItemClick(item: MenuItem): Boolean {

View File

@@ -118,7 +118,7 @@ class DownloadManager @AssistedInject constructor(
val data = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga val data = if (manga.chapters.isNullOrEmpty()) repo.getDetails(manga) else manga
output = CbzMangaOutput.get(destination, data) output = CbzMangaOutput.get(destination, data)
val coverUrl = data.largeCoverUrl ?: data.coverUrl val coverUrl = data.largeCoverUrl ?: data.coverUrl
downloadFile(coverUrl, data.publicUrl, destination, tempFileName).let { file -> downloadFile(coverUrl, data.publicUrl, destination, tempFileName, repo.source).let { file ->
output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl)) output.addCover(file, MimeTypeMap.getFileExtensionFromUrl(coverUrl))
} }
val chapters = checkNotNull( val chapters = checkNotNull(
@@ -139,7 +139,8 @@ class DownloadManager @AssistedInject constructor(
for ((pageIndex, page) in pages.withIndex()) { for ((pageIndex, page) in pages.withIndex()) {
runFailsafe(outState, pausingHandle) { runFailsafe(outState, pausingHandle) {
val url = repo.getPageUrl(page) val url = repo.getPageUrl(page)
val file = cache[url] ?: downloadFile(url, page.referer, destination, tempFileName) val file = cache[url]
?: downloadFile(url, page.referer, destination, tempFileName, repo.source)
output.addPage( output.addPage(
chapter = chapter, chapter = chapter,
file = file, file = file,
@@ -209,10 +210,17 @@ class DownloadManager @AssistedInject constructor(
} }
} }
private suspend fun downloadFile(url: String, referer: String, destination: File, tempFileName: String): File { private suspend fun downloadFile(
url: String,
referer: String,
destination: File,
tempFileName: String,
source: MangaSource,
): File {
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.header(CommonHeaders.REFERER, referer) .header(CommonHeaders.REFERER, referer)
.tag(source)
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED) .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.get() .get()
.build() .build()
@@ -243,6 +251,7 @@ class DownloadManager @AssistedInject constructor(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(manga.coverUrl) .data(manga.coverUrl)
.referer(manga.publicUrl) .referer(manga.publicUrl)
.tag(manga.source)
.size(coverWidth, coverHeight) .size(coverWidth, coverHeight)
.scale(Scale.FILL) .scale(Scale.FILL)
.build(), .build(),

View File

@@ -13,7 +13,11 @@ import org.koitharu.kotatsu.databinding.ItemDownloadBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.domain.DownloadState
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.getDisplayMessage
import org.koitharu.kotatsu.utils.ext.newImageRequest
import org.koitharu.kotatsu.utils.ext.onFirst
import org.koitharu.kotatsu.utils.ext.referer
fun downloadItemAD( fun downloadItemAD(
scope: CoroutineScope, scope: CoroutineScope,
@@ -40,7 +44,7 @@ fun downloadItemAD(
bind { bind {
job?.cancel() job?.cancel()
job = item.progressAsFlow().onFirst { state -> job = item.progressAsFlow().onFirst { state ->
binding.imageViewCover.newImageRequest(state.manga.coverUrl)?.run { binding.imageViewCover.newImageRequest(state.manga.coverUrl, state.manga.source)?.run {
referer(state.manga.publicUrl) referer(state.manga.publicUrl)
placeholder(state.cover) placeholder(state.cover)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)
@@ -60,6 +64,7 @@ fun downloadItemAD(
binding.buttonCancel.isVisible = false binding.buttonCancel.isVisible = false
binding.buttonResume.isVisible = false binding.buttonResume.isVisible = false
} }
is DownloadState.Done -> { is DownloadState.Done -> {
binding.textViewStatus.setText(R.string.download_complete) binding.textViewStatus.setText(R.string.download_complete)
binding.progressBar.isIndeterminate = false binding.progressBar.isIndeterminate = false
@@ -69,6 +74,7 @@ fun downloadItemAD(
binding.buttonCancel.isVisible = false binding.buttonCancel.isVisible = false
binding.buttonResume.isVisible = false binding.buttonResume.isVisible = false
} }
is DownloadState.Error -> { is DownloadState.Error -> {
binding.textViewStatus.setText(R.string.error_occurred) binding.textViewStatus.setText(R.string.error_occurred)
binding.progressBar.isIndeterminate = false binding.progressBar.isIndeterminate = false
@@ -79,6 +85,7 @@ fun downloadItemAD(
binding.buttonCancel.isVisible = state.canRetry binding.buttonCancel.isVisible = state.canRetry
binding.buttonResume.isVisible = state.canRetry binding.buttonResume.isVisible = state.canRetry
} }
is DownloadState.PostProcessing -> { is DownloadState.PostProcessing -> {
binding.textViewStatus.setText(R.string.processing_) binding.textViewStatus.setText(R.string.processing_)
binding.progressBar.isIndeterminate = true binding.progressBar.isIndeterminate = true
@@ -88,6 +95,7 @@ fun downloadItemAD(
binding.buttonCancel.isVisible = false binding.buttonCancel.isVisible = false
binding.buttonResume.isVisible = false binding.buttonResume.isVisible = false
} }
is DownloadState.Preparing -> { is DownloadState.Preparing -> {
binding.textViewStatus.setText(R.string.preparing_) binding.textViewStatus.setText(R.string.preparing_)
binding.progressBar.isIndeterminate = true binding.progressBar.isIndeterminate = true
@@ -97,6 +105,7 @@ fun downloadItemAD(
binding.buttonCancel.isVisible = true binding.buttonCancel.isVisible = true
binding.buttonResume.isVisible = false binding.buttonResume.isVisible = false
} }
is DownloadState.Progress -> { is DownloadState.Progress -> {
binding.textViewStatus.setText(R.string.manga_downloading_) binding.textViewStatus.setText(R.string.manga_downloading_)
binding.progressBar.isIndeterminate = false binding.progressBar.isIndeterminate = false
@@ -109,6 +118,7 @@ fun downloadItemAD(
binding.buttonCancel.isVisible = true binding.buttonCancel.isVisible = true
binding.buttonResume.isVisible = false binding.buttonResume.isVisible = false
} }
is DownloadState.Queued -> { is DownloadState.Queued -> {
binding.textViewStatus.setText(R.string.queued) binding.textViewStatus.setText(R.string.queued)
binding.progressBar.isIndeterminate = false binding.progressBar.isIndeterminate = false

View File

@@ -5,7 +5,9 @@ import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.View.* import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import android.view.View.OnTouchListener
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
@@ -16,7 +18,11 @@ import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.databinding.ItemCategoryBinding import org.koitharu.kotatsu.databinding.ItemCategoryBinding
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.animatorDurationScale
import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.getThemeColor
import org.koitharu.kotatsu.utils.ext.newImageRequest
fun categoryAD( fun categoryAD(
coil: ImageLoader, coil: ImageLoader,

View File

@@ -17,11 +17,12 @@ import coil.target.ViewTarget
import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.databinding.ActivityImageBinding import org.koitharu.kotatsu.databinding.ActivityImageBinding
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.indicator import org.koitharu.kotatsu.utils.ext.indicator
import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class ImageActivity : BaseActivity<ActivityImageBinding>() { class ImageActivity : BaseActivity<ActivityImageBinding>() {
@@ -56,6 +57,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
.data(url) .data(url)
.memoryCachePolicy(CachePolicy.DISABLED) .memoryCachePolicy(CachePolicy.DISABLED)
.lifecycle(this) .lifecycle(this)
.tag(intent.getSerializableExtra(EXTRA_SOURCE) as? MangaSource)
.target(SsivTarget(binding.ssiv)) .target(SsivTarget(binding.ssiv))
.indicator(binding.progressBar) .indicator(binding.progressBar)
.enqueueWith(coil) .enqueueWith(coil)
@@ -88,9 +90,12 @@ class ImageActivity : BaseActivity<ActivityImageBinding>() {
companion object { companion object {
fun newIntent(context: Context, url: String): Intent { private const val EXTRA_SOURCE = "source"
fun newIntent(context: Context, url: String, source: MangaSource?): Intent {
return Intent(context, ImageActivity::class.java) return Intent(context, ImageActivity::class.java)
.setData(Uri.parse(url)) .setData(Uri.parse(url))
.putExtra(EXTRA_SOURCE, source)
} }
} }
} }

View File

@@ -177,7 +177,7 @@ abstract class MangaListFragment :
private fun onError(e: Throwable) { private fun onError(e: Throwable) {
if (e is CloudFlareProtectedException) { if (e is CloudFlareProtectedException) {
CloudFlareDialog.newInstance(e.url).show(childFragmentManager, CloudFlareDialog.TAG) CloudFlareDialog.newInstance(e.url, e.headers).show(childFragmentManager, CloudFlareDialog.TAG)
} else { } else {
Snackbar.make( Snackbar.make(
binding.recyclerView, binding.recyclerView,

View File

@@ -39,7 +39,7 @@ fun mangaGridItemAD(
bind { payloads -> bind { payloads ->
binding.textViewTitle.text = item.title binding.textViewTitle.text = item.title
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads) binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
binding.imageViewCover.newImageRequest(item.coverUrl)?.run { binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
referer(item.manga.publicUrl) referer(item.manga.publicUrl)
size(CoverSizeResolver(binding.imageViewCover)) size(CoverSizeResolver(binding.imageViewCover))
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)

View File

@@ -52,7 +52,7 @@ fun mangaListDetailedItemAD(
binding.textViewTitle.text = item.title binding.textViewTitle.text = item.title
binding.textViewSubtitle.textAndVisible = item.subtitle binding.textViewSubtitle.textAndVisible = item.subtitle
binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads) binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads)
binding.imageViewCover.newImageRequest(item.coverUrl)?.run { binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
referer(item.manga.publicUrl) referer(item.manga.publicUrl)
size(CoverSizeResolver(binding.imageViewCover)) size(CoverSizeResolver(binding.imageViewCover))
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)

View File

@@ -10,7 +10,11 @@ import org.koitharu.kotatsu.databinding.ItemMangaListBinding
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.disposeImageRequest
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.newImageRequest
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.textAndVisible
fun mangaListItemAD( fun mangaListItemAD(
coil: ImageLoader, coil: ImageLoader,
@@ -31,7 +35,7 @@ fun mangaListItemAD(
bind { bind {
binding.textViewTitle.text = item.title binding.textViewTitle.text = item.title
binding.textViewSubtitle.textAndVisible = item.subtitle binding.textViewSubtitle.textAndVisible = item.subtitle
binding.imageViewCover.newImageRequest(item.coverUrl)?.run { binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
referer(item.manga.publicUrl) referer(item.manga.publicUrl)
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)

View File

@@ -1,6 +1,7 @@
package org.koitharu.kotatsu.list.ui.model package org.koitharu.kotatsu.list.ui.model
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
sealed interface MangaItemModel : ListModel { sealed interface MangaItemModel : ListModel {
@@ -10,4 +11,7 @@ sealed interface MangaItemModel : ListModel {
val coverUrl: String val coverUrl: String
val counter: Int val counter: Int
val progress: Float val progress: Float
}
val source: MangaSource
get() = manga.source
}

View File

@@ -99,7 +99,11 @@ class ImportService : CoroutineIntentService() {
if (manga != null) { if (manga != null) {
notification.setLargeIcon( notification.setLargeIcon(
coil.execute( coil.execute(
ImageRequest.Builder(applicationContext).data(manga.coverUrl).referer(manga.publicUrl).build(), ImageRequest.Builder(applicationContext)
.data(manga.coverUrl)
.tag(manga.source)
.referer(manga.publicUrl)
.build(),
).toBitmapOrNull(), ).toBitmapOrNull(),
) )
notification.setSubText(manga.title) notification.setSubText(manga.title)

View File

@@ -191,6 +191,7 @@ class PageLoader @Inject constructor(
.header(CommonHeaders.REFERER, page.referer) .header(CommonHeaders.REFERER, page.referer)
.header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8") .header(CommonHeaders.ACCEPT, "image/webp,image/png;q=0.9,image/jpeg,*/*;q=0.8")
.cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED) .cacheControl(CommonHeaders.CACHE_CONTROL_DISABLED)
.tag(page.source)
.build() .build()
okHttp.newCall(request).await().use { response -> okHttp.newCall(request).await().use { response ->
check(response.isSuccessful) { check(response.isSuccessful) {

View File

@@ -13,11 +13,9 @@ import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Scale import coil.size.Scale
import coil.size.ViewSizeResolver import coil.size.ViewSizeResolver
import com.google.android.material.R as materialR
import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.LabelFormatter
import com.google.android.material.slider.Slider import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
@@ -27,7 +25,14 @@ import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import org.koitharu.kotatsu.utils.ext.* import org.koitharu.kotatsu.utils.ext.assistedViewModels
import org.koitharu.kotatsu.utils.ext.decodeRegion
import org.koitharu.kotatsu.utils.ext.enqueueWith
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.setValueRounded
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint @AndroidEntryPoint
class ColorFilterConfigActivity : class ColorFilterConfigActivity :
@@ -115,6 +120,7 @@ class ColorFilterConfigActivity :
.referer(preview.referer) .referer(preview.referer)
.scale(Scale.FILL) .scale(Scale.FILL)
.decodeRegion() .decodeRegion()
.tag(preview.source)
.error(R.drawable.ic_error_placeholder) .error(R.drawable.ic_error_placeholder)
.size(ViewSizeResolver(binding.imageViewBefore)) .size(ViewSizeResolver(binding.imageViewBefore))
.allowRgb565(false) .allowRgb565(false)

View File

@@ -5,9 +5,12 @@ import coil.ImageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Scale import coil.size.Scale
import coil.size.Size import coil.size.Size
import com.google.android.material.R as materialR
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
import kotlinx.coroutines.* import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
@@ -19,6 +22,7 @@ import org.koitharu.kotatsu.utils.ext.isLowRamDevice
import org.koitharu.kotatsu.utils.ext.referer import org.koitharu.kotatsu.utils.ext.referer
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
import org.koitharu.kotatsu.utils.ext.setTextColorAttr import org.koitharu.kotatsu.utils.ext.setTextColorAttr
import com.google.android.material.R as materialR
fun pageThumbnailAD( fun pageThumbnailAD(
coil: ImageLoader, coil: ImageLoader,
@@ -41,6 +45,7 @@ fun pageThumbnailAD(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(url) .data(url)
.referer(item.page.referer) .referer(item.page.referer)
.tag(item.page.source)
.size(thumbSize) .size(thumbSize)
.scale(Scale.FILL) .scale(Scale.FILL)
.allowRgb565(true) .allowRgb565(true)

View File

@@ -17,7 +17,7 @@ fun searchSuggestionSourceAD(
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
listener: SearchSuggestionListener, listener: SearchSuggestionListener,
) = adapterDelegateViewBinding<SearchSuggestionItem.Source, SearchSuggestionItem, ItemSearchSuggestionSourceBinding>( ) = adapterDelegateViewBinding<SearchSuggestionItem.Source, SearchSuggestionItem, ItemSearchSuggestionSourceBinding>(
{ inflater, parent -> ItemSearchSuggestionSourceBinding.inflate(inflater, parent, false) } { inflater, parent -> ItemSearchSuggestionSourceBinding.inflate(inflater, parent, false) },
) { ) {
binding.switchLocal.setOnCheckedChangeListener { _, isChecked -> binding.switchLocal.setOnCheckedChangeListener { _, isChecked ->
@@ -31,7 +31,7 @@ fun searchSuggestionSourceAD(
binding.textViewTitle.text = item.source.title binding.textViewTitle.text = item.source.title
binding.switchLocal.isChecked = item.isEnabled binding.switchLocal.isChecked = item.isEnabled
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name) val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
binding.imageViewCover.newImageRequest(item.source.faviconUri())?.run { binding.imageViewCover.newImageRequest(item.source.faviconUri(), item.source)?.run {
fallback(fallbackIcon) fallback(fallbackIcon)
placeholder(fallbackIcon) placeholder(fallbackIcon)
error(fallbackIcon) error(fallbackIcon)
@@ -43,4 +43,4 @@ fun searchSuggestionSourceAD(
onViewRecycled { onViewRecycled {
binding.imageViewCover.disposeImageRequest() binding.imageViewCover.disposeImageRequest()
} }
} }

View File

@@ -55,7 +55,7 @@ private fun searchSuggestionMangaGridAD(
} }
bind { bind {
binding.imageViewCover.newImageRequest(item.coverUrl)?.run { binding.imageViewCover.newImageRequest(item.coverUrl, item.source)?.run {
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder) error(R.drawable.ic_error_placeholder)

View File

@@ -105,6 +105,7 @@ class SourceSettingsFragment : BasePreferenceFragment(0) {
} }
private fun resolveError(error: Throwable) { private fun resolveError(error: Throwable) {
view ?: return
viewLifecycleScope.launch { viewLifecycleScope.launch {
if (exceptionResolver.resolve(error)) { if (exceptionResolver.resolve(error)) {
val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch val pref = findPreference<Preference>(KEY_AUTH) ?: return@launch

View File

@@ -20,7 +20,7 @@ import org.koitharu.kotatsu.utils.image.FaviconFallbackDrawable
fun sourceConfigHeaderDelegate() = fun sourceConfigHeaderDelegate() =
adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>( adapterDelegateViewBinding<SourceConfigItem.Header, SourceConfigItem, ItemFilterHeaderBinding>(
{ layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) } { layoutInflater, parent -> ItemFilterHeaderBinding.inflate(layoutInflater, parent, false) },
) { ) {
bind { bind {
@@ -31,7 +31,7 @@ fun sourceConfigHeaderDelegate() =
fun sourceConfigGroupDelegate( fun sourceConfigGroupDelegate(
listener: SourceConfigListener, listener: SourceConfigListener,
) = adapterDelegateViewBinding<SourceConfigItem.LocaleGroup, SourceConfigItem, ItemExpandableBinding>( ) = adapterDelegateViewBinding<SourceConfigItem.LocaleGroup, SourceConfigItem, ItemExpandableBinding>(
{ layoutInflater, parent -> ItemExpandableBinding.inflate(layoutInflater, parent, false) } { layoutInflater, parent -> ItemExpandableBinding.inflate(layoutInflater, parent, false) },
) { ) {
binding.root.setOnClickListener { binding.root.setOnClickListener {
@@ -50,7 +50,7 @@ fun sourceConfigItemDelegate(
lifecycleOwner: LifecycleOwner, lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>( ) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>(
{ layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemSourceConfigBinding.inflate(layoutInflater, parent, false) },
on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable } on = { item, _, _ -> item is SourceConfigItem.SourceItem && !item.isDraggable },
) { ) {
binding.switchToggle.setOnCheckedChangeListener { _, isChecked -> binding.switchToggle.setOnCheckedChangeListener { _, isChecked ->
@@ -62,7 +62,7 @@ fun sourceConfigItemDelegate(
binding.switchToggle.isChecked = item.isEnabled binding.switchToggle.isChecked = item.isEnabled
binding.textViewDescription.textAndVisible = item.summary binding.textViewDescription.textAndVisible = item.summary
val fallbackIcon = FaviconFallbackDrawable(context, item.source.name) val fallbackIcon = FaviconFallbackDrawable(context, item.source.name)
binding.imageViewIcon.newImageRequest(item.source.faviconUri())?.run { binding.imageViewIcon.newImageRequest(item.source.faviconUri(), item.source)?.run {
crossfade(context) crossfade(context)
error(fallbackIcon) error(fallbackIcon)
placeholder(fallbackIcon) placeholder(fallbackIcon)
@@ -82,7 +82,7 @@ fun sourceConfigDraggableItemDelegate(
listener: SourceConfigListener, listener: SourceConfigListener,
) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigDraggableBinding>( ) = adapterDelegateViewBinding<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigDraggableBinding>(
{ layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) }, { layoutInflater, parent -> ItemSourceConfigDraggableBinding.inflate(layoutInflater, parent, false) },
on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable } on = { item, _, _ -> item is SourceConfigItem.SourceItem && item.isDraggable },
) { ) {
val eventListener = object : val eventListener = object :
@@ -117,5 +117,5 @@ fun sourceConfigDraggableItemDelegate(
} }
fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>( fun sourceConfigEmptySearchDelegate() = adapterDelegate<SourceConfigItem.EmptySearchResult, SourceConfigItem>(
R.layout.item_sources_empty R.layout.item_sources_empty,
) { } ) { }

View File

@@ -11,9 +11,7 @@ import androidx.activity.result.contract.ActivityResultContract
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import com.google.android.material.R as materialR
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseActivity import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserCallback import org.koitharu.kotatsu.browser.BrowserCallback
@@ -26,6 +24,8 @@ import org.koitharu.kotatsu.databinding.ActivityBrowserBinding
import org.koitharu.kotatsu.parsers.MangaParserAuthProvider import org.koitharu.kotatsu.parsers.MangaParserAuthProvider
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.TaggedActivityResult import org.koitharu.kotatsu.utils.TaggedActivityResult
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint @AndroidEntryPoint
class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback { class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
@@ -44,7 +44,8 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
finishAfterTransition() finishAfterTransition()
return return
} }
authProvider = (mangaRepositoryFactory.create(source) as? RemoteMangaRepository)?.getAuthProvider() ?: run { val repository = mangaRepositoryFactory.create(source) as? RemoteMangaRepository
authProvider = (repository)?.getAuthProvider() ?: run {
Toast.makeText( Toast.makeText(
this, this,
getString(R.string.auth_not_supported_by, source.title), getString(R.string.auth_not_supported_by, source.title),
@@ -59,7 +60,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
} }
with(binding.webView.settings) { with(binding.webView.settings) {
javaScriptEnabled = true javaScriptEnabled = true
userAgentString = UserAgentInterceptor.userAgentChrome userAgentString = repository.userAgent ?: UserAgentInterceptor.userAgent
} }
binding.webView.webViewClient = BrowserClient(this) binding.webView.webViewClient = BrowserClient(this)
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar) binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
@@ -96,6 +97,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
finishAfterTransition() finishAfterTransition()
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }

View File

@@ -28,7 +28,7 @@ fun feedItemAD(
bind { bind {
binding.textViewTitle.isBold = item.isNew binding.textViewTitle.isBold = item.isNew
binding.textViewSummary.isBold = item.isNew binding.textViewSummary.isBold = item.isNew
binding.imageViewCover.newImageRequest(item.imageUrl)?.run { binding.imageViewCover.newImageRequest(item.imageUrl, item.manga.source)?.run {
placeholder(R.drawable.ic_placeholder) placeholder(R.drawable.ic_placeholder)
fallback(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder)
error(R.drawable.ic_error_placeholder) error(R.drawable.ic_error_placeholder)

View File

@@ -155,7 +155,11 @@ class TrackWorker @AssistedInject constructor(
setNumber(newChapters.size) setNumber(newChapters.size)
setLargeIcon( setLargeIcon(
coil.execute( coil.execute(
ImageRequest.Builder(applicationContext).data(manga.coverUrl).referer(manga.publicUrl).build(), ImageRequest.Builder(applicationContext)
.data(manga.coverUrl)
.referer(manga.publicUrl)
.tag(manga.source)
.build(),
).toBitmapOrNull(), ).toBitmapOrNull(),
) )
setSmallIcon(R.drawable.ic_stat_book_plus) setSmallIcon(R.drawable.ic_stat_book_plus)

View File

@@ -13,16 +13,18 @@ import com.google.android.material.progressindicator.BaseProgressIndicator
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.utils.image.RegionBitmapDecoder import org.koitharu.kotatsu.utils.image.RegionBitmapDecoder
import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener import org.koitharu.kotatsu.utils.progress.ImageRequestIndicatorListener
fun ImageView.newImageRequest(url: Any?): ImageRequest.Builder? { fun ImageView.newImageRequest(url: Any?, mangaSource: MangaSource? = null): ImageRequest.Builder? {
val current = CoilUtils.result(this) val current = CoilUtils.result(this)
if (current != null && current.request.data == url) { if (current != null && current.request.data == url) {
return null return null
} }
return ImageRequest.Builder(context) return ImageRequest.Builder(context)
.data(url) .data(url)
.tag(mangaSource)
.crossfade(context) .crossfade(context)
.target(this) .target(this)
} }
@@ -45,6 +47,7 @@ fun ImageResult.toBitmapOrNull() = when (this) {
} catch (_: Throwable) { } catch (_: Throwable) {
null null
} }
is ErrorResult -> null is ErrorResult -> null
} }

View File

@@ -53,6 +53,7 @@ class RecentListFactory(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(item.coverUrl) .data(item.coverUrl)
.size(coverSize) .size(coverSize)
.tag(item.source)
.transformations(transformation) .transformations(transformation)
.build(), .build(),
).requireBitmap() ).requireBitmap()

View File

@@ -64,6 +64,7 @@ class ShelfListFactory(
ImageRequest.Builder(context) ImageRequest.Builder(context)
.data(item.coverUrl) .data(item.coverUrl)
.size(coverSize) .size(coverSize)
.tag(item.source)
.transformations(transformation) .transformations(transformation)
.build(), .build(),
).requireBitmap() ).requireBitmap()