diff --git a/app/build.gradle b/app/build.gradle index 4522ba6d7..61a46bf01 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,8 +15,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdkVersion 21 targetSdkVersion 32 - versionCode 420 - versionName '3.5.0' + versionCode 421 + versionName '3.4.9' generatedDensities = [] testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -84,7 +84,7 @@ afterEvaluate { } } dependencies { - implementation('com.github.KotatsuApp:kotatsu-parsers:e2308214a7') { + implementation('com.github.KotatsuApp:kotatsu-parsers:8709c3dd0c') { exclude group: 'org.json', module: 'json' } diff --git a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt index 9bd4ef5cf..22c2319bf 100644 --- a/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt +++ b/app/src/main/java/org/koitharu/kotatsu/core/model/MangaSource.kt @@ -1,10 +1,18 @@ package org.koitharu.kotatsu.core.model +import java.util.* import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.toTitleCase -import java.util.* fun MangaSource.getLocaleTitle(): String? { val lc = Locale(locale ?: return null) return lc.getDisplayLanguage(lc).toTitleCase(lc) +} + +@Suppress("FunctionName") +fun MangaSource(name: String): MangaSource? { + MangaSource.values().forEach { + if (it.name == name) return it + } + return null } \ No newline at end of file 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 cc965d679..6275a49f1 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 @@ -45,6 +45,7 @@ import org.koitharu.kotatsu.search.ui.MangaListActivity import org.koitharu.kotatsu.search.ui.SearchActivity import org.koitharu.kotatsu.utils.FileSize import org.koitharu.kotatsu.utils.ext.* +import org.koitharu.kotatsu.utils.image.CoverSizeResolver @AndroidEntryPoint class DetailsFragment : @@ -291,8 +292,10 @@ class DetailsFragment : override fun onWindowInsetsChanged(insets: Insets) { binding.root.updatePadding( - bottom = ((activity as? NoModalBottomSheetOwner)?.bsHeader?.measureHeight() - ?.plus(insets.bottom)?.plus(resources.resolveDp(16))) + bottom = ( + (activity as? NoModalBottomSheetOwner)?.bsHeader?.measureHeight() + ?.plus(insets.bottom)?.plus(resources.resolveDp(16)) + ) ?: insets.bottom, ) } @@ -319,16 +322,22 @@ class DetailsFragment : } val request = ImageRequest.Builder(context ?: return) .target(binding.imageViewCover) - .placeholder(R.drawable.ic_placeholder) - .fallback(R.drawable.ic_placeholder) - .error(R.drawable.ic_error_placeholder) + .size(CoverSizeResolver(binding.imageViewCover)) .data(imageUrl) .crossfade(context) .referer(manga.publicUrl) .lifecycle(viewLifecycleOwner) - lastResult?.drawable?.let { - request.fallback(it) - } ?: request.fallback(R.drawable.ic_placeholder) + .placeholderMemoryCacheKey(manga.coverUrl) + val previousDrawable = lastResult?.drawable + if (previousDrawable != null) { + request.fallback(previousDrawable) + .placeholder(previousDrawable) + .error(previousDrawable) + } else { + request.fallback(R.drawable.ic_placeholder) + .placeholder(R.drawable.ic_placeholder) + .error(R.drawable.ic_error_placeholder) + } request.enqueueWith(coil) } } diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index b28863d9b..e83fd1ae7 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -1,6 +1,10 @@ package org.koitharu.kotatsu.details.ui import android.text.Html +import android.text.SpannableString +import android.text.Spanned +import android.text.style.ForegroundColorSpan +import androidx.core.text.getSpans import androidx.core.text.parseAsHtml import androidx.lifecycle.LiveData import androidx.lifecycle.asFlow @@ -110,8 +114,8 @@ class DetailsViewModel @AssistedInject constructor( if (description.isNullOrEmpty()) { emit(null) } else { - emit(description.parseAsHtml()) - emit(description.parseAsHtml(imageGetter = imageGetter)) + emit(description.parseAsHtml().filterSpans()) + emit(description.parseAsHtml(imageGetter = imageGetter).filterSpans()) } }.asLiveDataDistinct(viewModelScope.coroutineContext + Dispatchers.Default, null) @@ -265,6 +269,15 @@ class DetailsViewModel @AssistedInject constructor( } } + private fun Spanned.filterSpans(): CharSequence { + val spannable = SpannableString.valueOf(this) + val spans = spannable.getSpans() + for (span in spans) { + spannable.removeSpan(span) + } + return spannable.trim() + } + @AssistedFactory interface Factory { 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 cc6191860..7484c6166 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 @@ -16,6 +16,7 @@ import org.koitharu.kotatsu.utils.ext.disposeImageRequest import org.koitharu.kotatsu.utils.ext.enqueueWith import org.koitharu.kotatsu.utils.ext.newImageRequest import org.koitharu.kotatsu.utils.ext.referer +import org.koitharu.kotatsu.utils.image.CoverSizeResolver fun mangaGridItemAD( coil: ImageLoader, @@ -40,6 +41,7 @@ fun mangaGridItemAD( binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads) binding.imageViewCover.newImageRequest(item.coverUrl)?.run { referer(item.manga.publicUrl) + size(CoverSizeResolver(binding.imageViewCover)) placeholder(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder) error(R.drawable.ic_error_placeholder) 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 15b8dd6ba..24e0a5e4e 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 @@ -12,6 +12,7 @@ import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.MangaListDetailedModel import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.utils.ext.* +import org.koitharu.kotatsu.utils.image.CoverSizeResolver fun mangaListDetailedItemAD( coil: ImageLoader, @@ -35,6 +36,7 @@ fun mangaListDetailedItemAD( binding.progressView.setPercent(item.progress, MangaListAdapter.PAYLOAD_PROGRESS in payloads) binding.imageViewCover.newImageRequest(item.coverUrl)?.run { referer(item.manga.publicUrl) + size(CoverSizeResolver(binding.imageViewCover)) placeholder(R.drawable.ic_placeholder) fallback(R.drawable.ic_placeholder) error(R.drawable.ic_error_placeholder) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt index 30bda62a0..d71fb079f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/onboard/OnboardViewModel.kt @@ -6,9 +6,11 @@ import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel import java.util.* import javax.inject.Inject +import kotlin.Comparator import org.koitharu.kotatsu.base.ui.BaseViewModel +import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.prefs.AppSettings -import org.koitharu.kotatsu.parsers.model.MangaSource +import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.settings.onboard.model.SourceLocale @@ -30,7 +32,7 @@ class OnboardViewModel @Inject constructor( init { if (settings.isSourcesSelected) { - selectedLocales.removeAll(settings.hiddenSources.mapToSet { x -> MangaSource.valueOf(x).locale }) + selectedLocales.removeAll(settings.hiddenSources.mapNotNullToSet { x -> MangaSource(x)?.locale }) } else { val deviceLocales = LocaleListCompat.getDefault().mapToSet { x -> x.language diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/CoverSizeResolver.kt b/app/src/main/java/org/koitharu/kotatsu/utils/image/CoverSizeResolver.kt new file mode 100644 index 000000000..69f61133f --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/utils/image/CoverSizeResolver.kt @@ -0,0 +1,83 @@ +package org.koitharu.kotatsu.utils.image + +import android.view.View +import android.view.View.OnLayoutChangeListener +import android.view.ViewGroup +import android.widget.ImageView +import coil.size.Dimension +import coil.size.Size +import coil.size.SizeResolver +import kotlin.coroutines.resume +import kotlin.math.roundToInt +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.suspendCancellableCoroutine + +private const val ASPECT_RATIO_HEIGHT = 18f +private const val ASPECT_RATIO_WIDTH = 13f + +class CoverSizeResolver( + private val imageView: ImageView, +) : SizeResolver { + + override suspend fun size(): Size { + getSize()?.let { return it } + return suspendCancellableCoroutine { cont -> + val layoutListener = LayoutListener(cont) + imageView.addOnLayoutChangeListener(layoutListener) + cont.invokeOnCancellation { + imageView.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) + if (width == null && height == null) { + return null + } + if (height == null && width != null) { + height = Dimension((width.px * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).roundToInt()) + } else if (width == null && height != null) { + width = Dimension((height.px * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).roundToInt()) + } + return Size(checkNotNull(width), checkNotNull(height)) + } + + private fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension.Pixels? { + if (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) { + return null + } + val insetParamSize = paramSize - paddingSize + if (insetParamSize > 0) { + return Dimension(insetParamSize) + } + val insetViewSize = viewSize - paddingSize + if (insetViewSize > 0) { + return Dimension(insetViewSize) + } + return null + } + + private inner class LayoutListener( + private val continuation: CancellableContinuation, + ) : OnLayoutChangeListener { + + override fun onLayoutChange( + v: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + oldLeft: Int, + oldTop: Int, + oldRight: Int, + oldBottom: Int, + ) { + val size = getSize() ?: return + v.removeOnLayoutChangeListener(this) + continuation.resume(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt b/app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt index 2640060b7..b44281f38 100644 --- a/app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt +++ b/app/src/main/java/org/koitharu/kotatsu/utils/image/TrimTransformation.kt @@ -1,11 +1,15 @@ package org.koitharu.kotatsu.utils.image import android.graphics.Bitmap -import androidx.core.graphics.get +import androidx.annotation.ColorInt +import androidx.core.graphics.* import coil.size.Size import coil.transform.Transformation +import kotlin.math.abs -class TrimTransformation : Transformation { +class TrimTransformation( + private val tolerance: Int = 20, +) : Transformation { override val cacheKey: String = javaClass.name @@ -20,7 +24,7 @@ class TrimTransformation : Transformation { var isColBlank = true val prevColor = input[x, 0] for (y in 1 until input.height) { - if (input[x, y] != prevColor) { + if (!isColorTheSame(input[x, y], prevColor)) { isColBlank = false break } @@ -39,7 +43,7 @@ class TrimTransformation : Transformation { var isColBlank = true val prevColor = input[x, 0] for (y in 1 until input.height) { - if (input[x, y] != prevColor) { + if (!isColorTheSame(input[x, y], prevColor)) { isColBlank = false break } @@ -55,7 +59,7 @@ class TrimTransformation : Transformation { var isRowBlank = true val prevColor = input[0, y] for (x in 1 until input.width) { - if (input[x, y] != prevColor) { + if (!isColorTheSame(input[x, y], prevColor)) { isRowBlank = false break } @@ -71,7 +75,7 @@ class TrimTransformation : Transformation { var isRowBlank = true val prevColor = input[0, y] for (x in 1 until input.width) { - if (input[x, y] != prevColor) { + if (!isColorTheSame(input[x, y], prevColor)) { isRowBlank = false break } @@ -93,4 +97,11 @@ class TrimTransformation : Transformation { override fun equals(other: Any?) = other is TrimTransformation override fun hashCode() = javaClass.hashCode() + + private fun isColorTheSame(@ColorInt a: Int, @ColorInt b: Int): Boolean { + return abs(a.red - b.red) <= tolerance && + abs(a.green - b.green) <= tolerance && + abs(a.blue - b.blue) <= tolerance && + abs(a.alpha - b.alpha) <= tolerance + } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed.xml b/app/src/main/res/layout/item_feed.xml index 6ec6681d1..54302aa1b 100644 --- a/app/src/main/res/layout/item_feed.xml +++ b/app/src/main/res/layout/item_feed.xml @@ -12,10 +12,9 @@