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