diff --git a/app/build.gradle b/app/build.gradle index 3994cb7b9..86b3aff91 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,8 +16,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 34 - versionCode = 636 - versionName = '7.0-b2' + versionCode = 637 + versionName = '7.0-b3' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt index e4240324d..0e7833217 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/AppModule.kt @@ -43,6 +43,7 @@ import org.koitharu.kotatsu.core.util.AcraScreenLogger import org.koitharu.kotatsu.core.util.ext.connectivityManager import org.koitharu.kotatsu.core.util.ext.isLowRamDevice import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher +import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer import org.koitharu.kotatsu.local.data.CacheDir import org.koitharu.kotatsu.local.data.CbzFetcher import org.koitharu.kotatsu.local.data.LocalStorageChanges @@ -101,8 +102,8 @@ interface AppModule { return ImageLoader.Builder(context) .okHttpClient(okHttpClient.newBuilder().cache(null).build()) .interceptorDispatcher(Dispatchers.Default) - .fetcherDispatcher(Dispatchers.IO) - .decoderDispatcher(Dispatchers.Default) + .fetcherDispatcher(Dispatchers.Default) + .decoderDispatcher(Dispatchers.IO) .transformationDispatcher(Dispatchers.Default) .diskCache(diskCacheFactory) .logger(if (BuildConfig.DEBUG) DebugLogger() else null) @@ -113,6 +114,7 @@ interface AppModule { .add(SvgDecoder.Factory()) .add(CbzFetcher.Factory()) .add(FaviconFetcher.Factory(context, okHttpClient, mangaRepositoryFactory)) + .add(MangaPageKeyer()) .add(pageFetcherFactory) .add(imageProxyInterceptor) .add(coverRestoreInterceptor) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt index df5f6598d..01d0d7c77 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedPlaceholderDrawable.kt @@ -7,23 +7,23 @@ import android.graphics.ColorFilter import android.graphics.PixelFormat import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable -import android.view.animation.AccelerateDecelerateInterpolator import androidx.core.graphics.ColorUtils import androidx.interpolator.view.animation.FastOutSlowInInterpolator import com.google.android.material.animation.ArgbEvaluatorCompat -import org.koitharu.kotatsu.core.util.ext.animatorDurationScale +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getThemeColor import kotlin.math.abs import com.google.android.material.R as materialR class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, TimeAnimator.TimeListener { - private val colorLow = context.getThemeColor(materialR.attr.colorBackgroundFloating) - private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainer) + private val colorLow = context.getThemeColor(materialR.attr.colorSurfaceContainerLowest) + private val colorHigh = context.getThemeColor(materialR.attr.colorSurfaceContainerHighest) private var currentColor: Int = colorLow private var alpha: Int = 255 private val interpolator = FastOutSlowInInterpolator() - private val period = 2000 * context.animatorDurationScale + private val period = context.getAnimationDuration(R.integer.config_longAnimTime) * 2 private val timeAnimator = TimeAnimator() init { @@ -40,23 +40,21 @@ class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, Ti } override fun setAlpha(alpha: Int) { - this.alpha = alpha + // this.alpha = alpha FIXME coil's crossfade } - override fun setColorFilter(colorFilter: ColorFilter?) { - throw UnsupportedOperationException("ColorFilter is not supported by PlaceholderDrawable") - } + override fun setColorFilter(colorFilter: ColorFilter?) = Unit @Deprecated("Deprecated in Java") - override fun getOpacity(): Int = PixelFormat.OPAQUE + override fun getOpacity(): Int = if (alpha == 255) PixelFormat.OPAQUE else PixelFormat.TRANSLUCENT override fun getAlpha(): Int = alpha override fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) { - if (callback != null) { + callback?.also { updateColor() - invalidateSelf() - } + it.invalidateDrawable(this) + } ?: stop() } override fun start() { @@ -64,7 +62,7 @@ class AnimatedPlaceholderDrawable(context: Context) : Drawable(), Animatable, Ti } override fun stop() { - timeAnimator.cancel() + timeAnimator.end() } override fun isRunning(): Boolean = timeAnimator.isStarted diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt index 43d662759..b7d7bf63e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import android.widget.ImageView import coil.size.Dimension import coil.size.Size -import coil.size.SizeResolver +import coil.size.ViewSizeResolver import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @@ -16,24 +16,24 @@ private const val ASPECT_RATIO_HEIGHT = 18f private const val ASPECT_RATIO_WIDTH = 13f class CoverSizeResolver( - private val imageView: ImageView, -) : SizeResolver { + override val view: ImageView, +) : ViewSizeResolver { override suspend fun size(): Size { getSize()?.let { return it } return suspendCancellableCoroutine { cont -> val layoutListener = LayoutListener(cont) - imageView.addOnLayoutChangeListener(layoutListener) + view.addOnLayoutChangeListener(layoutListener) cont.invokeOnCancellation { - imageView.removeOnLayoutChangeListener(layoutListener) + view.removeOnLayoutChangeListener(layoutListener) } } } private fun getSize(): Size? { - val lp = imageView.layoutParams - var width = getDimension(lp.width, imageView.width, imageView.paddingLeft + imageView.paddingRight) - var height = getDimension(lp.height, imageView.height, imageView.paddingTop + imageView.paddingBottom) + val lp = view.layoutParams + var width = getDimension(lp.width, view.width, view.paddingLeft + view.paddingRight) + var height = getDimension(lp.height, view.height, view.paddingTop + view.paddingBottom) if (width == null && height == null) { return null } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/BottomSheetNoHalfExpandedCallback.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/BottomSheetNoHalfExpandedCallback.kt new file mode 100644 index 000000000..0776c157f --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/util/BottomSheetNoHalfExpandedCallback.kt @@ -0,0 +1,21 @@ +package org.koitharu.kotatsu.core.ui.util + +import android.view.View +import androidx.coordinatorlayout.widget.CoordinatorLayout +import com.google.android.material.bottomsheet.BottomSheetBehavior + +class BottomSheetNoHalfExpandedCallback() : BottomSheetBehavior.BottomSheetCallback() { + + private var previousStableState = BottomSheetBehavior.STATE_COLLAPSED + + override fun onStateChanged(sheet: View, state: Int) { + if (state == BottomSheetBehavior.STATE_HALF_EXPANDED) { + val behavior = (sheet.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior as? BottomSheetBehavior<*> + behavior?.state = previousStableState + } else if (state == BottomSheetBehavior.STATE_EXPANDED || state == BottomSheetBehavior.STATE_COLLAPSED) { + previousStableState = state + } + } + + override fun onSlide(sheet: View, offset: Float) = Unit +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 1951887c4..0bb14f678 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -54,6 +54,7 @@ import org.koitharu.kotatsu.core.ui.image.ChipIconTarget import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.BottomSheetClollapseCallback +import org.koitharu.kotatsu.core.ui.util.BottomSheetNoHalfExpandedCallback import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.widgets.ChipsView @@ -155,6 +156,7 @@ class DetailsActivity : viewBinding.chipsTags.onChipClickListener = this TitleScrollCoordinator(viewBinding.textViewTitle).attach(viewBinding.scrollView) viewBinding.containerBottomSheet?.let { BottomSheetBehavior.from(it) }?.let { behavior -> + behavior.addBottomSheetCallback(BottomSheetNoHalfExpandedCallback()) onBackPressedDispatcher.addCallback(BottomSheetClollapseCallback(behavior)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt index 713c4eaf0..494b0305a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageFetcher.kt @@ -10,6 +10,7 @@ import coil.decode.ImageSource import coil.fetch.FetchResult import coil.fetch.Fetcher import coil.fetch.SourceResult +import coil.network.HttpException import coil.request.Options import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers @@ -44,15 +45,17 @@ class MangaPageFetcher( override suspend fun fetch(): FetchResult { val repo = mangaRepositoryFactory.create(page.source) val pageUrl = repo.getPageUrl(page) - pagesCache.get(pageUrl)?.let { file -> - return SourceResult( - source = ImageSource( - file = file.toOkioPath(), - metadata = MangaPageMetadata(page), - ), - mimeType = null, - dataSource = DataSource.DISK, - ) + if (options.diskCachePolicy.readEnabled) { + pagesCache.get(pageUrl)?.let { file -> + return SourceResult( + source = ImageSource( + file = file.toOkioPath(), + metadata = MangaPageMetadata(page), + ), + mimeType = null, + dataSource = DataSource.DISK, + ) + } } return loadPage(pageUrl) } @@ -91,8 +94,8 @@ class MangaPageFetcher( else -> { val request = PageLoader.createPageRequest(page, pageUrl) imageProxyInterceptor.interceptPageRequest(request, okHttpClient).use { response -> - check(response.isSuccessful) { - "Invalid response: ${response.code} ${response.message} at $pageUrl" + if (!response.isSuccessful) { + throw HttpException(response) } val body = checkNotNull(response.body) { "Null response" @@ -122,17 +125,15 @@ class MangaPageFetcher( private val imageProxyInterceptor: ImageProxyInterceptor, ) : Fetcher.Factory { - override fun create(data: MangaPage, options: Options, imageLoader: ImageLoader): Fetcher { - return MangaPageFetcher( - okHttpClient = okHttpClient, - pagesCache = pagesCache, - options = options, - page = data, - context = context, - mangaRepositoryFactory = mangaRepositoryFactory, - imageProxyInterceptor = imageProxyInterceptor, - ) - } + override fun create(data: MangaPage, options: Options, imageLoader: ImageLoader) = MangaPageFetcher( + okHttpClient = okHttpClient, + pagesCache = pagesCache, + options = options, + page = data, + context = context, + mangaRepositoryFactory = mangaRepositoryFactory, + imageProxyInterceptor = imageProxyInterceptor, + ) } class MangaPageMetadata(val page: MangaPage) : ImageSource.Metadata() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageKeyer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageKeyer.kt new file mode 100644 index 000000000..6895c6ad8 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/MangaPageKeyer.kt @@ -0,0 +1,10 @@ +package org.koitharu.kotatsu.details.ui.pager.pages + +import coil.key.Keyer +import coil.request.Options +import org.koitharu.kotatsu.parsers.model.MangaPage + +class MangaPageKeyer : Keyer { + + override fun key(data: MangaPage, options: Options) = data.url +} diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml index 7b2d78be4..e405948e9 100644 --- a/app/src/main/res/values/integers.xml +++ b/app/src/main/res/values/integers.xml @@ -1,5 +1,6 @@ + 1000 300 150 50