diff --git a/app/build.gradle b/app/build.gradle index ff599eb34..b2d55cf7c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -52,6 +52,7 @@ android { '-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi', '-opt-in=kotlinx.coroutines.FlowPreview', '-opt-in=kotlin.contracts.ExperimentalContracts', + '-opt-in=coil.annotation.ExperimentalCoilApi', ] } lint { @@ -86,7 +87,7 @@ dependencies { implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' implementation 'androidx.preference:preference-ktx:1.2.0' implementation 'androidx.work:work-runtime-ktx:2.7.1' - implementation 'com.google.android.material:material:1.6.0-rc01' + implementation 'com.google.android.material:material:1.7.0-alpha01' //noinspection LifecycleAnnotationProcessorWithJava8 kapt 'androidx.lifecycle:lifecycle-compiler:2.4.1' @@ -101,11 +102,11 @@ dependencies { implementation 'com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:4.3.2' implementation 'io.insert-koin:koin-android:3.1.6' - implementation 'io.coil-kt:coil-base:1.4.0' + implementation 'io.coil-kt:coil-base:2.0.0-rc03' implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' implementation 'com.github.solkin:disk-lru-cache:1.4' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' testImplementation 'junit:junit:4.13.2' testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1' diff --git a/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsRepository.kt b/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsRepository.kt index 98ebe3a6a..9e8b18a52 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/os/ShortcutsRepository.kt @@ -5,12 +5,12 @@ import android.content.Context import android.content.pm.ShortcutManager import android.media.ThumbnailUtils import android.os.Build +import android.util.Size import androidx.core.content.pm.ShortcutInfoCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.graphics.drawable.IconCompat import coil.ImageLoader import coil.request.ImageRequest -import coil.size.PixelSize import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.koitharu.kotatsu.R @@ -54,7 +54,7 @@ class ShortcutsRepository( val bmp = coil.execute( ImageRequest.Builder(context) .data(manga.coverUrl) - .size(iconSize) + .size(iconSize.width, iconSize.height) .build() ).requireBitmap() ThumbnailUtils.extractThumbnail(bmp, iconSize.width, iconSize.height, 0) @@ -74,14 +74,14 @@ class ShortcutsRepository( ) } - private fun getIconSize(context: Context): PixelSize { + private fun getIconSize(context: Context): Size { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { (context.getSystemService(Context.SHORTCUT_SERVICE) as ShortcutManager).let { - PixelSize(it.iconMaxWidth, it.iconMaxHeight) + Size(it.iconMaxWidth, it.iconMaxHeight) } } else { (context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).launcherLargeIconSize.let { - PixelSize(it, it) + Size(it, it) } } } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt b/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt index 4a386d3f8..ba5412a50 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/parser/FaviconMapper.kt @@ -2,17 +2,19 @@ package org.koitharu.kotatsu.core.parser import android.net.Uri import coil.map.Mapper +import coil.request.Options import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import org.koitharu.kotatsu.parsers.model.MangaSource -class FaviconMapper() : Mapper { +class FaviconMapper : Mapper { - override fun map(data: Uri): HttpUrl { + override fun map(data: Uri, options: Options): HttpUrl? { + if (data.scheme != "favicon") { + return null + } val mangaSource = MangaSource.valueOf(data.schemeSpecificPart) val repo = MangaRepository(mangaSource) as RemoteMangaRepository return repo.getFaviconUrl().toHttpUrl() } - - override fun handles(data: Uri) = data.scheme == "favicon" } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt index 26ccbf44a..54529b37c 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/ui/uiModule.kt @@ -2,11 +2,13 @@ package org.koitharu.kotatsu.core.ui import coil.ComponentRegistry import coil.ImageLoader -import coil.util.CoilUtils +import coil.disk.DiskCache +import kotlinx.coroutines.Dispatchers import okhttp3.OkHttpClient import org.koin.android.ext.koin.androidContext import org.koin.dsl.module import org.koitharu.kotatsu.core.parser.FaviconMapper +import org.koitharu.kotatsu.local.data.CacheDir import org.koitharu.kotatsu.local.data.CbzFetcher val uiModule @@ -14,15 +16,23 @@ val uiModule single { val httpClientFactory = { get().newBuilder() - .cache(CoilUtils.createDefaultCache(androidContext())) + .cache(null) + .build() + } + val diskCacheFactory = { + val context = androidContext() + val rootDir = context.externalCacheDir ?: context.cacheDir + DiskCache.Builder() + .directory(rootDir.resolve(CacheDir.THUMBS.dir)) .build() } ImageLoader.Builder(androidContext()) .okHttpClient(httpClientFactory) - .launchInterceptorChainOnMainThread(false) - .componentRegistry( + .interceptorDispatcher(Dispatchers.Default) + .diskCache(diskCacheFactory) + .components( ComponentRegistry.Builder() - .add(CbzFetcher()) + .add(CbzFetcher.Factory()) .add(FaviconMapper()) .build() ).build() diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt index df4fc2de9..99c7319e3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsFragment.kt @@ -283,7 +283,7 @@ class DetailsFragment : .target(binding.imageViewCover) if (currentCover != null) { request.data(manga.largeCoverUrl ?: return) - .placeholderMemoryCacheKey(CoilUtils.metadata(binding.imageViewCover)?.memoryCacheKey) + .placeholderMemoryCacheKey(CoilUtils.result(binding.imageViewCover)?.request?.memoryCacheKey) .fallback(currentCover) } else { request.crossfade(true) diff --git a/app/src/main/java/org/koitharu/kotatsu/image/ui/ImageActivity.kt b/app/src/main/java/org/koitharu/kotatsu/image/ui/ImageActivity.kt index 8e674701a..28f58887b 100644 --- a/app/src/main/java/org/koitharu/kotatsu/image/ui/ImageActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/image/ui/ImageActivity.kt @@ -6,7 +6,6 @@ import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import android.view.ViewGroup -import androidx.appcompat.app.AppCompatDelegate import androidx.core.graphics.Insets import androidx.core.graphics.drawable.toBitmap import androidx.core.view.updateLayoutParams @@ -14,7 +13,7 @@ import androidx.core.view.updatePadding import coil.ImageLoader import coil.request.CachePolicy import coil.request.ImageRequest -import coil.target.PoolableViewTarget +import coil.target.ViewTarget import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import org.koin.android.ext.android.inject @@ -61,16 +60,12 @@ class ImageActivity : BaseActivity() { private class SsivTarget( override val view: SubsamplingScaleImageView, - ) : PoolableViewTarget { - - override fun onStart(placeholder: Drawable?) = setDrawable(placeholder) + ) : ViewTarget { override fun onError(error: Drawable?) = setDrawable(error) override fun onSuccess(result: Drawable) = setDrawable(result) - override fun onClear() = setDrawable(null) - override fun equals(other: Any?): Boolean { return (this === other) || (other is SsivTarget && view == other.view) } diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt index ced1697b0..bc716c935 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt @@ -53,7 +53,7 @@ fun mangaGridItemAD( badge = null imageRequest?.dispose() imageRequest = null - CoilUtils.clear(binding.imageViewCover) + CoilUtils.dispose(binding.imageViewCover) binding.imageViewCover.setImageDrawable(null) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt index 10e9a473d..60420df4d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt @@ -57,7 +57,7 @@ fun mangaListDetailedItemAD( badge = null imageRequest?.dispose() imageRequest = null - CoilUtils.clear(binding.imageViewCover) + CoilUtils.dispose(binding.imageViewCover) binding.imageViewCover.setImageDrawable(null) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt index 18696de6b..d709d954d 100644 --- a/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt @@ -55,7 +55,7 @@ fun mangaListItemAD( badge = null imageRequest?.dispose() imageRequest = null - CoilUtils.clear(binding.imageViewCover) + CoilUtils.dispose(binding.imageViewCover) binding.imageViewCover.setImageDrawable(null) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt index ff04a2eff..c773a05d3 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/CbzFetcher.kt @@ -2,41 +2,52 @@ package org.koitharu.kotatsu.local.data import android.net.Uri import android.webkit.MimeTypeMap -import coil.bitmap.BitmapPool +import coil.ImageLoader import coil.decode.DataSource -import coil.decode.Options -import coil.fetch.FetchResult +import coil.decode.ImageSource import coil.fetch.Fetcher import coil.fetch.SourceResult -import coil.size.Size -import java.util.zip.ZipFile +import coil.request.Options import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible import okio.buffer import okio.source +import java.util.zip.ZipFile -class CbzFetcher : Fetcher { +class CbzFetcher( + private val uri: Uri, + private val options: Options +) : Fetcher { - override suspend fun fetch( - pool: BitmapPool, - data: Uri, - size: Size, - options: Options, - ): FetchResult = runInterruptible(Dispatchers.IO) { - val zip = ZipFile(data.schemeSpecificPart) - val entry = zip.getEntry(data.fragment) + override suspend fun fetch() = runInterruptible(Dispatchers.IO) { + val zip = ZipFile(uri.schemeSpecificPart) + val entry = zip.getEntry(uri.fragment) val ext = MimeTypeMap.getFileExtensionFromUrl(entry.name) + val bufferedSource = ExtraCloseableBufferedSource( + zip.getInputStream(entry).source().buffer(), + zip, + ) SourceResult( - source = ExtraCloseableBufferedSource( - zip.getInputStream(entry).source().buffer(), - zip, + source = ImageSource( + source = bufferedSource, + context = options.context, + metadata = CbzMetadata(uri), ), mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext), - dataSource = DataSource.DISK + dataSource = DataSource.DISK, ) } - override fun key(data: Uri) = data.toString() + class Factory : Fetcher.Factory { - override fun handles(data: Uri) = data.scheme == "cbz" + 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() } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt index 201a4f4ae..8cddae963 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/ui/thumbnails/adapter/PageThumbnailAD.kt @@ -3,7 +3,7 @@ package org.koitharu.kotatsu.reader.ui.thumbnails.adapter import android.graphics.drawable.Drawable import coil.ImageLoader import coil.request.ImageRequest -import coil.size.PixelSize +import coil.size.Size import com.google.android.material.R as materialR import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import kotlinx.coroutines.* @@ -27,7 +27,7 @@ fun pageThumbnailAD( var job: Job? = null val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width) - val thumbSize = PixelSize( + val thumbSize = Size( width = gridWidth, height = (gridWidth * 13f / 18f).toInt() ) diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/progress/ImageRequestIndicatorListener.kt b/app/src/main/java/org/koitharu/kotatsu/utils/progress/ImageRequestIndicatorListener.kt index eb38e1d32..317c77c5f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/progress/ImageRequestIndicatorListener.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/progress/ImageRequestIndicatorListener.kt @@ -1,7 +1,8 @@ package org.koitharu.kotatsu.utils.progress +import coil.request.ErrorResult import coil.request.ImageRequest -import coil.request.ImageResult +import coil.request.SuccessResult import com.google.android.material.progressindicator.BaseProgressIndicator class ImageRequestIndicatorListener( @@ -10,9 +11,9 @@ class ImageRequestIndicatorListener( override fun onCancel(request: ImageRequest) = indicator.hide() - override fun onError(request: ImageRequest, throwable: Throwable) = indicator.hide() + override fun onError(request: ImageRequest, result: ErrorResult) = indicator.hide() override fun onStart(request: ImageRequest) = indicator.show() - override fun onSuccess(request: ImageRequest, metadata: ImageResult.Metadata) = indicator.hide() + override fun onSuccess(request: ImageRequest, result: SuccessResult) = indicator.hide() } \ No newline at end of file