Migrate to coil3
This commit is contained in:
@@ -59,7 +59,7 @@ android {
|
||||
'-opt-in=kotlinx.coroutines.ExperimentalForInheritanceCoroutinesApi',
|
||||
'-opt-in=kotlinx.coroutines.FlowPreview',
|
||||
'-opt-in=kotlin.contracts.ExperimentalContracts',
|
||||
'-opt-in=coil.annotation.ExperimentalCoilApi',
|
||||
'-opt-in=coil3.annotation.ExperimentalCoilApi',
|
||||
]
|
||||
}
|
||||
lint {
|
||||
@@ -134,8 +134,9 @@ dependencies {
|
||||
implementation 'androidx.hilt:hilt-work:1.2.0'
|
||||
kapt 'androidx.hilt:hilt-compiler:1.2.0'
|
||||
|
||||
implementation 'io.coil-kt:coil-base:2.7.0'
|
||||
implementation 'io.coil-kt:coil-svg:2.7.0'
|
||||
implementation 'io.coil-kt.coil3:coil-core:3.0.0-rc01'
|
||||
implementation 'io.coil-kt.coil3:coil-network-okhttp:3.0.0-rc01'
|
||||
implementation 'io.coil-kt.coil3:coil-gif:3.0.0-rc01'
|
||||
implementation 'org.aomedia.avif.android:avif:1.1.1.14d8e3c4'
|
||||
implementation 'com.github.KotatsuApp:subsampling-scale-image-view:d1d10a6975'
|
||||
implementation 'com.github.solkin:disk-lru-cache:1.4'
|
||||
|
||||
@@ -5,9 +5,16 @@ import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.lifecycle
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.getTitle
|
||||
@@ -19,8 +26,9 @@ import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaAlternativeBinding
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -74,7 +82,7 @@ fun alternativeAD(
|
||||
.placeholder(R.drawable.ic_web)
|
||||
.fallback(R.drawable.ic_web)
|
||||
.error(R.drawable.ic_web)
|
||||
.source(item.manga.source)
|
||||
.mangaSourceExtra(item.manga.source)
|
||||
.transformations(RoundedCornersTransformation(context.resources.getDimension(R.dimen.chip_icon_corner)))
|
||||
.allowRgb565(true)
|
||||
.enqueueWith(coil)
|
||||
@@ -84,8 +92,7 @@ fun alternativeAD(
|
||||
defaultPlaceholders(context)
|
||||
transformations(TrimTransformation())
|
||||
allowRgb565(true)
|
||||
tag(item.manga)
|
||||
source(item.manga.source)
|
||||
mangaExtra(item.manga)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||
|
||||
@@ -12,8 +12,8 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -23,6 +23,7 @@ import org.koitharu.kotatsu.core.model.getTitle
|
||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
@@ -121,7 +122,7 @@ class AutoFixService : CoroutineIntentService() {
|
||||
coil.execute(
|
||||
ImageRequest.Builder(applicationContext)
|
||||
.data(replacement.coverUrl)
|
||||
.tag(replacement.source)
|
||||
.mangaSourceExtra(replacement.source)
|
||||
.build(),
|
||||
).toBitmapOrNull(),
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package org.koitharu.kotatsu.bookmarks.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.bookmarkExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemBookmarkLargeBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
@@ -29,9 +30,8 @@ fun bookmarkLargeAD(
|
||||
size(CoverSizeResolver(binding.imageViewThumb))
|
||||
defaultPlaceholders(context)
|
||||
allowRgb565(true)
|
||||
tag(item)
|
||||
bookmarkExtra(item)
|
||||
decodeRegion(item.scroll)
|
||||
source(item.manga.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
binding.progressView.setProgress(item.percent, false)
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package org.koitharu.kotatsu.bookmarks.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.bookmarkExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemBookmarkBinding
|
||||
|
||||
// TODO check usages
|
||||
fun bookmarkListAD(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
@@ -28,9 +30,8 @@ fun bookmarkListAD(
|
||||
size(CoverSizeResolver(binding.imageViewThumb))
|
||||
defaultPlaceholders(context)
|
||||
allowRgb565(true)
|
||||
tag(item)
|
||||
bookmarkExtra(item)
|
||||
decodeRegion(item.scroll)
|
||||
source(item.manga.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.bookmarks.ui.adapter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
|
||||
@@ -9,9 +9,10 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.net.toUri
|
||||
import coil.EventListener
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil3.EventListener
|
||||
import coil3.Extras
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageRequest
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.getTitle
|
||||
@@ -21,7 +22,7 @@ import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
|
||||
class CaptchaNotifier(
|
||||
private val context: Context,
|
||||
) : EventListener {
|
||||
) : EventListener() {
|
||||
|
||||
fun notify(exception: CloudFlareProtectedException) {
|
||||
if (!context.checkNotificationPermission(CHANNEL_ID)) {
|
||||
@@ -84,20 +85,19 @@ class CaptchaNotifier(
|
||||
override fun onError(request: ImageRequest, result: ErrorResult) {
|
||||
super.onError(request, result)
|
||||
val e = result.throwable
|
||||
if (e is CloudFlareProtectedException && request.parameters.value<Boolean>(PARAM_IGNORE_CAPTCHA) != true) {
|
||||
if (e is CloudFlareProtectedException && request.extras[ignoreCaptchaKey] != true) {
|
||||
notify(e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun ImageRequest.Builder.ignoreCaptchaErrors() = setParameter(
|
||||
key = PARAM_IGNORE_CAPTCHA,
|
||||
value = true,
|
||||
memoryCacheKey = null,
|
||||
)
|
||||
fun ImageRequest.Builder.ignoreCaptchaErrors() = apply {
|
||||
extras[ignoreCaptchaKey] = true
|
||||
}
|
||||
|
||||
val ignoreCaptchaKey = Extras.Key(false)
|
||||
|
||||
private const val PARAM_IGNORE_CAPTCHA = "ignore_captcha"
|
||||
private const val CHANNEL_ID = "captcha"
|
||||
private const val TAG = CHANNEL_ID
|
||||
private const val GROUP_CAPTCHA = "org.koitharu.kotatsu.CAPTCHA"
|
||||
|
||||
@@ -2,16 +2,20 @@ package org.koitharu.kotatsu.core
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.provider.SearchRecentSuggestions
|
||||
import android.text.Html
|
||||
import androidx.collection.arraySetOf
|
||||
import androidx.room.InvalidationTracker
|
||||
import androidx.work.WorkManager
|
||||
import coil.ComponentRegistry
|
||||
import coil.ImageLoader
|
||||
import coil.decode.SvgDecoder
|
||||
import coil.disk.DiskCache
|
||||
import coil.util.DebugLogger
|
||||
import coil3.ImageLoader
|
||||
import coil3.disk.DiskCache
|
||||
import coil3.disk.directory
|
||||
import coil3.gif.AnimatedImageDecoder
|
||||
import coil3.gif.GifDecoder
|
||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.util.DebugLogger
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@@ -28,6 +32,8 @@ import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.core.image.AvifImageDecoder
|
||||
import org.koitharu.kotatsu.core.image.CbzFetcher
|
||||
import org.koitharu.kotatsu.core.image.MangaSourceHeaderInterceptor
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||
@@ -44,7 +50,6 @@ import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
|
||||
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher
|
||||
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.CbzFetcher
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
|
||||
@@ -92,6 +97,7 @@ interface AppModule {
|
||||
imageProxyInterceptor: ImageProxyInterceptor,
|
||||
pageFetcherFactory: MangaPageFetcher.Factory,
|
||||
coverRestoreInterceptor: CoverRestoreInterceptor,
|
||||
networkStateProvider: Provider<NetworkState>,
|
||||
): ImageLoader {
|
||||
val diskCacheFactory = {
|
||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||
@@ -103,37 +109,38 @@ interface AppModule {
|
||||
okHttpClientProvider.get().newBuilder().cache(null).build()
|
||||
}
|
||||
return ImageLoader.Builder(context)
|
||||
.okHttpClient { okHttpClientLazy.value }
|
||||
.interceptorDispatcher(Dispatchers.Default)
|
||||
.fetcherDispatcher(Dispatchers.Default)
|
||||
.decoderDispatcher(Dispatchers.IO)
|
||||
.transformationDispatcher(Dispatchers.Default)
|
||||
.interceptorCoroutineContext(Dispatchers.Default)
|
||||
.diskCache(diskCacheFactory)
|
||||
.respectCacheHeaders(false)
|
||||
.networkObserverEnabled(false)
|
||||
.logger(if (BuildConfig.DEBUG) DebugLogger() else null)
|
||||
.allowRgb565(context.isLowRamDevice())
|
||||
.eventListener(CaptchaNotifier(context))
|
||||
.components(
|
||||
ComponentRegistry.Builder()
|
||||
.add(SvgDecoder.Factory())
|
||||
.add(CbzFetcher.Factory())
|
||||
.add(AvifImageDecoder.Factory())
|
||||
.add(FaviconFetcher.Factory(context, okHttpClientLazy, mangaRepositoryFactory))
|
||||
.add(MangaPageKeyer())
|
||||
.add(pageFetcherFactory)
|
||||
.add(imageProxyInterceptor)
|
||||
.add(coverRestoreInterceptor)
|
||||
.build(),
|
||||
).build()
|
||||
.components {
|
||||
add(
|
||||
OkHttpNetworkFetcherFactory(
|
||||
callFactory = okHttpClientLazy::value,
|
||||
connectivityChecker = { networkStateProvider.get() },
|
||||
),
|
||||
)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
add(AnimatedImageDecoder.Factory())
|
||||
} else {
|
||||
add(GifDecoder.Factory())
|
||||
}
|
||||
add(CbzFetcher.Factory())
|
||||
add(AvifImageDecoder.Factory())
|
||||
add(FaviconFetcher.Factory(mangaRepositoryFactory))
|
||||
add(MangaPageKeyer())
|
||||
add(pageFetcherFactory)
|
||||
add(imageProxyInterceptor)
|
||||
add(coverRestoreInterceptor)
|
||||
add(MangaSourceHeaderInterceptor())
|
||||
}.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideSearchSuggestions(
|
||||
@ApplicationContext context: Context,
|
||||
): SearchRecentSuggestions {
|
||||
return MangaSuggestionsProvider.createSuggestions(context)
|
||||
}
|
||||
): SearchRecentSuggestions = MangaSuggestionsProvider.createSuggestions(context)
|
||||
|
||||
@Provides
|
||||
@ElementsIntoSet
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DecodeResult
|
||||
import coil.decode.Decoder
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.SourceResult
|
||||
import coil.request.Options
|
||||
import coil3.ImageLoader
|
||||
import coil3.asImage
|
||||
import coil3.decode.DecodeResult
|
||||
import coil3.decode.Decoder
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.request.Options
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import org.aomedia.avif.android.AvifDecoder
|
||||
import org.aomedia.avif.android.AvifDecoder.Info
|
||||
import org.koitharu.kotatsu.core.util.ext.toByteBuffer
|
||||
|
||||
class AvifImageDecoder(source: ImageSource, options: Options, parallelismLock: Semaphore) :
|
||||
BaseCoilDecoder(source, options, parallelismLock) {
|
||||
class AvifImageDecoder(
|
||||
private val source: ImageSource,
|
||||
private val options: Options,
|
||||
) : Decoder {
|
||||
|
||||
override fun BitmapFactory.Options.decode(): DecodeResult {
|
||||
override suspend fun decode(): DecodeResult = runInterruptible {
|
||||
val bytes = source.source().use {
|
||||
it.inputStream().toByteBuffer()
|
||||
}
|
||||
@@ -36,22 +37,20 @@ class AvifImageDecoder(source: ImageSource, options: Options, parallelismLock: S
|
||||
bitmap.recycle()
|
||||
throw ImageDecodeException(null, "avif")
|
||||
}
|
||||
return DecodeResult(
|
||||
drawable = bitmap.toDrawable(options.context.resources),
|
||||
DecodeResult(
|
||||
image = bitmap.asImage(),
|
||||
isSampled = false,
|
||||
)
|
||||
}
|
||||
|
||||
class Factory : Decoder.Factory {
|
||||
|
||||
private val parallelismLock = Semaphore(DEFAULT_PARALLELISM)
|
||||
|
||||
override fun create(
|
||||
result: SourceResult,
|
||||
result: SourceFetchResult,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Decoder? = if (isApplicable(result)) {
|
||||
AvifImageDecoder(result.source, options, parallelismLock)
|
||||
AvifImageDecoder(result.source, options)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -60,7 +59,7 @@ class AvifImageDecoder(source: ImageSource, options: Options, parallelismLock: S
|
||||
|
||||
override fun hashCode() = javaClass.hashCode()
|
||||
|
||||
private fun isApplicable(result: SourceResult): Boolean {
|
||||
private fun isApplicable(result: SourceFetchResult): Boolean {
|
||||
return result.mimeType == "image/avif"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import coil.decode.DecodeResult
|
||||
import coil.decode.Decoder
|
||||
import coil.decode.ImageSource
|
||||
import coil.request.Options
|
||||
import coil.size.Dimension
|
||||
import coil.size.Scale
|
||||
import coil.size.Size
|
||||
import coil.size.isOriginal
|
||||
import coil.size.pxOrElse
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import org.jetbrains.annotations.Blocking
|
||||
|
||||
abstract class BaseCoilDecoder(
|
||||
protected val source: ImageSource,
|
||||
protected val options: Options,
|
||||
private val parallelismLock: Semaphore,
|
||||
) : Decoder {
|
||||
|
||||
final override suspend fun decode(): DecodeResult = parallelismLock.withPermit {
|
||||
runInterruptible { BitmapFactory.Options().decode() }
|
||||
}
|
||||
|
||||
@Blocking
|
||||
protected abstract fun BitmapFactory.Options.decode(): DecodeResult
|
||||
|
||||
protected companion object {
|
||||
|
||||
const val DEFAULT_PARALLELISM = 4
|
||||
|
||||
inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
|
||||
return if (isOriginal) original() else width.toPx(scale)
|
||||
}
|
||||
|
||||
inline fun Size.heightPx(scale: Scale, original: () -> Int): Int {
|
||||
return if (isOriginal) original() else height.toPx(scale)
|
||||
}
|
||||
|
||||
fun Dimension.toPx(scale: Scale) = pxOrElse {
|
||||
when (scale) {
|
||||
Scale.FILL -> Int.MIN_VALUE
|
||||
Scale.FIT -> Int.MAX_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package org.koitharu.kotatsu.core.image
|
||||
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import coil3.ImageLoader
|
||||
import coil3.decode.DataSource
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.Fetcher
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.request.Options
|
||||
import coil3.toAndroidUri
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okio.Path.Companion.toPath
|
||||
import okio.openZip
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import coil3.Uri as CoilUri
|
||||
|
||||
class CbzFetcher(
|
||||
private val uri: Uri,
|
||||
private val options: Options,
|
||||
) : Fetcher {
|
||||
|
||||
override suspend fun fetch() = runInterruptible {
|
||||
val filePath = uri.schemeSpecificPart.toPath()
|
||||
val entryName = requireNotNull(uri.fragment)
|
||||
SourceFetchResult(
|
||||
source = ImageSource(entryName.toPath(), options.fileSystem.openZip(filePath)),
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(entryName.substringAfterLast('.', "")),
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
}
|
||||
|
||||
class Factory : Fetcher.Factory<CoilUri> {
|
||||
|
||||
override fun create(
|
||||
data: CoilUri,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Fetcher? {
|
||||
val androidUri = data.toAndroidUri()
|
||||
return if (androidUri.isZipUri()) {
|
||||
CbzFetcher(androidUri, options)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package org.koitharu.kotatsu.core.image
|
||||
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.network.httpHeaders
|
||||
import coil3.request.ImageResult
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceKey
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
|
||||
class MangaSourceHeaderInterceptor : Interceptor {
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val mangaSource = chain.request.extras[mangaSourceKey] as? MangaParserSource ?: return chain.proceed()
|
||||
val request = chain.request
|
||||
val newHeaders = request.httpHeaders.newBuilder()
|
||||
.set(CommonHeaders.MANGA_SOURCE, mangaSource.name)
|
||||
.build()
|
||||
val newRequest = request.newBuilder()
|
||||
.httpHeaders(newHeaders)
|
||||
.build()
|
||||
return chain.withRequest(newRequest).proceed()
|
||||
}
|
||||
}
|
||||
@@ -5,22 +5,35 @@ import android.graphics.BitmapFactory
|
||||
import android.graphics.BitmapRegionDecoder
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DecodeResult
|
||||
import coil.decode.DecodeUtils
|
||||
import coil.decode.Decoder
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.SourceResult
|
||||
import coil.request.Options
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import coil3.Extras
|
||||
import coil3.ImageLoader
|
||||
import coil3.asImage
|
||||
import coil3.decode.DecodeResult
|
||||
import coil3.decode.DecodeUtils
|
||||
import coil3.decode.Decoder
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.getExtra
|
||||
import coil3.request.Options
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.bitmapConfig
|
||||
import coil3.request.colorSpace
|
||||
import coil3.request.premultipliedAlpha
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.Precision
|
||||
import coil3.size.Scale
|
||||
import coil3.size.Size
|
||||
import coil3.size.isOriginal
|
||||
import coil3.size.pxOrElse
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class RegionBitmapDecoder(
|
||||
source: ImageSource, options: Options, parallelismLock: Semaphore
|
||||
) : BaseCoilDecoder(source, options, parallelismLock) {
|
||||
private val source: ImageSource,
|
||||
private val options: Options,
|
||||
) : Decoder {
|
||||
|
||||
override fun BitmapFactory.Options.decode(): DecodeResult {
|
||||
override suspend fun decode(): DecodeResult = runInterruptible {
|
||||
val regionDecoder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
BitmapRegionDecoder.newInstance(source.source().inputStream())
|
||||
} else {
|
||||
@@ -28,13 +41,14 @@ class RegionBitmapDecoder(
|
||||
BitmapRegionDecoder.newInstance(source.source().inputStream(), false)
|
||||
}
|
||||
checkNotNull(regionDecoder)
|
||||
val bitmapOptions = BitmapFactory.Options()
|
||||
try {
|
||||
val rect = configureScale(regionDecoder.width, regionDecoder.height)
|
||||
configureConfig()
|
||||
val bitmap = regionDecoder.decodeRegion(rect, this)
|
||||
val rect = bitmapOptions.configureScale(regionDecoder.width, regionDecoder.height)
|
||||
bitmapOptions.configureConfig()
|
||||
val bitmap = regionDecoder.decodeRegion(rect, bitmapOptions)
|
||||
bitmap.density = options.context.resources.displayMetrics.densityDpi
|
||||
return DecodeResult(
|
||||
drawable = bitmap.toDrawable(options.context.resources),
|
||||
DecodeResult(
|
||||
image = bitmap.asImage(),
|
||||
isSampled = true,
|
||||
)
|
||||
} finally {
|
||||
@@ -55,7 +69,7 @@ class RegionBitmapDecoder(
|
||||
} else {
|
||||
Rect(0, 0, (srcHeight / dstRatio).toInt().coerceAtLeast(1), srcHeight)
|
||||
}
|
||||
val scroll = options.parameters.value(PARAM_SCROLL) ?: SCROLL_UNDEFINED
|
||||
val scroll = options.getExtra(regionScrollKey)
|
||||
if (scroll == SCROLL_UNDEFINED) {
|
||||
rect.offsetTo(
|
||||
(srcWidth - rect.width()) / 2,
|
||||
@@ -87,7 +101,7 @@ class RegionBitmapDecoder(
|
||||
)
|
||||
|
||||
// Only upscale the image if the options require an exact size.
|
||||
if (options.allowInexactSize) {
|
||||
if (options.precision == Precision.INEXACT) {
|
||||
scale = scale.coerceAtMost(1.0)
|
||||
}
|
||||
|
||||
@@ -107,7 +121,7 @@ class RegionBitmapDecoder(
|
||||
}
|
||||
|
||||
private fun BitmapFactory.Options.configureConfig() {
|
||||
var config = options.config
|
||||
var config = options.bitmapConfig
|
||||
|
||||
inMutable = false
|
||||
|
||||
@@ -131,13 +145,11 @@ class RegionBitmapDecoder(
|
||||
|
||||
object Factory : Decoder.Factory {
|
||||
|
||||
private val parallelismLock = Semaphore(DEFAULT_PARALLELISM)
|
||||
|
||||
override fun create(
|
||||
result: SourceResult,
|
||||
result: SourceFetchResult,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Decoder = RegionBitmapDecoder(result.source, options, parallelismLock)
|
||||
): Decoder = RegionBitmapDecoder(result.source, options)
|
||||
|
||||
override fun equals(other: Any?) = other is Factory
|
||||
|
||||
@@ -146,7 +158,22 @@ class RegionBitmapDecoder(
|
||||
|
||||
companion object {
|
||||
|
||||
const val PARAM_SCROLL = "scroll"
|
||||
const val SCROLL_UNDEFINED = -1
|
||||
val regionScrollKey = Extras.Key(SCROLL_UNDEFINED)
|
||||
|
||||
private inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
|
||||
return if (isOriginal) original() else width.toPx(scale)
|
||||
}
|
||||
|
||||
private inline fun Size.heightPx(scale: Scale, original: () -> Int): Int {
|
||||
return if (isOriginal) original() else height.toPx(scale)
|
||||
}
|
||||
|
||||
private fun Dimension.toPx(scale: Scale) = pxOrElse {
|
||||
when (scale) {
|
||||
Scale.FILL -> Int.MIN_VALUE
|
||||
Scale.FIT -> Int.MAX_VALUE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ object CommonHeaders {
|
||||
const val CACHE_CONTROL = "Cache-Control"
|
||||
const val PROXY_AUTHORIZATION = "Proxy-Authorization"
|
||||
const val RETRY_AFTER = "Retry-After"
|
||||
const val MANGA_SOURCE = "X-Manga-Source"
|
||||
|
||||
val CACHE_CONTROL_NO_STORE: CacheControl
|
||||
get() = CacheControl.Builder().noStore().build()
|
||||
|
||||
@@ -9,11 +9,12 @@ import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.BuildConfig
|
||||
import org.koitharu.kotatsu.core.model.UnknownMangaSource
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.model.MangaParserSource
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mergeWith
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
@@ -30,15 +31,17 @@ class CommonHeadersInterceptor @Inject constructor(
|
||||
override fun intercept(chain: Chain): Response {
|
||||
val request = chain.request()
|
||||
val source = request.tag(MangaSource::class.java)
|
||||
val repository = if (source == null || source == UnknownMangaSource) {
|
||||
?: request.headers[CommonHeaders.MANGA_SOURCE]?.let { MangaSource(it) }
|
||||
val repository = if (source is MangaParserSource) {
|
||||
mangaRepositoryFactoryLazy.get().create(source) as? ParserMangaRepository
|
||||
} else {
|
||||
if (BuildConfig.DEBUG && source == null) {
|
||||
Log.w("Http", "Request without source tag: ${request.url}")
|
||||
}
|
||||
null
|
||||
} else {
|
||||
mangaRepositoryFactoryLazy.get().create(source) as? ParserMangaRepository
|
||||
}
|
||||
val headersBuilder = request.headers.newBuilder()
|
||||
.removeAll(CommonHeaders.MANGA_SOURCE)
|
||||
repository?.getRequestHeaders()?.let {
|
||||
headersBuilder.mergeWith(it, replaceExisting = false)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import android.util.Log
|
||||
import androidx.collection.ArraySet
|
||||
import coil.intercept.Interceptor
|
||||
import coil.network.HttpException
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.ImageResult
|
||||
import coil.request.SuccessResult
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.network.HttpException
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.ImageResult
|
||||
import coil3.request.SuccessResult
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -35,14 +35,14 @@ abstract class BaseImageProxyInterceptor : ImageProxyInterceptor {
|
||||
else -> null
|
||||
}
|
||||
if (url == null || !url.isHttpOrHttps || url.host in blacklist) {
|
||||
return chain.proceed(request)
|
||||
return chain.proceed()
|
||||
}
|
||||
val newRequest = onInterceptImageRequest(request, url)
|
||||
return when (val result = chain.proceed(newRequest)) {
|
||||
return when (val result = chain.withRequest(newRequest).proceed()) {
|
||||
is SuccessResult -> result
|
||||
is ErrorResult -> {
|
||||
logDebug(result.throwable, newRequest.data)
|
||||
chain.proceed(request).also {
|
||||
chain.proceed().also {
|
||||
if (it is SuccessResult && result.throwable.isBlockedByServer()) {
|
||||
blacklist.add(url.host)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.intercept.Interceptor
|
||||
import coil3.intercept.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.intercept.Interceptor
|
||||
import coil.request.ImageResult
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.request.ImageResult
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.plus
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -26,7 +26,7 @@ class RealImageProxyInterceptor @Inject constructor(
|
||||
)
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
return delegate.value?.intercept(chain) ?: chain.proceed(chain.request)
|
||||
return delegate.value?.intercept(chain) ?: chain.proceed()
|
||||
}
|
||||
|
||||
override suspend fun interceptPageRequest(request: Request, okHttp: OkHttpClient): Response {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Dimension
|
||||
import coil.size.isOriginal
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.isOriginal
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Request
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.koitharu.kotatsu.core.network.imageproxy
|
||||
|
||||
import coil.request.ImageRequest
|
||||
import coil3.request.ImageRequest
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
|
||||
@@ -10,10 +10,11 @@ import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.room.InvalidationTracker
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.Size
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.transformations
|
||||
import coil3.size.Scale
|
||||
import coil3.size.Size
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -27,9 +28,9 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.ui.image.ThumbnailTransformation
|
||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.processLifecycleScope
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -138,7 +139,7 @@ class AppShortcutManager @Inject constructor(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
.size(iconSize)
|
||||
.source(manga.source)
|
||||
.mangaSourceExtra(manga.source)
|
||||
.scale(Scale.FILL)
|
||||
.transformations(ThumbnailTransformation())
|
||||
.build(),
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import coil3.network.ConnectivityChecker
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.MediatorStateFlow
|
||||
@@ -13,13 +14,17 @@ import org.koitharu.kotatsu.core.util.MediatorStateFlow
|
||||
class NetworkState(
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
private val settings: AppSettings,
|
||||
) : MediatorStateFlow<Boolean>(connectivityManager.isOnline(settings)) {
|
||||
) : MediatorStateFlow<Boolean>(connectivityManager.isOnline(settings)), ConnectivityChecker {
|
||||
|
||||
private val callback = NetworkCallbackImpl()
|
||||
|
||||
override val value: Boolean
|
||||
get() = connectivityManager.isOnline(settings)
|
||||
|
||||
override fun isOnline(): Boolean {
|
||||
return connectivityManager.isOnline(settings)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onActive() {
|
||||
invalidate()
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import android.util.Log
|
||||
import androidx.collection.MutableLongSet
|
||||
import coil.request.CachePolicy
|
||||
import coil3.request.CachePolicy
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.parser
|
||||
|
||||
import android.net.Uri
|
||||
import coil.request.CachePolicy
|
||||
import coil3.request.CachePolicy
|
||||
import dagger.Reusable
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.model.UnknownMangaSource
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.core.parser.favicon
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
@@ -8,67 +7,43 @@ import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.webkit.MimeTypeMap
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
import coil.disk.DiskCache
|
||||
import coil.fetch.DrawableResult
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.Fetcher
|
||||
import coil.fetch.SourceResult
|
||||
import coil.network.HttpException
|
||||
import coil.request.Options
|
||||
import coil.size.Size
|
||||
import coil.size.pxOrElse
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import coil3.ImageLoader
|
||||
import coil3.asImage
|
||||
import coil3.decode.DataSource
|
||||
import coil3.fetch.FetchResult
|
||||
import coil3.fetch.Fetcher
|
||||
import coil3.fetch.ImageFetchResult
|
||||
import coil3.request.Options
|
||||
import coil3.size.pxOrElse
|
||||
import coil3.toAndroidUri
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.ResponseBody
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okio.Closeable
|
||||
import okio.buffer
|
||||
import okio.IOException
|
||||
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
|
||||
import org.koitharu.kotatsu.core.model.MangaSource
|
||||
import org.koitharu.kotatsu.core.parser.EmptyMangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.ParserMangaRepository
|
||||
import org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.local.data.CacheDir
|
||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.await
|
||||
import org.koitharu.kotatsu.parsers.util.requireBody
|
||||
import java.net.HttpURLConnection
|
||||
import org.koitharu.kotatsu.core.util.ext.fetch
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
private const val FALLBACK_SIZE = 9999 // largest icon
|
||||
import coil3.Uri as CoilUri
|
||||
|
||||
class FaviconFetcher(
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val diskCache: Lazy<DiskCache?>,
|
||||
private val mangaSource: MangaSource,
|
||||
private val uri: Uri,
|
||||
private val options: Options,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : Fetcher {
|
||||
|
||||
private val diskCacheKey
|
||||
get() = options.diskCacheKey ?: "${mangaSource.name}x${options.size.toCacheKey()}"
|
||||
|
||||
private val fileSystem
|
||||
get() = checkNotNull(diskCache.value).fileSystem
|
||||
|
||||
override suspend fun fetch(): FetchResult {
|
||||
getCached(options)?.let { return it }
|
||||
val mangaSource = MangaSource(uri.schemeSpecificPart)
|
||||
|
||||
return when (val repo = mangaRepositoryFactory.create(mangaSource)) {
|
||||
is ParserMangaRepository -> fetchParserFavicon(repo)
|
||||
is ExternalMangaRepository -> fetchPluginIcon(repo)
|
||||
is EmptyMangaRepository -> DrawableResult(
|
||||
drawable = ColorDrawable(Color.WHITE),
|
||||
is EmptyMangaRepository -> ImageFetchResult(
|
||||
image = ColorDrawable(Color.WHITE).asImage(),
|
||||
isSampled = false,
|
||||
dataSource = DataSource.MEMORY,
|
||||
)
|
||||
@@ -77,163 +52,81 @@ class FaviconFetcher(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchParserFavicon(repo: ParserMangaRepository): FetchResult {
|
||||
private suspend fun fetchParserFavicon(repository: ParserMangaRepository): FetchResult {
|
||||
val sizePx = maxOf(
|
||||
options.size.width.pxOrElse { FALLBACK_SIZE },
|
||||
options.size.height.pxOrElse { FALLBACK_SIZE },
|
||||
)
|
||||
var favicons = repo.getFavicons()
|
||||
var favicons = repository.getFavicons()
|
||||
var lastError: Exception? = null
|
||||
while (favicons.isNotEmpty()) {
|
||||
coroutineContext.ensureActive()
|
||||
val icon = favicons.find(sizePx) ?: throwNSEE(lastError)
|
||||
val response = try {
|
||||
loadIcon(icon.url, mangaSource)
|
||||
try {
|
||||
val result = imageLoader.fetch(icon.url, options)
|
||||
if (result != null) {
|
||||
return result
|
||||
} else {
|
||||
favicons -= icon
|
||||
}
|
||||
} catch (e: CloudFlareProtectedException) {
|
||||
throw e
|
||||
} catch (e: HttpException) {
|
||||
} catch (e: IOException) {
|
||||
lastError = e
|
||||
favicons -= icon
|
||||
continue
|
||||
}
|
||||
val responseBody = response.requireBody()
|
||||
val source = writeToDiskCache(responseBody)?.toImageSource()?.also {
|
||||
response.closeQuietly()
|
||||
} ?: responseBody.toImageSource(response)
|
||||
return SourceResult(
|
||||
source = source,
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(icon.type),
|
||||
dataSource = response.toDataSource(),
|
||||
)
|
||||
}
|
||||
throwNSEE(lastError)
|
||||
}
|
||||
|
||||
private suspend fun loadIcon(url: String, source: MangaSource): Response {
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.get()
|
||||
.tag(MangaSource::class.java, source)
|
||||
request.tag(MangaSource::class.java, source)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
options.tags.asMap().forEach { request.tag(it.key as Class<Any>, it.value) }
|
||||
val response = okHttpClient.newCall(request.build()).await()
|
||||
if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||
response.closeQuietly()
|
||||
throw HttpException(response)
|
||||
}
|
||||
return response
|
||||
}
|
||||
|
||||
private suspend fun fetchPluginIcon(repository: ExternalMangaRepository): FetchResult {
|
||||
val source = repository.source
|
||||
val pm = options.context.packageManager
|
||||
val icon = runInterruptible(Dispatchers.IO) {
|
||||
val icon = runInterruptible {
|
||||
val provider = pm.resolveContentProvider(source.authority, 0)
|
||||
provider?.loadIcon(pm) ?: pm.getApplicationIcon(source.packageName)
|
||||
}
|
||||
return DrawableResult(
|
||||
drawable = icon.nonAdaptive(),
|
||||
return ImageFetchResult(
|
||||
image = icon.nonAdaptive().asImage(),
|
||||
isSampled = false,
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCached(options: Options): SourceResult? {
|
||||
if (!options.diskCachePolicy.readEnabled) {
|
||||
return null
|
||||
}
|
||||
val snapshot = diskCache.value?.openSnapshot(diskCacheKey) ?: return null
|
||||
return SourceResult(
|
||||
source = snapshot.toImageSource(),
|
||||
mimeType = null,
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun writeToDiskCache(body: ResponseBody): DiskCache.Snapshot? {
|
||||
if (!options.diskCachePolicy.writeEnabled || body.contentLength() == 0L) {
|
||||
return null
|
||||
}
|
||||
val editor = diskCache.value?.openEditor(diskCacheKey) ?: return null
|
||||
try {
|
||||
fileSystem.write(editor.data) {
|
||||
writeAllCancellable(body.source())
|
||||
}
|
||||
return editor.commitAndOpenSnapshot()
|
||||
} catch (e: Throwable) {
|
||||
try {
|
||||
editor.abort()
|
||||
} catch (abortingError: Throwable) {
|
||||
e.addSuppressed(abortingError)
|
||||
}
|
||||
body.closeQuietly()
|
||||
throw e
|
||||
} finally {
|
||||
body.closeQuietly()
|
||||
}
|
||||
}
|
||||
|
||||
private fun DiskCache.Snapshot.toImageSource(): ImageSource {
|
||||
return ImageSource(data, fileSystem, diskCacheKey, this)
|
||||
}
|
||||
|
||||
private fun ResponseBody.toImageSource(response: Closeable): ImageSource {
|
||||
return ImageSource(
|
||||
source().withExtraCloseable(response).buffer(),
|
||||
options.context,
|
||||
FaviconMetadata(mangaSource),
|
||||
)
|
||||
}
|
||||
|
||||
private fun Response.toDataSource(): DataSource {
|
||||
return if (networkResponse != null) DataSource.NETWORK else DataSource.DISK
|
||||
}
|
||||
|
||||
private fun Size.toCacheKey() = buildString {
|
||||
append(width.toString())
|
||||
append('x')
|
||||
append(height.toString())
|
||||
}
|
||||
|
||||
private fun throwNSEE(lastError: Exception?): Nothing {
|
||||
if (lastError != null) {
|
||||
throw lastError
|
||||
} else {
|
||||
throw NoSuchElementException("No favicons found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Drawable.nonAdaptive() =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this is AdaptiveIconDrawable) {
|
||||
LayerDrawable(arrayOf(background, foreground))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
class Factory(
|
||||
context: Context,
|
||||
okHttpClientLazy: Lazy<OkHttpClient>,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) : Fetcher.Factory<Uri> {
|
||||
) : Fetcher.Factory<CoilUri> {
|
||||
|
||||
private val okHttpClient by okHttpClientLazy
|
||||
private val diskCache = lazy {
|
||||
val rootDir = context.externalCacheDir ?: context.cacheDir
|
||||
DiskCache.Builder()
|
||||
.directory(rootDir.resolve(CacheDir.FAVICONS.dir))
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
|
||||
return if (data.scheme == URI_SCHEME_FAVICON) {
|
||||
val mangaSource = MangaSource(data.schemeSpecificPart)
|
||||
FaviconFetcher(okHttpClient, diskCache, mangaSource, options, mangaRepositoryFactory)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
override fun create(
|
||||
data: CoilUri,
|
||||
options: Options,
|
||||
imageLoader: ImageLoader
|
||||
): Fetcher? = if (data.scheme == URI_SCHEME_FAVICON) {
|
||||
FaviconFetcher(data.toAndroidUri(), options, imageLoader, mangaRepositoryFactory)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
class FaviconMetadata(val source: MangaSource) : ImageSource.Metadata()
|
||||
private companion object {
|
||||
|
||||
const val FALLBACK_SIZE = 9999 // largest icon
|
||||
|
||||
private fun throwNSEE(lastError: Exception?): Nothing {
|
||||
if (lastError != null) {
|
||||
throw lastError
|
||||
} else {
|
||||
throw NoSuchElementException("No favicons found")
|
||||
}
|
||||
}
|
||||
|
||||
private fun Drawable.nonAdaptive() =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this is AdaptiveIconDrawable) {
|
||||
LayerDrawable(arrayOf(background, foreground))
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.core.ui.image
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import coil.target.GenericViewTarget
|
||||
import coil3.target.GenericViewTarget
|
||||
import com.google.android.material.chip.Chip
|
||||
|
||||
class ChipIconTarget(override val view: Chip) : GenericViewTarget<Chip>() {
|
||||
|
||||
@@ -4,10 +4,12 @@ import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.text.Html
|
||||
import androidx.annotation.WorkerThread
|
||||
import coil.ImageLoader
|
||||
import coil.executeBlocking
|
||||
import coil.request.ImageRequest
|
||||
import coil3.ImageLoader
|
||||
import coil3.executeBlocking
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.allowHardware
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.koitharu.kotatsu.core.util.ext.drawable
|
||||
import javax.inject.Inject
|
||||
|
||||
class CoilImageGetter @Inject constructor(
|
||||
|
||||
@@ -4,9 +4,9 @@ import android.view.View
|
||||
import android.view.View.OnLayoutChangeListener
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import coil.size.Dimension
|
||||
import coil.size.Size
|
||||
import coil.size.ViewSizeResolver
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.Size
|
||||
import coil3.size.ViewSizeResolver
|
||||
import kotlinx.coroutines.CancellableContinuation
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@@ -2,11 +2,11 @@ package org.koitharu.kotatsu.core.ui.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.media.ThumbnailUtils
|
||||
import coil.size.Size
|
||||
import coil.size.pxOrElse
|
||||
import coil.transform.Transformation
|
||||
import coil3.size.Size
|
||||
import coil3.size.pxOrElse
|
||||
import coil3.transform.Transformation
|
||||
|
||||
class ThumbnailTransformation : Transformation {
|
||||
class ThumbnailTransformation : Transformation() {
|
||||
|
||||
override val cacheKey: String = javaClass.name
|
||||
|
||||
@@ -17,8 +17,4 @@ class ThumbnailTransformation : Transformation {
|
||||
size.height.pxOrElse { input.height },
|
||||
)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) = other is ThumbnailTransformation
|
||||
|
||||
override fun hashCode() = javaClass.hashCode()
|
||||
}
|
||||
|
||||
@@ -2,13 +2,13 @@ package org.koitharu.kotatsu.core.ui.image
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.core.graphics.get
|
||||
import coil.size.Size
|
||||
import coil.transform.Transformation
|
||||
import coil3.size.Size
|
||||
import coil3.transform.Transformation
|
||||
import org.koitharu.kotatsu.reader.domain.EdgeDetector.Companion.isColorTheSame
|
||||
|
||||
class TrimTransformation(
|
||||
private val tolerance: Int = 20,
|
||||
) : Transformation {
|
||||
) : Transformation() {
|
||||
|
||||
override val cacheKey: String = "${javaClass.name}-$tolerance"
|
||||
|
||||
@@ -92,12 +92,4 @@ class TrimTransformation(
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || (other is TrimTransformation && other.tolerance == tolerance)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return tolerance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,16 @@ import androidx.annotation.ColorRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.children
|
||||
import coil.ImageLoader
|
||||
import coil.request.Disposable
|
||||
import coil.request.ImageRequest
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.Disposable
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipDrawable
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
|
||||
@@ -2,21 +2,37 @@ package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.ImageView
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.ImageResult
|
||||
import coil.request.SuccessResult
|
||||
import coil.util.CoilUtils
|
||||
import coil3.Extras
|
||||
import coil3.ImageLoader
|
||||
import coil3.asDrawable
|
||||
import coil3.fetch.FetchResult
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.ImageResult
|
||||
import coil3.request.Options
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.request.bitmapConfig
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.lifecycle
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.target
|
||||
import coil3.size.Scale
|
||||
import coil3.size.ViewSizeResolver
|
||||
import coil3.toBitmap
|
||||
import coil3.util.CoilUtils
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.core.image.RegionBitmapDecoder
|
||||
import org.koitharu.kotatsu.core.ui.image.AnimatedPlaceholderDrawable
|
||||
import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
@@ -32,6 +48,8 @@ fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): Image
|
||||
.data(data?.takeUnless { it == "" || it == 0 })
|
||||
.lifecycle(lifecycleOwner)
|
||||
.crossfade(context)
|
||||
.size(ViewSizeResolver(this))
|
||||
.scale(scaleType.toCoilScale())
|
||||
.target(this)
|
||||
}
|
||||
|
||||
@@ -43,13 +61,16 @@ fun ImageView.disposeImageRequest() {
|
||||
fun ImageRequest.Builder.enqueueWith(loader: ImageLoader) = loader.enqueue(build())
|
||||
|
||||
fun ImageResult.getDrawableOrThrow() = when (this) {
|
||||
is SuccessResult -> drawable
|
||||
is SuccessResult -> image.asDrawable(request.context.resources)
|
||||
is ErrorResult -> throw throwable
|
||||
}
|
||||
|
||||
val ImageResult.drawable: Drawable?
|
||||
get() = image?.asDrawable(request.context.resources)
|
||||
|
||||
fun ImageResult.toBitmapOrNull() = when (this) {
|
||||
is SuccessResult -> try {
|
||||
drawable.toBitmap()
|
||||
image.toBitmap(image.width, image.height, request.bitmapConfig)
|
||||
} catch (_: Throwable) {
|
||||
null
|
||||
}
|
||||
@@ -63,8 +84,10 @@ fun ImageRequest.Builder.indicator(indicators: List<BaseProgressIndicator<*>>):
|
||||
|
||||
fun ImageRequest.Builder.decodeRegion(
|
||||
scroll: Int = RegionBitmapDecoder.SCROLL_UNDEFINED,
|
||||
): ImageRequest.Builder = decoderFactory(RegionBitmapDecoder.Factory)
|
||||
.setParameter(RegionBitmapDecoder.PARAM_SCROLL, scroll)
|
||||
): ImageRequest.Builder = apply {
|
||||
decoderFactory(RegionBitmapDecoder.Factory)
|
||||
extras[RegionBitmapDecoder.regionScrollKey] = scroll
|
||||
}
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
fun ImageRequest.Builder.crossfade(context: Context): ImageRequest.Builder {
|
||||
@@ -72,8 +95,18 @@ fun ImageRequest.Builder.crossfade(context: Context): ImageRequest.Builder {
|
||||
return crossfade(duration.toInt())
|
||||
}
|
||||
|
||||
fun ImageRequest.Builder.source(source: MangaSource?): ImageRequest.Builder {
|
||||
return tag(MangaSource::class.java, source)
|
||||
fun ImageRequest.Builder.mangaSourceExtra(source: MangaSource?): ImageRequest.Builder = apply {
|
||||
extras[mangaSourceKey] = source
|
||||
}
|
||||
|
||||
fun ImageRequest.Builder.mangaExtra(manga: Manga): ImageRequest.Builder = apply {
|
||||
extras[mangaKey] = manga
|
||||
mangaSourceExtra(manga.source)
|
||||
}
|
||||
|
||||
fun ImageRequest.Builder.bookmarkExtra(bookmark: Bookmark): ImageRequest.Builder = apply {
|
||||
extras[bookmarkKey] = bookmark
|
||||
mangaSourceExtra(bookmark.manga.source)
|
||||
}
|
||||
|
||||
fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Builder {
|
||||
@@ -87,6 +120,12 @@ fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Bui
|
||||
.error(ColorDrawable(errorColor))
|
||||
}
|
||||
|
||||
private fun ImageView.ScaleType.toCoilScale(): Scale = if (this == ImageView.ScaleType.CENTER_CROP) {
|
||||
Scale.FILL
|
||||
} else {
|
||||
Scale.FIT
|
||||
}
|
||||
|
||||
fun ImageRequest.Builder.addListener(listener: ImageRequest.Listener): ImageRequest.Builder {
|
||||
val existing = build().listener
|
||||
return listener(
|
||||
@@ -98,6 +137,12 @@ fun ImageRequest.Builder.addListener(listener: ImageRequest.Listener): ImageRequ
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun ImageLoader.fetch(data: Any, options: Options): FetchResult? {
|
||||
val mappedData = components.map(data, options)
|
||||
val fetcher = components.newFetcher(mappedData, options, this)?.first
|
||||
return fetcher?.fetch()
|
||||
}
|
||||
|
||||
private class CompositeImageRequestListener(
|
||||
private val delegates: Array<ImageRequest.Listener>,
|
||||
) : ImageRequest.Listener {
|
||||
@@ -113,3 +158,7 @@ private class CompositeImageRequestListener(
|
||||
|
||||
operator fun plus(other: ImageRequest.Listener) = CompositeImageRequestListener(delegates + other)
|
||||
}
|
||||
|
||||
val mangaKey = Extras.Key<Manga?>(null)
|
||||
val bookmarkKey = Extras.Key<Bookmark?>(null)
|
||||
val mangaSourceKey = Extras.Key<MangaSource?>(null)
|
||||
|
||||
@@ -61,7 +61,7 @@ fun File.getStorageName(context: Context): String = runCatching {
|
||||
}
|
||||
}.getOrNull() ?: context.getString(R.string.other_storage)
|
||||
|
||||
fun Uri.toFileOrNull() = if (scheme == URI_SCHEME_FILE) path?.let(::File) else null
|
||||
fun Uri.toFileOrNull() = if (isFileUri()) path?.let(::File) else null
|
||||
|
||||
suspend fun File.deleteAwait() = runInterruptible(Dispatchers.IO) {
|
||||
delete() || deleteRecursively()
|
||||
|
||||
@@ -3,8 +3,9 @@ package org.koitharu.kotatsu.core.util.ext
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.DrawableRes
|
||||
import coil.network.HttpException
|
||||
import coil3.network.HttpException
|
||||
import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException
|
||||
import okhttp3.Response
|
||||
import okio.FileNotFoundException
|
||||
import okio.IOException
|
||||
import okio.ProtocolException
|
||||
@@ -121,7 +122,7 @@ fun Throwable.getCauseUrl(): String? = when (this) {
|
||||
is CloudFlareBlockedException -> url
|
||||
is CloudFlareProtectedException -> url
|
||||
is HttpStatusException -> url
|
||||
is HttpException -> response.request.url.toString()
|
||||
is HttpException -> (response.delegate as? Response)?.request?.url?.toString()
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
||||
@@ -1,62 +1,25 @@
|
||||
package org.koitharu.kotatsu.core.util.ext
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toFile
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okio.Source
|
||||
import okio.source
|
||||
import okio.use
|
||||
import org.jetbrains.annotations.Blocking
|
||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
const val URI_SCHEME_FILE = "file"
|
||||
const val URI_SCHEME_ZIP = "file+zip"
|
||||
private const val URI_SCHEME_FILE = "file"
|
||||
private const val URI_SCHEME_HTTP = "http"
|
||||
private const val URI_SCHEME_HTTPS = "https"
|
||||
private const val URI_SCHEME_LEGACY_CBZ = "cbz"
|
||||
private const val URI_SCHEME_LEGACY_ZIP = "zip"
|
||||
|
||||
@Blocking
|
||||
fun Uri.exists(): Boolean = when (scheme) {
|
||||
URI_SCHEME_FILE -> toFile().exists()
|
||||
URI_SCHEME_ZIP -> {
|
||||
val file = File(requireNotNull(schemeSpecificPart))
|
||||
file.exists() && ZipFile(file).use { it.getEntry(fragment) != null }
|
||||
}
|
||||
|
||||
else -> unsupportedUri(this)
|
||||
fun Uri.isZipUri() = scheme.let {
|
||||
it == URI_SCHEME_ZIP || it == URI_SCHEME_LEGACY_CBZ || it == URI_SCHEME_LEGACY_ZIP
|
||||
}
|
||||
|
||||
@Blocking
|
||||
fun Uri.isTargetNotEmpty(): Boolean = when (scheme) {
|
||||
URI_SCHEME_FILE -> toFile().isNotEmpty()
|
||||
URI_SCHEME_ZIP -> {
|
||||
val file = File(requireNotNull(schemeSpecificPart))
|
||||
file.exists() && ZipFile(file).use { (it.getEntry(fragment)?.size ?: 0L) != 0L }
|
||||
}
|
||||
fun Uri.isFileUri() = scheme == URI_SCHEME_FILE
|
||||
|
||||
else -> unsupportedUri(this)
|
||||
}
|
||||
|
||||
@Blocking
|
||||
fun Uri.source(): Source = when (scheme) {
|
||||
URI_SCHEME_FILE -> toFile().source()
|
||||
URI_SCHEME_ZIP -> {
|
||||
val zip = ZipFile(schemeSpecificPart)
|
||||
try {
|
||||
val entry = zip.getEntry(fragment)
|
||||
zip.getInputStream(entry).source().withExtraCloseable(zip)
|
||||
} catch (e: Throwable) {
|
||||
zip.closeQuietly()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
else -> unsupportedUri(this)
|
||||
fun Uri.isNetworkUri() = scheme.let {
|
||||
it == URI_SCHEME_HTTP || it == URI_SCHEME_HTTPS
|
||||
}
|
||||
|
||||
fun File.toZipUri(entryName: String): Uri = Uri.parse("$URI_SCHEME_ZIP://$absolutePath#$entryName")
|
||||
|
||||
fun String.toUriOrNull() = if (isEmpty()) null else Uri.parse(this)
|
||||
|
||||
private fun unsupportedUri(uri: Uri): Nothing {
|
||||
throw IllegalArgumentException("Bad uri $uri: only schemes $URI_SCHEME_FILE and $URI_SCHEME_ZIP are supported")
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.koitharu.kotatsu.core.util.progress
|
||||
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.SuccessResult
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
|
||||
class ImageRequestIndicatorListener(
|
||||
|
||||
@@ -26,11 +26,20 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import coil.util.CoilUtils
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.lifecycle
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.target
|
||||
import coil3.request.transformations
|
||||
import coil3.size.Scale
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import coil3.util.CoilUtils
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
@@ -65,18 +74,19 @@ import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.util.FileSize
|
||||
import org.koitharu.kotatsu.core.util.ext.crossfade
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.drawable
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isTextTruncated
|
||||
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.parentView
|
||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
|
||||
import org.koitharu.kotatsu.details.data.MangaDetails
|
||||
@@ -485,7 +495,7 @@ class DetailsActivity :
|
||||
.placeholder(R.drawable.ic_web)
|
||||
.fallback(R.drawable.ic_web)
|
||||
.error(R.drawable.ic_web)
|
||||
.source(manga.source)
|
||||
.mangaSourceExtra(manga.source)
|
||||
.transformations(RoundedCornersTransformation(resources.getDimension(R.dimen.chip_icon_corner)))
|
||||
.allowRgb565(true)
|
||||
.enqueueWith(coil)
|
||||
@@ -621,8 +631,9 @@ class DetailsActivity :
|
||||
val request = ImageRequest.Builder(this)
|
||||
.target(viewBinding.imageViewCover)
|
||||
.size(CoverSizeResolver(viewBinding.imageViewCover))
|
||||
.scale(Scale.FILL)
|
||||
.data(imageUrl)
|
||||
.tag(manga.source)
|
||||
.mangaSourceExtra(manga.source)
|
||||
.crossfade(this)
|
||||
.lifecycle(this)
|
||||
.placeholderMemoryCacheKey(manga.coverUrl)
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
|
||||
@@ -1,60 +1,53 @@
|
||||
package org.koitharu.kotatsu.details.ui.pager.pages
|
||||
|
||||
import android.content.Context
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.Fetcher
|
||||
import coil.fetch.SourceResult
|
||||
import coil.network.HttpException
|
||||
import coil.request.Options
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import coil3.ImageLoader
|
||||
import coil3.decode.DataSource
|
||||
import coil3.decode.ImageSource
|
||||
import coil3.fetch.FetchResult
|
||||
import coil3.fetch.Fetcher
|
||||
import coil3.fetch.SourceFetchResult
|
||||
import coil3.network.HttpException
|
||||
import coil3.network.NetworkHeaders
|
||||
import coil3.network.NetworkResponse
|
||||
import coil3.network.NetworkResponseBody
|
||||
import coil3.request.Options
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okhttp3.Response
|
||||
import okio.FileSystem
|
||||
import okio.Path.Companion.toOkioPath
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.fetch
|
||||
import org.koitharu.kotatsu.core.util.ext.isNetworkUri
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.local.data.isFileUri
|
||||
import org.koitharu.kotatsu.local.data.isZipUri
|
||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.mimeType
|
||||
import org.koitharu.kotatsu.parsers.util.requireBody
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
|
||||
class MangaPageFetcher(
|
||||
private val context: Context,
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val pagesCache: PagesCache,
|
||||
private val options: Options,
|
||||
private val page: MangaPage,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val imageProxyInterceptor: ImageProxyInterceptor,
|
||||
private val imageLoader: ImageLoader,
|
||||
) : Fetcher {
|
||||
|
||||
override suspend fun fetch(): FetchResult {
|
||||
override suspend fun fetch(): FetchResult? {
|
||||
val repo = mangaRepositoryFactory.create(page.source)
|
||||
val pageUrl = repo.getPageUrl(page)
|
||||
if (options.diskCachePolicy.readEnabled) {
|
||||
pagesCache.get(pageUrl)?.let { file ->
|
||||
return SourceResult(
|
||||
source = ImageSource(
|
||||
file = file.toOkioPath(),
|
||||
metadata = MangaPageMetadata(page),
|
||||
),
|
||||
mimeType = null,
|
||||
return SourceFetchResult(
|
||||
source = ImageSource(file.toOkioPath(), options.fileSystem),
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension),
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
}
|
||||
@@ -62,67 +55,50 @@ class MangaPageFetcher(
|
||||
return loadPage(pageUrl)
|
||||
}
|
||||
|
||||
private suspend fun loadPage(pageUrl: String): SourceResult {
|
||||
val uri = pageUrl.toUri()
|
||||
return when {
|
||||
uri.isZipUri() -> runInterruptible(Dispatchers.IO) {
|
||||
val zip = ZipFile(uri.schemeSpecificPart)
|
||||
try {
|
||||
val entry = zip.getEntry(uri.fragment)
|
||||
SourceResult(
|
||||
source = ImageSource(
|
||||
source = zip.getInputStream(entry).source().withExtraCloseable(zip).buffer(),
|
||||
context = context,
|
||||
metadata = MangaPageMetadata(page),
|
||||
),
|
||||
mimeType = MimeTypeMap.getSingleton()
|
||||
.getMimeTypeFromExtension(entry.name.substringAfterLast('.', "")),
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
zip.closeQuietly()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
private suspend fun loadPage(pageUrl: String): FetchResult? = if (pageUrl.toUri().isNetworkUri()) {
|
||||
fetchPage(pageUrl)
|
||||
} else {
|
||||
imageLoader.fetch(pageUrl, options)
|
||||
}
|
||||
|
||||
uri.isFileUri() -> runInterruptible(Dispatchers.IO) {
|
||||
val file = uri.toFile()
|
||||
SourceResult(
|
||||
source = ImageSource(
|
||||
source = file.source().buffer(),
|
||||
context = context,
|
||||
metadata = MangaPageMetadata(page),
|
||||
),
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(file.extension),
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
private suspend fun fetchPage(pageUrl: String): FetchResult {
|
||||
val request = PageLoader.createPageRequest(pageUrl, page.source)
|
||||
return imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw HttpException(response.toNetworkResponse())
|
||||
}
|
||||
|
||||
else -> {
|
||||
val request = PageLoader.createPageRequest(pageUrl, page.source)
|
||||
imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw HttpException(response)
|
||||
}
|
||||
val mimeType = response.mimeType
|
||||
val file = response.requireBody().use {
|
||||
pagesCache.put(pageUrl, it.source(), mimeType)
|
||||
}
|
||||
SourceResult(
|
||||
source = ImageSource(
|
||||
file = file.toOkioPath(),
|
||||
metadata = MangaPageMetadata(page),
|
||||
),
|
||||
mimeType = mimeType,
|
||||
dataSource = DataSource.NETWORK,
|
||||
)
|
||||
}
|
||||
val mimeType = response.mimeType
|
||||
val file = response.requireBody().use {
|
||||
pagesCache.put(pageUrl, it.source(), mimeType)
|
||||
}
|
||||
SourceFetchResult(
|
||||
source = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),
|
||||
mimeType = mimeType,
|
||||
dataSource = DataSource.NETWORK,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Response.toNetworkResponse(): NetworkResponse {
|
||||
return NetworkResponse(
|
||||
code = code,
|
||||
requestMillis = sentRequestAtMillis,
|
||||
responseMillis = receivedResponseAtMillis,
|
||||
headers = headers.toNetworkHeaders(),
|
||||
body = body?.source()?.let(::NetworkResponseBody),
|
||||
delegate = this,
|
||||
)
|
||||
}
|
||||
|
||||
private fun Headers.toNetworkHeaders(): NetworkHeaders {
|
||||
val headers = NetworkHeaders.Builder()
|
||||
for ((key, values) in this) {
|
||||
headers.add(key, values)
|
||||
}
|
||||
return headers.build()
|
||||
}
|
||||
|
||||
class Factory @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
@MangaHttpClient private val okHttpClient: OkHttpClient,
|
||||
private val pagesCache: PagesCache,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
@@ -134,11 +110,9 @@ class MangaPageFetcher(
|
||||
pagesCache = pagesCache,
|
||||
options = options,
|
||||
page = data,
|
||||
context = context,
|
||||
mangaRepositoryFactory = mangaRepositoryFactory,
|
||||
imageProxyInterceptor = imageProxyInterceptor,
|
||||
imageLoader = imageLoader,
|
||||
)
|
||||
}
|
||||
|
||||
class MangaPageMetadata(val page: MangaPage) : ImageSource.Metadata()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.details.ui.pager.pages
|
||||
|
||||
import coil.key.Keyer
|
||||
import coil.request.Options
|
||||
import coil3.key.Keyer
|
||||
import coil3.request.Options
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
|
||||
class MangaPageKeyer : Keyer<MangaPage> {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.koitharu.kotatsu.details.ui.pager.pages
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil.size.Scale
|
||||
import coil.size.Size
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.size.Scale
|
||||
import coil3.size.Size
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
@@ -11,9 +12,9 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.setTextColorAttr
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemPageThumbBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import com.google.android.material.R as materialR
|
||||
@@ -42,13 +43,13 @@ fun pageThumbnailAD(
|
||||
scale(Scale.FILL)
|
||||
allowRgb565(true)
|
||||
decodeRegion(0)
|
||||
source(item.page.source)
|
||||
mangaSourceExtra(item.page.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
with(binding.textViewNumber) {
|
||||
setBackgroundResource(if (item.isCurrent) R.drawable.bg_badge_accent else R.drawable.bg_badge_empty)
|
||||
setTextColorAttr(if (item.isCurrent) materialR.attr.colorOnTertiary else android.R.attr.textColorPrimary)
|
||||
text = (item.number).toString()
|
||||
text = item.number.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.details.ui.pager.pages
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
|
||||
|
||||
@@ -10,7 +10,7 @@ import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.combine
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.details.ui.scrobbling
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
|
||||
@@ -14,7 +14,7 @@ import androidx.core.net.toUri
|
||||
import androidx.core.text.method.LinkMovementMethodCompat
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.details.ui.scrobbling
|
||||
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.work.WorkInfo
|
||||
import coil.ImageLoader
|
||||
import coil.request.SuccessResult
|
||||
import coil.util.CoilUtils
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.transformations
|
||||
import coil3.util.CoilUtils
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import kotlinx.coroutines.CoroutineStart
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -19,8 +21,8 @@ import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemDownloadBinding
|
||||
import org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter
|
||||
@@ -92,7 +94,7 @@ fun downloadItemAD(
|
||||
allowRgb565(true)
|
||||
transformations(TrimTransformation())
|
||||
memoryCacheKey(item.coverCacheKey)
|
||||
source(item.manga?.source)
|
||||
mangaSourceExtra(item.manga?.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.core.text.bold
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.color
|
||||
import androidx.work.WorkInfo
|
||||
import coil.memory.MemoryCache
|
||||
import coil3.memory.MemoryCache
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter
|
||||
|
||||
@@ -9,7 +9,7 @@ import androidx.activity.viewModels
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.download.ui.list
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD
|
||||
|
||||
@@ -12,9 +12,10 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.work.WorkManager
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.allowHardware
|
||||
import coil3.size.Scale
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
@@ -26,6 +27,7 @@ import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
||||
import org.koitharu.kotatsu.core.model.LocalMangaSource
|
||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||
import org.koitharu.kotatsu.core.util.ext.isReportable
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.details.ui.DetailsActivity
|
||||
import org.koitharu.kotatsu.download.domain.DownloadState
|
||||
@@ -279,7 +281,7 @@ class DownloadNotificationFactory @AssistedInject constructor(
|
||||
ImageRequest.Builder(context)
|
||||
.data(manga.coverUrl)
|
||||
.allowHardware(false)
|
||||
.tag(manga.source)
|
||||
.mangaSourceExtra(manga.source)
|
||||
.size(coverWidth, coverHeight)
|
||||
.scale(Scale.FILL)
|
||||
.build(),
|
||||
|
||||
@@ -18,7 +18,7 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.ui.AllBookmarksActivity
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.explore.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
|
||||
|
||||
@@ -3,7 +3,12 @@ package org.koitharu.kotatsu.explore.ui.adapter
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.transformations
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.getSummary
|
||||
@@ -18,10 +23,10 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.drawableStart
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.recyclerView
|
||||
import org.koitharu.kotatsu.core.util.ext.setProgressIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemExploreSourceGridBinding
|
||||
@@ -94,7 +99,7 @@ fun recommendationMangaItemAD(
|
||||
defaultPlaceholders(context)
|
||||
allowRgb565(true)
|
||||
transformations(TrimTransformation())
|
||||
source(item.manga.source)
|
||||
mangaSourceExtra(item.manga.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
@@ -128,7 +133,7 @@ fun exploreSourceListItemAD(
|
||||
fallback(fallbackIcon)
|
||||
placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name))
|
||||
error(fallbackIcon)
|
||||
source(item.source)
|
||||
mangaSourceExtra(item.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
@@ -160,7 +165,7 @@ fun exploreSourceGridItemAD(
|
||||
fallback(fallbackIcon)
|
||||
placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name))
|
||||
error(fallbackIcon)
|
||||
source(item.source)
|
||||
mangaSourceExtra(item.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.ReorderableListAdapter
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
|
||||
@@ -14,14 +14,19 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemCategoryBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener
|
||||
@@ -87,7 +92,7 @@ fun categoryAD(
|
||||
coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run {
|
||||
placeholder(R.drawable.ic_placeholder)
|
||||
fallback(fallback)
|
||||
source(cover?.mangaSource)
|
||||
mangaSourceExtra(cover?.mangaSource)
|
||||
crossfade(crossFadeDuration * (i + 1))
|
||||
error(R.drawable.ic_error_placeholder)
|
||||
allowRgb565(true)
|
||||
@@ -153,7 +158,7 @@ fun allCategoriesAD(
|
||||
coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run {
|
||||
placeholder(R.drawable.ic_placeholder)
|
||||
fallback(fallback)
|
||||
source(cover?.mangaSource)
|
||||
mangaSourceExtra(cover?.mangaSource)
|
||||
crossfade(crossFadeDuration * (i + 1))
|
||||
error(R.drawable.ic_error_placeholder)
|
||||
allowRgb565(true)
|
||||
|
||||
@@ -7,7 +7,7 @@ import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
|
||||
@@ -9,7 +9,12 @@ import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.disposeImageRequest
|
||||
@@ -17,8 +22,8 @@ import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getAnimationDuration
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemCategoriesHeaderBinding
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesActivity
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.edit.FavouritesCategoryEditActivity
|
||||
@@ -74,7 +79,7 @@ fun categoriesHeaderAD(
|
||||
view.newImageRequest(lifecycleOwner, cover.url)?.run {
|
||||
placeholder(R.drawable.ic_placeholder)
|
||||
fallback(fallback)
|
||||
source(cover.mangaSource)
|
||||
mangaSourceExtra(cover.mangaSource)
|
||||
crossfade(crossFadeDuration * (i + 1))
|
||||
error(R.drawable.ic_error_placeholder)
|
||||
allowRgb565(true)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.favourites.ui.categories.select.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem
|
||||
|
||||
@@ -12,7 +12,7 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.history.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
|
||||
@@ -13,12 +13,15 @@ import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil.target.ViewTarget
|
||||
import coil3.Image
|
||||
import coil3.ImageLoader
|
||||
import coil3.asDrawable
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.request.lifecycle
|
||||
import coil3.target.ViewTarget
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -33,9 +36,9 @@ import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayIcon
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ActivityImageBinding
|
||||
import org.koitharu.kotatsu.databinding.ItemErrorStateBinding
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
@@ -120,7 +123,7 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.lifecycle(this)
|
||||
.listener(this)
|
||||
.source(MangaSource(intent.getStringExtra(EXTRA_SOURCE)))
|
||||
.mangaSourceExtra(MangaSource(intent.getStringExtra(EXTRA_SOURCE)))
|
||||
.target(SsivTarget(viewBinding.ssiv))
|
||||
.enqueueWith(coil)
|
||||
}
|
||||
@@ -152,9 +155,9 @@ class ImageActivity : BaseActivity<ActivityImageBinding>(), ImageRequest.Listene
|
||||
override val view: SubsamplingScaleImageView,
|
||||
) : ViewTarget<SubsamplingScaleImageView> {
|
||||
|
||||
override fun onError(error: Drawable?) = setDrawable(error)
|
||||
override fun onError(error: Image?) = setDrawable(error?.asDrawable(view.resources))
|
||||
|
||||
override fun onSuccess(result: Drawable) = setDrawable(result)
|
||||
override fun onSuccess(result: Image) = setDrawable(result.asDrawable(view.resources))
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return (this === other) || (other is SsivTarget && view == other.view)
|
||||
|
||||
@@ -11,8 +11,8 @@ import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.view.MenuProvider
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||
import org.koitharu.kotatsu.local.data.isZipUri
|
||||
|
||||
class ImageMenuProvider(
|
||||
private val activity: ComponentActivity,
|
||||
|
||||
@@ -5,9 +5,9 @@ import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import coil.ImageLoader
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.CachePolicy
|
||||
import coil3.request.ImageRequest
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -18,8 +18,8 @@ import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.getDrawableOrThrow
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -37,7 +37,7 @@ class ImageViewModel @Inject constructor(
|
||||
.memoryCachePolicy(CachePolicy.READ_ONLY)
|
||||
.data(savedStateHandle.require<Uri>(BaseActivity.EXTRA_DATA))
|
||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||
.source(MangaSource(savedStateHandle[ImageActivity.EXTRA_SOURCE]))
|
||||
.mangaSourceExtra(MangaSource(savedStateHandle[ImageActivity.EXTRA_SOURCE]))
|
||||
.build()
|
||||
val bitmap = coil.execute(request).getDrawableOrThrow().toBitmap()
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
|
||||
@@ -2,7 +2,9 @@ package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.transformations
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
@@ -11,8 +13,8 @@ import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -42,8 +44,7 @@ fun mangaGridItemAD(
|
||||
defaultPlaceholders(context)
|
||||
transformations(TrimTransformation())
|
||||
allowRgb565(true)
|
||||
tag(item.manga)
|
||||
source(item.source)
|
||||
mangaExtra(item.manga)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.transformations
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
@@ -9,8 +11,8 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
|
||||
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
|
||||
@@ -40,8 +42,7 @@ fun mangaListDetailedItemAD(
|
||||
defaultPlaceholders(context)
|
||||
transformations(TrimTransformation())
|
||||
allowRgb565(true)
|
||||
tag(item.manga)
|
||||
source(item.source)
|
||||
mangaExtra(item.manga)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
binding.textViewTags.text = item.tags.joinToString(separator = ", ") { it.title ?: "" }
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package org.koitharu.kotatsu.list.ui.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import coil3.request.transformations
|
||||
import com.google.android.material.badge.BadgeDrawable
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
@@ -9,8 +11,8 @@ import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
import org.koitharu.kotatsu.databinding.ItemMangaListBinding
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
@@ -35,8 +37,7 @@ fun mangaListItemAD(
|
||||
defaultPlaceholders(context)
|
||||
allowRgb565(true)
|
||||
transformations(TrimTransformation())
|
||||
tag(item.manga)
|
||||
source(item.source)
|
||||
mangaExtra(item.manga)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
badge = itemView.bindBadge(badge, item.counter)
|
||||
|
||||
@@ -9,10 +9,15 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.text.method.LinkMovementMethodCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.viewModels
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import coil.util.CoilUtils
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.SuccessResult
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.lifecycle
|
||||
import coil3.request.placeholder
|
||||
import coil3.request.target
|
||||
import coil3.util.CoilUtils
|
||||
import com.google.android.material.chip.Chip
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -21,8 +26,10 @@ import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
|
||||
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
|
||||
import org.koitharu.kotatsu.core.util.ext.crossfade
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
import org.koitharu.kotatsu.core.util.ext.drawable
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.scaleUpActivityOptionsOf
|
||||
import org.koitharu.kotatsu.core.util.ext.textAndVisible
|
||||
@@ -169,7 +176,7 @@ class PreviewFragment : BaseFragment<FragmentPreviewBinding>(), View.OnClickList
|
||||
.target(requireViewBinding().imageViewCover)
|
||||
.size(CoverSizeResolver(requireViewBinding().imageViewCover))
|
||||
.data(imageUrl)
|
||||
.tag(manga.source)
|
||||
.mangaSourceExtra(manga.source)
|
||||
.crossfade(requireContext())
|
||||
.lifecycle(viewLifecycleOwner)
|
||||
.placeholderMemoryCacheKey(manga.coverUrl)
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
package org.koitharu.kotatsu.local.data
|
||||
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DataSource
|
||||
import coil.decode.ImageSource
|
||||
import coil.fetch.Fetcher
|
||||
import coil.fetch.SourceResult
|
||||
import coil.request.Options
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import org.koitharu.kotatsu.local.data.util.withExtraCloseable
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
class CbzFetcher(
|
||||
private val uri: Uri,
|
||||
private val options: Options
|
||||
) : Fetcher {
|
||||
|
||||
override suspend fun fetch() = runInterruptible(Dispatchers.IO) {
|
||||
val zip = ZipFile(uri.schemeSpecificPart)
|
||||
try {
|
||||
val entry = zip.getEntry(uri.fragment)
|
||||
val ext = MimeTypeMap.getFileExtensionFromUrl(entry.name)
|
||||
val bufferedSource = zip.getInputStream(entry).source().withExtraCloseable(zip).buffer()
|
||||
SourceResult(
|
||||
source = ImageSource(
|
||||
source = bufferedSource,
|
||||
context = options.context,
|
||||
metadata = CbzMetadata(uri),
|
||||
),
|
||||
mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext),
|
||||
dataSource = DataSource.DISK,
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
zip.closeQuietly()
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
class Factory : Fetcher.Factory<Uri> {
|
||||
|
||||
override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
|
||||
return if (data.scheme == "cbz") {
|
||||
CbzFetcher(data, options)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CbzMetadata(val uri: Uri) : ImageSource.Metadata()
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.koitharu.kotatsu.local.data
|
||||
|
||||
import android.net.Uri
|
||||
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_ZIP
|
||||
import java.io.File
|
||||
|
||||
private fun isCbzExtension(ext: String?): Boolean {
|
||||
@@ -14,9 +12,3 @@ fun hasCbzExtension(string: String): Boolean {
|
||||
}
|
||||
|
||||
fun File.hasCbzExtension() = isCbzExtension(extension)
|
||||
|
||||
fun Uri.isZipUri() = scheme.let {
|
||||
it == URI_SCHEME_ZIP || it == "cbz" || it == "zip"
|
||||
}
|
||||
|
||||
fun Uri.isFileUri() = scheme == "file"
|
||||
|
||||
@@ -19,9 +19,9 @@ import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Cache
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.URI_SCHEME_FILE
|
||||
import org.koitharu.kotatsu.core.util.ext.computeSize
|
||||
import org.koitharu.kotatsu.core.util.ext.getStorageName
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.resolveFile
|
||||
import org.koitharu.kotatsu.parsers.util.mapToSet
|
||||
import java.io.File
|
||||
@@ -90,7 +90,7 @@ class LocalStorageManager @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun resolveUri(uri: Uri): File? = runInterruptible(Dispatchers.IO) {
|
||||
if (uri.scheme == URI_SCHEME_FILE) {
|
||||
if (uri.isFileUri()) {
|
||||
uri.toFile()
|
||||
} else {
|
||||
uri.resolveFile(context)
|
||||
|
||||
@@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koitharu.kotatsu.core.util.ext.toZipUri
|
||||
import org.koitharu.kotatsu.local.data.hasCbzExtension
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -64,8 +65,7 @@ sealed class LocalMangaInput(
|
||||
}.flowOn(Dispatchers.Default).firstOrNull()
|
||||
|
||||
@JvmStatic
|
||||
protected fun zipUri(file: File, entryName: String): String =
|
||||
Uri.fromParts("cbz", file.path, entryName).toString()
|
||||
protected fun zipUri(file: File, entryName: String): String = file.toZipUri(entryName).toString()
|
||||
|
||||
@JvmStatic
|
||||
protected fun Manga.copy2(
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package org.koitharu.kotatsu.local.data.util
|
||||
|
||||
import okhttp3.internal.closeQuietly
|
||||
import okio.Closeable
|
||||
import okio.Source
|
||||
|
||||
private class ExtraCloseableSource(
|
||||
private val delegate: Source,
|
||||
private val extraCloseable: Closeable,
|
||||
) : Source by delegate {
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
delegate.close()
|
||||
} finally {
|
||||
extraCloseable.closeQuietly()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Source.withExtraCloseable(closeable: Closeable): Source = ExtraCloseableSource(this, closeable)
|
||||
@@ -13,8 +13,8 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.R
|
||||
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.ErrorReporterReceiver
|
||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||
@@ -113,7 +114,7 @@ class ImportService : CoroutineIntentService() {
|
||||
coil.execute(
|
||||
ImageRequest.Builder(applicationContext)
|
||||
.data(manga.coverUrl)
|
||||
.tag(manga.source)
|
||||
.mangaSourceExtra(manga.source)
|
||||
.build(),
|
||||
).toBitmapOrNull(),
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package org.koitharu.kotatsu.main.domain
|
||||
|
||||
import androidx.collection.ArraySet
|
||||
import coil.intercept.Interceptor
|
||||
import coil.network.HttpException
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageResult
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.network.HttpException
|
||||
import coil3.request.ErrorResult
|
||||
import coil3.request.ImageResult
|
||||
import okio.FileNotFoundException
|
||||
import org.jsoup.HttpStatusException
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
@@ -13,7 +13,9 @@ import org.koitharu.kotatsu.core.model.findById
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.ext.bookmarkKey
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaKey
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.parsers.exception.ParseException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
@@ -33,18 +35,18 @@ class CoverRestoreInterceptor @Inject constructor(
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val request = chain.request
|
||||
val result = chain.proceed(request)
|
||||
val result = chain.proceed()
|
||||
if (result is ErrorResult && result.throwable.shouldRestore()) {
|
||||
request.tags.tag<Manga>()?.let {
|
||||
request.extras[mangaKey]?.let {
|
||||
if (restoreManga(it)) {
|
||||
return chain.proceed(request.newBuilder().build())
|
||||
return chain.withRequest(request.newBuilder().build()).proceed()
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
}
|
||||
request.tags.tag<Bookmark>()?.let {
|
||||
request.extras[bookmarkKey]?.let {
|
||||
if (restoreBookmark(it)) {
|
||||
return chain.proceed(request.newBuilder().build())
|
||||
return chain.withRequest(request.newBuilder().build()).proceed()
|
||||
} else {
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@ import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.local.data.isFileUri
|
||||
import org.koitharu.kotatsu.local.data.isZipUri
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package org.koitharu.kotatsu.reader.domain
|
||||
|
||||
import android.content.ContentResolver.MimeTypeInfo
|
||||
import android.content.Context
|
||||
import android.graphics.Rect
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.annotation.AnyThread
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.collection.set
|
||||
@@ -29,6 +27,8 @@ import kotlinx.coroutines.sync.withPermit
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okio.use
|
||||
import org.jetbrains.annotations.Blocking
|
||||
import org.koitharu.kotatsu.core.image.BitmapDecoderCompat
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.core.network.MangaHttpClient
|
||||
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
|
||||
@@ -42,26 +42,25 @@ import org.koitharu.kotatsu.core.util.ext.cancelChildrenAndJoin
|
||||
import org.koitharu.kotatsu.core.util.ext.compressToPNG
|
||||
import org.koitharu.kotatsu.core.util.ext.ensureRamAtLeast
|
||||
import org.koitharu.kotatsu.core.util.ext.ensureSuccess
|
||||
import org.koitharu.kotatsu.core.util.ext.exists
|
||||
import org.koitharu.kotatsu.core.util.ext.getCompletionResultOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isNotEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
|
||||
import org.koitharu.kotatsu.core.util.ext.isTargetNotEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.mimeType
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.ramAvailable
|
||||
import org.koitharu.kotatsu.core.util.ext.use
|
||||
import org.koitharu.kotatsu.core.util.ext.withProgress
|
||||
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
|
||||
import org.koitharu.kotatsu.local.data.PagesCache
|
||||
import org.koitharu.kotatsu.local.data.isFileUri
|
||||
import org.koitharu.kotatsu.local.data.isZipUri
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.mimeType
|
||||
import org.koitharu.kotatsu.parsers.util.requireBody
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.core.image.BitmapDecoderCompat
|
||||
import org.koitharu.kotatsu.core.util.ext.mimeType
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import java.io.File
|
||||
import java.util.LinkedList
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import java.util.zip.ZipFile
|
||||
@@ -276,5 +275,28 @@ class PageLoader @Inject constructor(
|
||||
.cacheControl(CommonHeaders.CACHE_CONTROL_NO_STORE)
|
||||
.tag(MangaSource::class.java, mangaSource)
|
||||
.build()
|
||||
|
||||
|
||||
@Blocking
|
||||
private fun Uri.exists(): Boolean = when {
|
||||
isFileUri() -> toFile().exists()
|
||||
isZipUri() -> {
|
||||
val file = File(requireNotNull(schemeSpecificPart))
|
||||
file.exists() && ZipFile(file).use { it.getEntry(fragment) != null }
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
|
||||
@Blocking
|
||||
private fun Uri.isTargetNotEmpty(): Boolean = when {
|
||||
isFileUri() -> toFile().isNotEmpty()
|
||||
isZipUri() -> {
|
||||
val file = File(requireNotNull(schemeSpecificPart))
|
||||
file.exists() && ZipFile(file).use { (it.getEntry(fragment)?.size ?: 0L) != 0L }
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.webkit.MimeTypeMap
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.core.net.toFile
|
||||
import androidx.core.net.toUri
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -12,11 +13,17 @@ import kotlinx.coroutines.runInterruptible
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okio.FileSystem
|
||||
import okio.IOException
|
||||
import okio.Path.Companion.toPath
|
||||
import okio.Source
|
||||
import okio.buffer
|
||||
import okio.openZip
|
||||
import okio.sink
|
||||
import okio.source
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.core.util.ext.isFileUri
|
||||
import org.koitharu.kotatsu.core.util.ext.isZipUri
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
@@ -50,7 +57,7 @@ class PageSaveHelper @Inject constructor(
|
||||
runInterruptible(Dispatchers.IO) {
|
||||
contentResolver.openOutputStream(destination)?.sink()?.buffer()
|
||||
}?.use { output ->
|
||||
pageUri.source().use { input ->
|
||||
getSource(pageUri).use { input ->
|
||||
output.writeAllCancellable(input)
|
||||
}
|
||||
} ?: throw IOException("Output stream is null")
|
||||
@@ -68,6 +75,14 @@ class PageSaveHelper @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSource(uri: Uri): Source = when {
|
||||
uri.isFileUri() -> uri.toFile().source()
|
||||
uri.isZipUri() -> FileSystem.SYSTEM.openZip(uri.schemeSpecificPart.toPath())
|
||||
.source(requireNotNull(uri.fragment).toPath())
|
||||
|
||||
else -> throw IllegalArgumentException("Bad uri $uri: unsupported scheme")
|
||||
}
|
||||
|
||||
private suspend fun pickFileUri(saveLauncher: ActivityResultLauncher<String>, proposedName: String): Uri {
|
||||
val defaultUri = settings.getPagesSaveDir(context)?.uri?.buildUpon()?.appendPath(proposedName)?.toString()
|
||||
return withContext(Dispatchers.Main) {
|
||||
|
||||
@@ -12,10 +12,12 @@ import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import coil.size.Scale
|
||||
import coil.size.ViewSizeResolver
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.bitmapConfig
|
||||
import coil3.request.error
|
||||
import coil3.size.Scale
|
||||
import coil3.size.ViewSizeResolver
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.LabelFormatter
|
||||
import com.google.android.material.slider.Slider
|
||||
@@ -27,6 +29,7 @@ import org.koitharu.kotatsu.core.ui.BaseActivity
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.indicator
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||
import org.koitharu.kotatsu.core.util.ext.setChecked
|
||||
@@ -139,7 +142,7 @@ class ColorFilterConfigActivity :
|
||||
.data(data)
|
||||
.scale(Scale.FILL)
|
||||
.decodeRegion()
|
||||
.tag(page.source)
|
||||
.mangaSourceExtra(page.source)
|
||||
.bitmapConfig(if (viewModel.is32BitColorsEnabled) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565)
|
||||
.indicator(listOf(viewBinding.progressBefore, viewBinding.progressAfter))
|
||||
.error(R.drawable.ic_error_placeholder)
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.koitharu.kotatsu.reader.ui.colorfilter
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.widget.ImageView
|
||||
import coil.target.ImageViewTarget
|
||||
import coil3.target.ImageViewTarget
|
||||
|
||||
class DoubleViewTarget(
|
||||
primaryView: ImageView,
|
||||
|
||||
@@ -7,7 +7,10 @@ import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.AsyncListDiffer
|
||||
import androidx.recyclerview.widget.RecyclerView.NO_ID
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.list.ui.adapter.ListItemType
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.allowRgb565
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders
|
||||
|
||||
@@ -11,7 +11,7 @@ import androidx.activity.viewModels
|
||||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.koitharu.kotatsu.search.ui.multi.adapter
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
|
||||
|
||||
@@ -4,7 +4,7 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.RecyclerView.RecycledViewPool
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.core.graphics.Insets
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.os.VoiceInputContract
|
||||
@@ -74,11 +74,12 @@ class SearchSuggestionFragment :
|
||||
|
||||
companion object {
|
||||
|
||||
@Deprecated("",
|
||||
@Deprecated(
|
||||
"",
|
||||
ReplaceWith(
|
||||
"SearchSuggestionFragment()",
|
||||
"org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment"
|
||||
)
|
||||
"org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionFragment",
|
||||
),
|
||||
)
|
||||
fun newInstance() = SearchSuggestionFragment()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package org.koitharu.kotatsu.search.ui.suggestion.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.BaseListAdapter
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.koitharu.kotatsu.search.ui.suggestion.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.getSummary
|
||||
@@ -10,8 +13,8 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||
import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable
|
||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceBinding
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
|
||||
@@ -40,7 +43,7 @@ fun searchSuggestionSourceAD(
|
||||
fallback(fallbackIcon)
|
||||
placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name))
|
||||
error(fallbackIcon)
|
||||
source(item.source)
|
||||
mangaSourceExtra(item.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package org.koitharu.kotatsu.search.ui.suggestion.adapter
|
||||
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil.ImageLoader
|
||||
import coil3.ImageLoader
|
||||
import coil3.request.error
|
||||
import coil3.request.fallback
|
||||
import coil3.request.placeholder
|
||||
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.model.getSummary
|
||||
@@ -10,8 +13,8 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri
|
||||
import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable
|
||||
import org.koitharu.kotatsu.core.ui.image.FaviconDrawable
|
||||
import org.koitharu.kotatsu.core.util.ext.enqueueWith
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.newImageRequest
|
||||
import org.koitharu.kotatsu.core.util.ext.source
|
||||
import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceTipBinding
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener
|
||||
import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem
|
||||
@@ -37,7 +40,7 @@ fun searchSuggestionSourceTipAD(
|
||||
fallback(fallbackIcon)
|
||||
placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name))
|
||||
error(fallbackIcon)
|
||||
source(item.source)
|
||||
mangaSourceExtra(item.source)
|
||||
enqueueWith(coil)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user