From ef691b1aed8a668bc82188b81628230d6a5f5bbd Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 18 Dec 2024 15:38:56 +0200 Subject: [PATCH 1/5] Update parsers --- app/build.gradle | 4 ++-- .../kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt | 5 +++-- .../kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt | 3 ++- .../org/koitharu/kotatsu/core/util/ext/ContentResolver.kt | 3 ++- .../kotatsu/details/ui/pager/pages/PageThumbnailAD.kt | 3 ++- .../org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt | 3 ++- .../org/koitharu/kotatsu/list/ui/adapter/BadgeADUtil.kt | 3 ++- .../reader/ui/colorfilter/ColorFilterConfigActivity.kt | 3 ++- .../kotatsu/scrobbling/common/data/ScrobblerStorage.kt | 3 ++- .../kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt | 3 ++- gradle/libs.versions.toml | 4 ++-- 11 files changed, 23 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e64fde895..a2dc359fe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 35 - versionCode = 694 - versionName = '7.7.2' + versionCode = 695 + versionName = '7.7.3' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index e677c4b13..25b220cae 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -30,6 +30,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.util.find import org.koitharu.kotatsu.parsers.util.mapNotNullToSet import org.koitharu.kotatsu.parsers.util.mapToSet +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import java.io.File import java.net.Proxy @@ -412,10 +413,10 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { get() = prefs.getString(KEY_PROXY_PORT, null)?.toIntOrNull() ?: 0 val proxyLogin: String? - get() = prefs.getString(KEY_PROXY_LOGIN, null)?.takeUnless { it.isEmpty() } + get() = prefs.getString(KEY_PROXY_LOGIN, null)?.nullIfEmpty() val proxyPassword: String? - get() = prefs.getString(KEY_PROXY_PASSWORD, null)?.takeUnless { it.isEmpty() } + get() = prefs.getString(KEY_PROXY_PASSWORD, null)?.nullIfEmpty() var localListOrder: SortOrder get() = prefs.getEnumValue(KEY_LOCAL_LIST_ORDER, SortOrder.NEWEST) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt index 8b38fb341..0f491099d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/SourceSettings.kt @@ -11,6 +11,7 @@ import org.koitharu.kotatsu.parsers.config.ConfigKey import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.SortOrder +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.settings.utils.validation.DomainValidator class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig { @@ -38,7 +39,7 @@ class SourceSettings(context: Context, source: MangaSource) : MangaSourceConfig is ConfigKey.ShowSuspiciousContent -> prefs.getBoolean(key.key, key.defaultValue) is ConfigKey.SplitByTranslations -> prefs.getBoolean(key.key, key.defaultValue) - is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.takeUnless(String::isEmpty) + is ConfigKey.PreferredImageServer -> prefs.getString(key.key, key.defaultValue)?.nullIfEmpty() } as T } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ContentResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ContentResolver.kt index 3f273a060..883df5b5a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ContentResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/ContentResolver.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.os.Build import android.os.storage.StorageManager import android.provider.DocumentsContract +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.parsers.util.removeSuffix import java.io.File import java.lang.reflect.Array as ArrayReflect @@ -80,7 +81,7 @@ private fun getVolumePathForAndroid11AndAbove(volumeId: String, context: Context private fun getVolumeIdFromTreeUri(treeUri: Uri): String? { val docId = DocumentsContract.getTreeDocumentId(treeUri) val split = docId.split(":".toRegex()) - return split.firstOrNull()?.takeUnless { it.isEmpty() } + return split.firstOrNull()?.nullIfEmpty() } private fun getDocumentPathFromTreeUri(treeUri: Uri): String? { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt index 494cbfdd7..71d2f26c6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt @@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.setTextColorAttr import org.koitharu.kotatsu.databinding.ItemPageThumbBinding import org.koitharu.kotatsu.list.ui.model.ListModel +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import com.google.android.material.R as materialR fun pageThumbnailAD( @@ -36,7 +37,7 @@ fun pageThumbnailAD( AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView) bind { - val data: Any = item.page.preview?.takeUnless { it.isEmpty() } ?: item.page.toMangaPage() + val data: Any = item.page.preview?.nullIfEmpty() ?: item.page.toMangaPage() binding.imageViewThumb.newImageRequest(lifecycleOwner, data)?.run { defaultPlaceholders(context) size(thumbSize) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt index d01f44e23..0d58c83a0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/filter/ui/FilterCoordinator.kt @@ -36,6 +36,7 @@ import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.model.YEAR_MIN import org.koitharu.kotatsu.parsers.util.ifZero +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy import org.koitharu.kotatsu.remotelist.ui.RemoteListFragment import org.koitharu.kotatsu.search.domain.MangaSearchRepository @@ -267,7 +268,7 @@ class FilterCoordinator @Inject constructor( } fun setQuery(value: String?) { - val newQuery = value?.trim()?.takeUnless { it.isEmpty() } + val newQuery = value?.trim()?.nullIfEmpty() currentListFilter.update { oldValue -> if (capabilities.isSearchWithFiltersSupported || newQuery == null) { oldValue.copy(query = newQuery) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/BadgeADUtil.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/BadgeADUtil.kt index a2e65e65b..de13d0b2b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/BadgeADUtil.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/BadgeADUtil.kt @@ -10,6 +10,7 @@ import com.google.android.material.badge.BadgeDrawable import com.google.android.material.badge.BadgeUtils import com.google.android.material.badge.ExperimentalBadgeUtils import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.parsers.util.nullIfEmpty @CheckResult fun View.bindBadge(badge: BadgeDrawable?, counter: Int): BadgeDrawable? { @@ -34,7 +35,7 @@ private fun View.bindBadgeImpl( if (counter > 0) { badgeDrawable.number = counter } else { - badgeDrawable.text = text?.takeUnless { it.isEmpty() } + badgeDrawable.text = text?.nullIfEmpty() } badgeDrawable.isVisible = true badgeDrawable.align(this) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt index c9444532a..b2e132758 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt @@ -38,6 +38,7 @@ import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.util.format +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import javax.inject.Inject import com.google.android.material.R as materialR @@ -137,7 +138,7 @@ class ColorFilterConfigActivity : } private fun loadPreview(page: MangaPage) { - val data: Any = page.preview?.takeUnless { it.isEmpty() } ?: page + val data: Any = page.preview?.nullIfEmpty() ?: page ImageRequest.Builder(this@ColorFilterConfigActivity) .data(data) .scale(Scale.FILL) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt index c5fc090a1..d4b15e415 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/data/ScrobblerStorage.kt @@ -3,6 +3,7 @@ package org.koitharu.kotatsu.scrobbling.common.data import android.content.Context import androidx.core.content.edit import org.jsoup.internal.StringUtil.StringJoiner +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser @@ -31,7 +32,7 @@ class ScrobblerStorage(context: Context, service: ScrobblerService) { ScrobblerUser( id = lines[0].toLong(), nickname = lines[1], - avatar = lines[2].takeUnless(String::isEmpty), + avatar = lines[2].nullIfEmpty(), service = ScrobblerService.valueOf(lines[3]), ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt index 9fe5b3f35..aac301e3b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/kitsu/data/KitsuInterceptor.kt @@ -7,6 +7,7 @@ import okhttp3.internal.closeQuietly import okio.IOException import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.parsers.util.mimeType +import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.parsers.util.parseHtml import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage @@ -34,7 +35,7 @@ class KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor { } if (response.mimeType?.toMediaTypeOrNull()?.subtype == SUBTYPE_HTML) { val message = runCatchingCancellable { - response.parseHtml().title().takeUnless { it.isEmpty() } + response.parseHtml().title().nullIfEmpty() }.onFailure { response.closeQuietly() }.getOrNull() ?: "Invalid response (${response.code})" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 07308cc66..20a612dee 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,10 +28,10 @@ leakcanary = "3.0-alpha-8" lifecycle = "2.8.7" markwon = "4.6.2" material = "1.12.0" -moshi = "1.15.1" +moshi = "1.15.2" okhttp = "4.12.0" okio = "3.9.1" -parsers = "fece09b781" +parsers = "f86d31f811" preference = "1.2.1" recyclerview = "1.3.2" room = "2.6.1" From 754ccc4197d4581b204644639d5ef1c43fad8f96 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 18 Dec 2024 16:26:49 +0200 Subject: [PATCH 2/5] Added url for NoDataReceivedException --- .../koitharu/kotatsu/core/exceptions/NoDataReceivedException.kt | 2 +- .../main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/NoDataReceivedException.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/NoDataReceivedException.kt index 3594ce18b..0c1890905 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/NoDataReceivedException.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/exceptions/NoDataReceivedException.kt @@ -3,5 +3,5 @@ package org.koitharu.kotatsu.core.exceptions import okio.IOException class NoDataReceivedException( - url: String, + val url: String, ) : IOException("No data has been received from $url") diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt index a7bae7f51..5811cfa65 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Throwable.kt @@ -142,6 +142,7 @@ fun Throwable.getCauseUrl(): String? = when (this) { is NotFoundException -> url is TooManyRequestExceptions -> url is CaughtException -> cause?.getCauseUrl() + is NoDataReceivedException -> url is CloudFlareBlockedException -> url is CloudFlareProtectedException -> url is HttpStatusException -> url From 0b8afe9c404fbe7153d04923259c211d9d4cedcc Mon Sep 17 00:00:00 2001 From: Koitharu Date: Wed, 18 Dec 2024 18:24:41 +0200 Subject: [PATCH 3/5] Fix checking for new chapters in some cases (#1212, #1195, #1190) --- .../tracker/domain/CheckNewChaptersUseCase.kt | 32 ++++++++++++++----- .../tracker/domain/TrackingRepository.kt | 3 +- .../tracker/domain/model/MangaUpdates.kt | 1 + 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/CheckNewChaptersUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/CheckNewChaptersUseCase.kt index 6802672d6..787498997 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/CheckNewChaptersUseCase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/CheckNewChaptersUseCase.kt @@ -1,6 +1,8 @@ package org.koitharu.kotatsu.tracker.domain +import android.util.Log import coil3.request.CachePolicy +import org.koitharu.kotatsu.BuildConfig import org.koitharu.kotatsu.core.model.getPreferredBranch import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.parser.CachingMangaRepository @@ -11,6 +13,7 @@ import org.koitharu.kotatsu.core.util.ext.toInstantOrNull import org.koitharu.kotatsu.history.data.HistoryRepository import org.koitharu.kotatsu.local.data.LocalMangaRepository import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.findById import org.koitharu.kotatsu.parsers.util.runCatchingCancellable import org.koitharu.kotatsu.tracker.domain.model.MangaTracking import org.koitharu.kotatsu.tracker.domain.model.MangaUpdates @@ -45,8 +48,9 @@ class CheckNewChaptersUseCase @Inject constructor( runCatchingCancellable { repository.updateTracks() val details = getFullManga(manga) - val chapters = details.chapters ?: return@withLock val track = repository.getTrackOrNull(manga) ?: return@withLock + val branch = checkNotNull(details.chapters?.findById(currentChapterId)).branch + val chapters = details.getChapters(branch) val chapterIndex = chapters.indexOfFirst { x -> x.id == currentChapterId } val lastNewChapterIndex = chapters.size - track.newChapters val lastChapter = chapters.lastOrNull() @@ -70,7 +74,7 @@ class CheckNewChaptersUseCase @Inject constructor( private suspend fun invokeImpl(track: MangaTracking): MangaUpdates = runCatchingCancellable { val details = getFullManga(track.manga) - compare(track, details, getBranch(details)) + compare(track, details, getBranch(details, track.lastChapterId)) }.getOrElse { error -> MangaUpdates.Failure( manga = track.manga, @@ -80,9 +84,17 @@ class CheckNewChaptersUseCase @Inject constructor( repository.saveUpdates(updates) } - private suspend fun getBranch(manga: Manga): String? { - val history = historyRepository.getOne(manga) - return manga.getPreferredBranch(history) + private suspend fun getBranch(manga: Manga, trackChapterId: Long): String? { + historyRepository.getOne(manga)?.let { + manga.chapters?.findById(it.chapterId) + }?.let { + return it.branch + } + manga.chapters?.findById(trackChapterId)?.let { + return it.branch + } + // fallback + return manga.getPreferredBranch(null) } private suspend fun getFullManga(manga: Manga): Manga = when { @@ -111,25 +123,29 @@ class CheckNewChaptersUseCase @Inject constructor( private fun compare(track: MangaTracking, manga: Manga, branch: String?): MangaUpdates.Success { if (track.isEmpty()) { // first check or manga was empty on last check - return MangaUpdates.Success(manga, emptyList(), isValid = false) + return MangaUpdates.Success(manga, branch, emptyList(), isValid = false) } val chapters = requireNotNull(manga.getChapters(branch)) + if (BuildConfig.DEBUG && chapters.findById(track.lastChapterId) == null) { + Log.e("Tracker", "Chapter ${track.lastChapterId} not found") + } val newChapters = chapters.takeLastWhile { x -> x.id != track.lastChapterId } return when { newChapters.isEmpty() -> { MangaUpdates.Success( manga = manga, + branch = branch, newChapters = emptyList(), isValid = chapters.lastOrNull()?.id == track.lastChapterId, ) } newChapters.size == chapters.size -> { - MangaUpdates.Success(manga, emptyList(), isValid = false) + MangaUpdates.Success(manga, branch, emptyList(), isValid = false) } else -> { - MangaUpdates.Success(manga, newChapters, isValid = true) + MangaUpdates.Success(manga, branch, newChapters, isValid = true) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt index 58bd10e95..569a4e1e8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/TrackingRepository.kt @@ -216,7 +216,6 @@ class TrackingRepository @Inject constructor( } private fun TrackEntity.mergeWith(updates: MangaUpdates): TrackEntity { - val chapters = updates.manga.chapters.orEmpty() return when (updates) { is MangaUpdates.Failure -> TrackEntity( mangaId = mangaId, @@ -230,7 +229,7 @@ class TrackingRepository @Inject constructor( is MangaUpdates.Success -> TrackEntity( mangaId = mangaId, - lastChapterId = chapters.lastOrNull()?.id ?: NO_ID, + lastChapterId = updates.manga.getChapters(updates.branch).lastOrNull()?.id ?: NO_ID, newChapters = if (updates.isValid) newChapters + updates.newChapters.size else 0, lastCheckTime = System.currentTimeMillis(), lastChapterDate = updates.lastChapterDate().ifZero { lastChapterDate }, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaUpdates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaUpdates.kt index 5d27a2f36..e59011199 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaUpdates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/domain/model/MangaUpdates.kt @@ -11,6 +11,7 @@ sealed interface MangaUpdates { data class Success( override val manga: Manga, + val branch: String?, val newChapters: List, val isValid: Boolean, ) : MangaUpdates { From 0f68be9663d1ab276af9c2559698cc11c510ca91 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 19 Dec 2024 17:10:01 +0200 Subject: [PATCH 4/5] Use advanced bitmap decoder for MangaLoaderContext --- .../kotatsu/core/image/BitmapDecoderCompat.kt | 23 ++++++++++++++++--- .../core/parser/MangaLoaderContextImpl.kt | 10 +++----- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt index 80e9a4bdb..34584ec57 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/BitmapDecoderCompat.kt @@ -5,6 +5,7 @@ import android.graphics.BitmapFactory import android.graphics.ImageDecoder import android.os.Build import android.webkit.MimeTypeMap +import androidx.annotation.RequiresApi import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull @@ -32,19 +33,21 @@ object BitmapDecoderCompat { } @Blocking - fun decode(stream: InputStream, type: MediaType?): Bitmap { + fun decode(stream: InputStream, type: MediaType?, isMutable: Boolean = false): Bitmap { val format = type?.subtype if (format == FORMAT_AVIF) { return decodeAvif(stream.toByteBuffer()) } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { - return checkBitmapNotNull(BitmapFactory.decodeStream(stream), format) + val opts = BitmapFactory.Options() + opts.inMutable = isMutable + return checkBitmapNotNull(BitmapFactory.decodeStream(stream, null, opts), format) } val byteBuffer = stream.toByteBuffer() return if (AvifDecoder.isAvifImage(byteBuffer)) { decodeAvif(byteBuffer) } else { - ImageDecoder.decodeBitmap(ImageDecoder.createSource(byteBuffer)) + ImageDecoder.decodeBitmap(ImageDecoder.createSource(byteBuffer), DecoderConfigListener(isMutable)) } } @@ -74,4 +77,18 @@ object BitmapDecoderCompat { } return bitmap } + + @RequiresApi(Build.VERSION_CODES.P) + private class DecoderConfigListener( + private val isMutable: Boolean, + ) : ImageDecoder.OnHeaderDecodedListener { + + override fun onHeaderDecoded( + decoder: ImageDecoder, + info: ImageDecoder.ImageInfo, + source: ImageDecoder.Source + ) { + decoder.isMutableRequired = isMutable + } + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt index 824aea5d2..c68c79fec 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaLoaderContextImpl.kt @@ -2,12 +2,10 @@ package org.koitharu.kotatsu.core.parser import android.annotation.SuppressLint import android.content.Context -import android.graphics.BitmapFactory import android.util.Base64 import android.webkit.WebView import androidx.annotation.MainThread import androidx.core.os.LocaleListCompat -import com.davemorrissey.labs.subscaleview.decoder.ImageDecodeException import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -17,6 +15,7 @@ import okhttp3.OkHttpClient import okhttp3.Response import okhttp3.ResponseBody.Companion.asResponseBody import okio.Buffer +import org.koitharu.kotatsu.core.image.BitmapDecoderCompat import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.prefs.SourceSettings @@ -31,7 +30,6 @@ import org.koitharu.kotatsu.parsers.config.MangaSourceConfig import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.network.UserAgents import org.koitharu.kotatsu.parsers.util.map -import org.koitharu.kotatsu.parsers.util.mimeType import java.lang.ref.WeakReference import java.util.Locale import javax.inject.Inject @@ -80,15 +78,13 @@ class MangaLoaderContextImpl @Inject constructor( override fun redrawImageResponse(response: Response, redraw: (image: Bitmap) -> Bitmap): Response { return response.map { body -> - val opts = BitmapFactory.Options() - opts.inMutable = true - BitmapFactory.decodeStream(body.byteStream(), null, opts)?.use { bitmap -> + BitmapDecoderCompat.decode(body.byteStream(), body.contentType(), isMutable = true).use { bitmap -> (redraw(BitmapWrapper.create(bitmap)) as BitmapWrapper).use { result -> Buffer().also { result.compressTo(it.outputStream()) }.asResponseBody("image/jpeg".toMediaType()) } - } ?: throw ImageDecodeException(response.request.url.toString(), response.mimeType) + } } } From 40f262b0ef503a509d191f4e01c076b442cb99f1 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Thu, 19 Dec 2024 17:28:12 +0200 Subject: [PATCH 5/5] Update parsers --- app/build.gradle | 4 ++-- gradle/libs.versions.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a2dc359fe..b00f82afc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,8 @@ android { applicationId 'org.koitharu.kotatsu' minSdk = 21 targetSdk = 35 - versionCode = 695 - versionName = '7.7.3' + versionCode = 696 + versionName = '7.7.4' generatedDensities = [] testInstrumentationRunner 'org.koitharu.kotatsu.HiltTestRunner' ksp { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 20a612dee..6ba7fe0e2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ material = "1.12.0" moshi = "1.15.2" okhttp = "4.12.0" okio = "3.9.1" -parsers = "f86d31f811" +parsers = "2550b9cac1" preference = "1.2.1" recyclerview = "1.3.2" room = "2.6.1"