From 1eb6276fad07b1502bc76a79928766877f050487 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 20:07:12 +0200 Subject: [PATCH 01/74] Update compose bom and fix renovate config for it Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- .github/renovate.json5 | 10 +++++++++- gradle/compose.versions.toml | 3 ++- .../presentation/core/components/AdaptiveSheet.kt | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ccf8871bf9..0424c4ed9d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -6,12 +6,20 @@ "schedule": ["every friday"], "labels": ["Dependencies"], "packageRules": [ + { + "groupName": "Compose BOM", + "matchPackageNames": [ + "dev.chrisbanes.compose:compose-bom" + ], + "ignoreUnstable": false + }, { // Compiler plugins are tightly coupled to Kotlin version "groupName": "Kotlin", "matchPackagePrefixes": [ "androidx.compose.compiler", - "org.jetbrains.kotlin", + "org.jetbrains.kotlin.", + "org.jetbrains.kotlin:" ], } ] diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 4ccf751b74..0b05dd508a 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,6 +1,7 @@ [versions] compiler = "1.5.11" -compose-bom = "2024.02.00-alpha02" +# 2024.04.00-alpha01 has several bugs with the new animateItem() modifier +compose-bom = "2024.03.00-alpha02" accompanist = "0.35.0-alpha" [libraries] diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index 9c2adf6303..b42c8e41f8 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -130,7 +130,7 @@ fun AdaptiveSheet( ) } val internalOnDismissRequest = { - if (anchoredDraggableState.currentValue == 0) { + if (anchoredDraggableState.settledValue == 0) { scope.launch { anchoredDraggableState.animateTo(1) } } } @@ -200,7 +200,7 @@ fun AdaptiveSheet( LaunchedEffect(anchoredDraggableState) { scope.launch { anchoredDraggableState.animateTo(0) } - snapshotFlow { anchoredDraggableState.currentValue } + snapshotFlow { anchoredDraggableState.settledValue } .drop(1) .filter { it == 1 } .collectLatest { From 3c4142cb543f91382f2fec31dd5212c241ef9f47 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 20:38:02 +0200 Subject: [PATCH 02/74] Use m3 ripple and clean up interactionSource usage Also remove a leftover of scoped storage adaptation. Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- app/build.gradle.kts | 1 - .../browse/anime/AnimeExtensionDetailsScreen.kt | 5 +---- .../browse/manga/MangaExtensionDetailsScreen.kt | 5 +---- .../components/ItemDownloadIndicator.kt | 2 +- .../entries/components/EntryBottomActionMenu.kt | 5 ++--- .../presentation/core/components/AdaptiveSheet.kt | 10 ++++------ .../core/components/material/Surface.kt | 2 +- .../tachiyomi/presentation/core/util/Modifier.kt | 15 ++++++--------- 8 files changed, 16 insertions(+), 29 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index dc602dc8f6..f490427d39 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -310,7 +310,6 @@ tasks { "-opt-in=androidx.compose.animation.ExperimentalAnimationApi", "-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi", "-opt-in=coil3.annotation.ExperimentalCoilApi", - "-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.coroutines.InternalCoroutinesApi", diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt index 6b88b69438..a0089a9633 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt @@ -5,7 +5,6 @@ import android.net.Uri import android.provider.Settings import android.util.DisplayMetrics import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -355,10 +354,8 @@ private fun InfoText( primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge, onClick: (() -> Unit)? = null, ) { - val interactionSource = remember { MutableInteractionSource() } - val clickableModifier = if (onClick != null) { - Modifier.clickable(interactionSource, indication = null) { onClick() } + Modifier.clickable(interactionSource = null, indication = null, onClick = onClick) } else { Modifier } diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt index 7b9a755354..be6c07fffb 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt @@ -5,7 +5,6 @@ import android.net.Uri import android.provider.Settings import android.util.DisplayMetrics import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues @@ -354,10 +353,8 @@ private fun InfoText( primaryTextStyle: TextStyle = MaterialTheme.typography.bodyLarge, onClick: (() -> Unit)? = null, ) { - val interactionSource = remember { MutableInteractionSource() } - val clickableModifier = if (onClick != null) { - Modifier.clickable(interactionSource, indication = null) { onClick() } + Modifier.clickable(interactionSource = null, indication = null, onClick = onClick) } else { Modifier } diff --git a/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt b/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt index ffecc3815c..57b8c297e5 100644 --- a/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt +++ b/app/src/main/java/eu/kanade/presentation/components/ItemDownloadIndicator.kt @@ -3,7 +3,7 @@ package eu.kanade.presentation.components import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material.ripple +import androidx.compose.material3.ripple import androidx.compose.ui.Modifier import androidx.compose.ui.hapticfeedback.HapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedbackType diff --git a/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt index bd12639d39..e7f9861d42 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -32,11 +31,11 @@ import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Input import androidx.compose.material.icons.outlined.OpenInNew import androidx.compose.material.icons.outlined.RemoveDone -import androidx.compose.material.ripple import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf @@ -234,7 +233,7 @@ private fun RowScope.Button( .size(48.dp) .weight(animatedWeight) .combinedClickable( - interactionSource = remember { MutableInteractionSource() }, + interactionSource = null, indication = ripple(bounded = false), onLongClick = onLongClick, onClick = onClick, diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index b42c8e41f8..3758f173c2 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.gestures.anchoredDraggable import androidx.compose.foundation.gestures.animateTo -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.navigationBarsPadding @@ -86,8 +85,7 @@ fun AdaptiveSheet( Box( modifier = Modifier .clickable( - enabled = true, - interactionSource = remember { MutableInteractionSource() }, + interactionSource = null, indication = null, onClick = internalOnDismissRequest, ) @@ -99,7 +97,7 @@ fun AdaptiveSheet( modifier = Modifier .requiredWidthIn(max = maxWidth) .clickable( - interactionSource = remember { MutableInteractionSource() }, + interactionSource = null, indication = null, onClick = {}, ) @@ -137,7 +135,7 @@ fun AdaptiveSheet( Box( modifier = Modifier .clickable( - interactionSource = remember { MutableInteractionSource() }, + interactionSource = null, indication = null, onClick = internalOnDismissRequest, ) @@ -155,7 +153,7 @@ fun AdaptiveSheet( modifier = Modifier .widthIn(max = maxWidth) .clickable( - interactionSource = remember { MutableInteractionSource() }, + interactionSource = null, indication = null, onClick = {}, ) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Surface.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Surface.kt index f6def12247..8433bf6dab 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Surface.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Surface.kt @@ -6,13 +6,13 @@ import androidx.compose.foundation.border import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box -import androidx.compose.material.ripple import androidx.compose.material3.ColorScheme import androidx.compose.material3.LocalAbsoluteTonalElevation import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.contentColorFor import androidx.compose.material3.minimumInteractiveComponentSize +import androidx.compose.material3.ripple import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.NonRestartableComposable diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt index 411fc99833..875cd45832 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/util/Modifier.kt @@ -1,7 +1,6 @@ package tachiyomi.presentation.core.util import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.isImeVisible @@ -42,14 +41,12 @@ fun Modifier.secondaryItemAlpha(): Modifier = this.alpha(SecondaryItemAlpha) fun Modifier.clickableNoIndication( onLongClick: (() -> Unit)? = null, onClick: () -> Unit, -): Modifier = composed { - Modifier.combinedClickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null, - onLongClick = onLongClick, - onClick = onClick, - ) -} +) = this.combinedClickable( + interactionSource = null, + indication = null, + onLongClick = onLongClick, + onClick = onClick, +) /** * For TextField, the provided [action] will be invoked when From 4f865ff629ce681a621f6e3d4a7e3d91e31d211c Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:00:47 +0200 Subject: [PATCH 03/74] Use Okio instead of `java.io` for image processing Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- app/src/main/java/eu/kanade/tachiyomi/App.kt | 2 + .../data/coil/BufferedSourceFetcher.kt | 38 +++++++++ .../ui/reader/viewer/ReaderPageImageView.kt | 28 +++---- .../ui/reader/viewer/pager/PagerPageHolder.kt | 81 +++++++++---------- .../viewer/webtoon/WebtoonPageHolder.kt | 39 ++++----- .../core/common/util/system/ImageUtil.kt | 76 +++++++---------- 6 files changed, 132 insertions(+), 132 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/coil/BufferedSourceFetcher.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index ac7f890f77..672c401d98 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -32,6 +32,7 @@ import eu.kanade.tachiyomi.crash.GlobalExceptionHandler import eu.kanade.tachiyomi.data.coil.AnimeCoverFetcher import eu.kanade.tachiyomi.data.coil.AnimeCoverKeyer import eu.kanade.tachiyomi.data.coil.AnimeKeyer +import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer import eu.kanade.tachiyomi.data.coil.MangaKeyer @@ -187,6 +188,7 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor add(MangaKeyer()) add(AnimeCoverKeyer()) add(MangaCoverKeyer()) + add(BufferedSourceFetcher.Factory()) } crossfade((300 * this@App.animatorDurationScale).toInt()) allowRgb565(DeviceUtil.isLowRamDevice(this@App)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/BufferedSourceFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/BufferedSourceFetcher.kt new file mode 100644 index 0000000000..4bee925ed5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/BufferedSourceFetcher.kt @@ -0,0 +1,38 @@ +package eu.kanade.tachiyomi.data.coil + +import coil3.ImageLoader +import coil3.decode.DataSource +import coil3.decode.ImageSource +import coil3.fetch.FetchResult +import coil3.fetch.Fetcher +import coil3.fetch.SourceFetchResult +import coil3.request.Options +import okio.BufferedSource + +class BufferedSourceFetcher( + private val data: BufferedSource, + private val options: Options, +) : Fetcher { + + override suspend fun fetch(): FetchResult { + return SourceFetchResult( + source = ImageSource( + source = data, + fileSystem = options.fileSystem, + ), + mimeType = null, + dataSource = DataSource.MEMORY, + ) + } + + class Factory : Fetcher.Factory { + + override fun create( + data: BufferedSource, + options: Options, + imageLoader: ImageLoader, + ): Fetcher { + return BufferedSourceFetcher(data, options) + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index ffaa68aeed..ef43d8685c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -33,8 +33,7 @@ import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.view.isVisibleOnScreen -import java.io.InputStream -import java.nio.ByteBuffer +import okio.BufferedSource /** * A wrapper view for showing page image. @@ -159,14 +158,14 @@ open class ReaderPageImageView @JvmOverloads constructor( } } - fun setImage(inputStream: InputStream, isAnimated: Boolean, config: Config) { + fun setImage(source: BufferedSource, isAnimated: Boolean, config: Config) { this.config = config if (isAnimated) { prepareAnimatedImageView() - setAnimatedImage(inputStream, config) + setAnimatedImage(source, config) } else { prepareNonAnimatedImageView() - setNonAnimatedImage(inputStream, config) + setNonAnimatedImage(source, config) } } @@ -275,7 +274,7 @@ open class ReaderPageImageView @JvmOverloads constructor( } private fun setNonAnimatedImage( - image: Any, + data: Any, config: Config, ) = (pageView as? SubsamplingScaleImageView)?.apply { setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration()) @@ -296,11 +295,11 @@ open class ReaderPageImageView @JvmOverloads constructor( }, ) - when (image) { - is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap)) - is InputStream -> setImage(ImageSource.inputStream(image)) + when (data) { + is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap)) + is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream())) else -> throw IllegalArgumentException( - "Not implemented for class ${image::class.simpleName}", + "Not implemented for class ${data::class.simpleName}", ) } isVisible = true @@ -346,20 +345,13 @@ open class ReaderPageImageView @JvmOverloads constructor( } private fun setAnimatedImage( - image: Any, + data: Any, config: Config, ) = (pageView as? AppCompatImageView)?.apply { if (this is PhotoView) { setZoomTransitionDuration(config.zoomDuration.getSystemScaledDuration()) } - val data = when (image) { - is Drawable -> image - is InputStream -> ByteBuffer.wrap(image.readBytes()) - else -> throw IllegalArgumentException( - "Not implemented for class ${image::class.simpleName}", - ) - } val request = ImageRequest.Builder(context) .data(data) .memoryCachePolicy(CachePolicy.DISABLED) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index cbf53afbec..e3e2113224 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -18,14 +18,13 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import logcat.LogPriority +import okio.Buffer +import okio.BufferedSource import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat -import java.io.BufferedInputStream -import java.io.ByteArrayInputStream -import java.io.InputStream /** * View of the ViewPager that contains a page of a chapter. @@ -141,38 +140,30 @@ class PagerPageHolder( val streamFn = page.stream ?: return try { - val (bais, isAnimated, background) = withIOContext { - streamFn().buffered(16).use { stream -> - process(item, stream).use { itemStream -> - val bais = ByteArrayInputStream(itemStream.readBytes()) - val isAnimated = ImageUtil.isAnimatedAndSupported(bais) - bais.reset() - val background = if (!isAnimated && viewer.config.automaticBackground) { - ImageUtil.chooseBackground(context, bais) - } else { - null - } - bais.reset() - Triple(bais, isAnimated, background) - } + val (source, isAnimated, background) = withIOContext { + val source = streamFn().use { process(item, Buffer().readFrom(it)) } + val isAnimated = ImageUtil.isAnimatedAndSupported(source) + val background = if (!isAnimated && viewer.config.automaticBackground) { + ImageUtil.chooseBackground(context, source.peek().inputStream()) + } else { + null } + Triple(source, isAnimated, background) } withUIContext { - bais.use { - setImage( - it, - isAnimated, - Config( - zoomDuration = viewer.config.doubleTapAnimDuration, - minimumScaleType = viewer.config.imageScaleType, - cropBorders = viewer.config.imageCropBorders, - zoomStartPosition = viewer.config.imageZoomType, - landscapeZoom = viewer.config.landscapeZoom, - ), - ) - if (!isAnimated) { - pageBackground = background - } + setImage( + source, + isAnimated, + Config( + zoomDuration = viewer.config.doubleTapAnimDuration, + minimumScaleType = viewer.config.imageScaleType, + cropBorders = viewer.config.imageCropBorders, + zoomStartPosition = viewer.config.imageZoomType, + landscapeZoom = viewer.config.landscapeZoom, + ), + ) + if (!isAnimated) { + pageBackground = background } removeErrorLayout() } @@ -184,40 +175,40 @@ class PagerPageHolder( } } - private fun process(page: ReaderPage, imageStream: BufferedInputStream): InputStream { + private fun process(page: ReaderPage, imageSource: BufferedSource): BufferedSource { if (viewer.config.dualPageRotateToFit) { - return rotateDualPage(imageStream) + return rotateDualPage(imageSource) } if (!viewer.config.dualPageSplit) { - return imageStream + return imageSource } if (page is InsertPage) { - return splitInHalf(imageStream) + return splitInHalf(imageSource) } - val isDoublePage = ImageUtil.isWideImage(imageStream) + val isDoublePage = ImageUtil.isWideImage(imageSource) if (!isDoublePage) { - return imageStream + return imageSource } onPageSplit(page) - return splitInHalf(imageStream) + return splitInHalf(imageSource) } - private fun rotateDualPage(imageStream: BufferedInputStream): InputStream { - val isDoublePage = ImageUtil.isWideImage(imageStream) + private fun rotateDualPage(imageSource: BufferedSource): BufferedSource { + val isDoublePage = ImageUtil.isWideImage(imageSource) return if (isDoublePage) { val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f - ImageUtil.rotateImage(imageStream, rotation) + ImageUtil.rotateImage(imageSource, rotation) } else { - imageStream + imageSource } } - private fun splitInHalf(imageStream: InputStream): InputStream { + private fun splitInHalf(imageSource: BufferedSource): BufferedSource { var side = when { viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT viewer !is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.LEFT @@ -233,7 +224,7 @@ class PagerPageHolder( } } - return ImageUtil.splitInHalf(imageStream, side) + return ImageUtil.splitInHalf(imageSource, side) } private fun onPageSplit(page: ReaderPage) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index a66139f42f..4412a86480 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -22,15 +22,14 @@ import kotlinx.coroutines.MainScope import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope -import kotlinx.coroutines.suspendCancellableCoroutine import logcat.LogPriority +import okio.Buffer +import okio.BufferedSource import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat -import java.io.BufferedInputStream -import java.io.InputStream /** * Holder of the webtoon reader for a single page of a chapter. @@ -188,16 +187,14 @@ class WebtoonPageHolder( val streamFn = page?.stream ?: return try { - val (openStream, isAnimated) = withIOContext { - val stream = streamFn().buffered(16) - val openStream = process(stream) - - val isAnimated = ImageUtil.isAnimatedAndSupported(stream) - Pair(openStream, isAnimated) + val (source, isAnimated) = withIOContext { + val source = streamFn().use { process(Buffer().readFrom(it)) } + val isAnimated = ImageUtil.isAnimatedAndSupported(source) + Pair(source, isAnimated) } withUIContext { frame.setImage( - openStream, + source, isAnimated, ReaderPageImageView.Config( zoomDuration = viewer.config.doubleTapAnimDuration, @@ -207,10 +204,6 @@ class WebtoonPageHolder( ) removeErrorLayout() } - // Suspend the coroutine to close the input stream only when the WebtoonPageHolder is recycled - suspendCancellableCoroutine { continuation -> - continuation.invokeOnCancellation { openStream.close() } - } } catch (e: Throwable) { logcat(LogPriority.ERROR, e) withUIContext { @@ -219,29 +212,29 @@ class WebtoonPageHolder( } } - private fun process(imageStream: BufferedInputStream): InputStream { + private fun process(imageSource: BufferedSource): BufferedSource { if (viewer.config.dualPageRotateToFit) { - return rotateDualPage(imageStream) + return rotateDualPage(imageSource) } if (viewer.config.dualPageSplit) { - val isDoublePage = ImageUtil.isWideImage(imageStream) + val isDoublePage = ImageUtil.isWideImage(imageSource) if (isDoublePage) { val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT - return ImageUtil.splitAndMerge(imageStream, upperSide) + return ImageUtil.splitAndMerge(imageSource, upperSide) } } - return imageStream + return imageSource } - private fun rotateDualPage(imageStream: BufferedInputStream): InputStream { - val isDoublePage = ImageUtil.isWideImage(imageStream) + private fun rotateDualPage(imageSource: BufferedSource): BufferedSource { + val isDoublePage = ImageUtil.isWideImage(imageSource) return if (isDoublePage) { val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f - ImageUtil.rotateImage(imageStream, rotation) + ImageUtil.rotateImage(imageSource, rotation) } else { - imageStream + imageSource } } diff --git a/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt b/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt index a477f8a987..e33275de65 100644 --- a/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt +++ b/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt @@ -24,11 +24,10 @@ import androidx.core.graphics.green import androidx.core.graphics.red import com.hippo.unifile.UniFile import logcat.LogPriority +import okio.Buffer +import okio.BufferedSource import tachiyomi.decoder.Format import tachiyomi.decoder.ImageDecoder -import java.io.BufferedInputStream -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream import java.io.InputStream import java.net.URLConnection import java.util.Locale @@ -76,9 +75,9 @@ object ImageUtil { ?: "jpg" } - fun isAnimatedAndSupported(stream: InputStream): Boolean { + fun isAnimatedAndSupported(source: BufferedSource): Boolean { return try { - val type = getImageType(stream) ?: return false + val type = getImageType(source.peek().inputStream()) ?: return false // https://coil-kt.github.io/coil/getting_started/#supported-image-formats when (type.format) { Format.Gif -> true @@ -125,18 +124,16 @@ object ImageUtil { * * @return true if the width is greater than the height */ - fun isWideImage(imageStream: BufferedInputStream): Boolean { - val options = extractImageOptions(imageStream) + fun isWideImage(imageSource: BufferedSource): Boolean { + val options = extractImageOptions(imageSource) return options.outWidth > options.outHeight } /** - * Extract the 'side' part from imageStream and return it as InputStream. + * Extract the 'side' part from [BufferedSource] and return it as [BufferedSource]. */ - fun splitInHalf(imageStream: InputStream, side: Side): InputStream { - val imageBytes = imageStream.readBytes() - - val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + fun splitInHalf(imageSource: BufferedSource, side: Side): BufferedSource { + val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream()) val height = imageBitmap.height val width = imageBitmap.width @@ -150,22 +147,20 @@ object ImageUtil { half.applyCanvas { drawBitmap(imageBitmap, part, singlePage, null) } - val output = ByteArrayOutputStream() - half.compress(Bitmap.CompressFormat.JPEG, 100, output) + val output = Buffer() + half.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream()) - return ByteArrayInputStream(output.toByteArray()) + return output } - fun rotateImage(imageStream: InputStream, degrees: Float): InputStream { - val imageBytes = imageStream.readBytes() - - val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + fun rotateImage(imageSource: BufferedSource, degrees: Float): BufferedSource { + val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream()) val rotated = rotateBitMap(imageBitmap, degrees) - val output = ByteArrayOutputStream() - rotated.compress(Bitmap.CompressFormat.JPEG, 100, output) + val output = Buffer() + rotated.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream()) - return ByteArrayInputStream(output.toByteArray()) + return output } private fun rotateBitMap(bitmap: Bitmap, degrees: Float): Bitmap { @@ -176,10 +171,8 @@ object ImageUtil { /** * Split the image into left and right parts, then merge them into a new image. */ - fun splitAndMerge(imageStream: InputStream, upperSide: Side): InputStream { - val imageBytes = imageStream.readBytes() - - val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + fun splitAndMerge(imageSource: BufferedSource, upperSide: Side): BufferedSource { + val imageBitmap = BitmapFactory.decodeStream(imageSource.inputStream()) val height = imageBitmap.height val width = imageBitmap.width @@ -201,9 +194,9 @@ object ImageUtil { drawBitmap(imageBitmap, leftPart, bottomPart, null) } - val output = ByteArrayOutputStream() - result.compress(Bitmap.CompressFormat.JPEG, 100, output) - return ByteArrayInputStream(output.toByteArray()) + val output = Buffer() + result.compress(Bitmap.CompressFormat.JPEG, 100, output.outputStream()) + return output } enum class Side { @@ -216,8 +209,8 @@ object ImageUtil { * * @return true if the height:width ratio is greater than 3. */ - private fun isTallImage(imageStream: InputStream): Boolean { - val options = extractImageOptions(imageStream, resetAfterExtraction = false) + private fun isTallImage(imageSource: BufferedSource): Boolean { + val options = extractImageOptions(imageSource) return (options.outHeight / options.outWidth) > 3 } @@ -225,20 +218,18 @@ object ImageUtil { * Splits tall images to improve performance of reader */ fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String): Boolean { - if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage( - imageFile.openInputStream(), - ) - ) { + val imageSource = imageFile.openInputStream().use { Buffer().readFrom(it) } + if (isAnimatedAndSupported(imageSource) || !isTallImage(imageSource)) { return true } - val bitmapRegionDecoder = getBitmapRegionDecoder(imageFile.openInputStream()) + val bitmapRegionDecoder = getBitmapRegionDecoder(imageSource.peek().inputStream()) if (bitmapRegionDecoder == null) { logcat { "Failed to create new instance of BitmapRegionDecoder" } return false } - val options = extractImageOptions(imageFile.openInputStream(), resetAfterExtraction = false).apply { + val options = extractImageOptions(imageSource).apply { inJustDecodeBounds = false } @@ -589,16 +580,9 @@ object ImageUtil { /** * Used to check an image's dimensions without loading it in the memory. */ - private fun extractImageOptions( - imageStream: InputStream, - resetAfterExtraction: Boolean = true, - ): BitmapFactory.Options { - imageStream.mark(Int.MAX_VALUE) - - val imageBytes = imageStream.readBytes() + private fun extractImageOptions(imageSource: BufferedSource): BitmapFactory.Options { val options = BitmapFactory.Options().apply { inJustDecodeBounds = true } - BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options) - if (resetAfterExtraction) imageStream.reset() + BitmapFactory.decodeStream(imageSource.peek().inputStream(), null, options) return options } From ee3e9b7c4bff7913cde7080585f57cb907694a30 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:03:23 +0200 Subject: [PATCH 04/74] =?UTF-8?q?fix(deps):=20update=20dependency=20androi?= =?UTF-8?q?dx.compose.compiler:compiler=20to=20v1=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ….5.12 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/compose.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 0b05dd508a..9911588379 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compiler = "1.5.11" +compiler = "1.5.12" # 2024.04.00-alpha01 has several bugs with the new animateItem() modifier compose-bom = "2024.03.00-alpha02" accompanist = "0.35.0-alpha" From 107c3f68536a6db2fc3d9055059549a521d7af69 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:04:11 +0200 Subject: [PATCH 05/74] chore(deps): update actions/checkout action to v4.1.4 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_pull_request.yml | 2 +- .github/workflows/build_push.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 01959c3cfe..a73d487767 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index e76a4874ea..b98a9043a3 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 From 1ee6f0fc57f01e00e6a5e18f54d1051e43d51b9e Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:09:41 +0200 Subject: [PATCH 06/74] Use Coil pipeline instead of SSIV for image decode Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- .../data/coil/TachiyomiImageDecoder.kt | 46 +++++++++++++++++-- .../eu/kanade/tachiyomi/data/coil/Utils.kt | 44 ++++++++++++++++++ .../tachiyomi/ui/reader/ReaderActivity.kt | 5 +- .../ui/reader/viewer/ReaderPageImageView.kt | 41 ++++++++++++++--- 4 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt index 2f0c3df493..2929f4da3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt @@ -1,12 +1,16 @@ package eu.kanade.tachiyomi.data.coil +import android.graphics.Bitmap import coil3.ImageLoader import coil3.asCoilImage import coil3.decode.DecodeResult +import coil3.decode.DecodeUtils import coil3.decode.Decoder import coil3.decode.ImageSource import coil3.fetch.SourceFetchResult import coil3.request.Options +import coil3.request.bitmapConfig +import eu.kanade.tachiyomi.util.system.GLUtil import okio.BufferedSource import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.decoder.ImageDecoder @@ -18,27 +22,55 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti override suspend fun decode(): DecodeResult { val decoder = resources.sourceOrNull()?.use { - ImageDecoder.newInstance(it.inputStream()) + ImageDecoder.newInstance(it.inputStream(), options.cropBorders, displayProfile) } check(decoder != null && decoder.width > 0 && decoder.height > 0) { "Failed to initialize decoder" } - val bitmap = decoder.decode() + val srcWidth = decoder.width + val srcHeight = decoder.height + + val dstWidth = options.size.widthPx(options.scale) { srcWidth } + val dstHeight = options.size.heightPx(options.scale) { srcHeight } + + val sampleSize = DecodeUtils.calculateInSampleSize( + srcWidth = srcWidth, + srcHeight = srcHeight, + dstWidth = dstWidth, + dstHeight = dstHeight, + scale = options.scale, + ) + + var bitmap = decoder.decode(sampleSize = sampleSize) decoder.recycle() check(bitmap != null) { "Failed to decode image" } + if ( + options.bitmapConfig == Bitmap.Config.HARDWARE && + maxOf(bitmap.width, bitmap.height) <= GLUtil.maxTextureSize + ) { + val hwBitmap = bitmap.copy(Bitmap.Config.HARDWARE, false) + if (hwBitmap != null) { + bitmap.recycle() + bitmap = hwBitmap + } + } + return DecodeResult( image = bitmap.asCoilImage(), - isSampled = false, + isSampled = sampleSize > 1, ) } class Factory : Decoder.Factory { override fun create(result: SourceFetchResult, options: Options, imageLoader: ImageLoader): Decoder? { - if (!isApplicable(result.source.source())) return null - return TachiyomiImageDecoder(result.source, options) + return if (options.customDecoder || isApplicable(result.source.source())) { + TachiyomiImageDecoder(result.source, options) + } else { + null + } } private fun isApplicable(source: BufferedSource): Boolean { @@ -55,4 +87,8 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti override fun hashCode() = javaClass.hashCode() } + + companion object { + var displayProfile: ByteArray? = null + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt new file mode 100644 index 0000000000..7a920bf398 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt @@ -0,0 +1,44 @@ +package eu.kanade.tachiyomi.data.coil + +import coil3.Extras +import coil3.getExtra +import coil3.request.ImageRequest +import coil3.request.Options +import coil3.size.Dimension +import coil3.size.Scale +import coil3.size.Size +import coil3.size.isOriginal +import coil3.size.pxOrElse + +internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else width.toPx(scale) +} + +internal inline fun Size.heightPx(scale: Scale, original: () -> Int): Int { + return if (isOriginal) original() else height.toPx(scale) +} + +internal fun Dimension.toPx(scale: Scale): Int = pxOrElse { + when (scale) { + Scale.FILL -> Int.MIN_VALUE + Scale.FIT -> Int.MAX_VALUE + } +} + +fun ImageRequest.Builder.cropBorders(enable: Boolean) = apply { + extras[cropBordersKey] = enable +} + +val Options.cropBorders: Boolean + get() = getExtra(cropBordersKey) + +private val cropBordersKey = Extras.Key(default = false) + +fun ImageRequest.Builder.customDecoder(enable: Boolean) = apply { + extras[customDecoderKey] = enable +} + +val Options.customDecoder: Boolean + get() = getExtra(customDecoderKey) + +private val customDecoderKey = Extras.Key(default = false) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 7e94223fd5..940c4c5f47 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -52,6 +52,7 @@ import eu.kanade.presentation.reader.appbars.ReaderAppBars import eu.kanade.presentation.reader.settings.ReaderSettingsDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.core.common.Constants +import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.databinding.ReaderActivityBinding @@ -879,7 +880,9 @@ class ReaderActivity : BaseActivity() { input.copyTo(output) } } - SubsamplingScaleImageView.setDisplayProfile(outputStream.toByteArray()) + val data = outputStream.toByteArray() + SubsamplingScaleImageView.setDisplayProfile(data) + TachiyomiImageDecoder.displayProfile = data } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index ef43d8685c..17674cf3ad 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -18,17 +18,22 @@ import androidx.annotation.StyleRes import androidx.appcompat.widget.AppCompatImageView import androidx.core.os.postDelayed import androidx.core.view.isVisible +import coil3.BitmapImage import coil3.dispose import coil3.imageLoader import coil3.request.CachePolicy import coil3.request.ImageRequest import coil3.request.crossfade +import coil3.size.Precision +import coil3.size.ViewSizeResolver import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_IN_OUT_QUAD import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.EASE_OUT_QUAD import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE import com.github.chrisbanes.photoview.PhotoView +import eu.kanade.tachiyomi.data.coil.cropBorders +import eu.kanade.tachiyomi.data.coil.customDecoder import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView import eu.kanade.tachiyomi.util.system.GLUtil import eu.kanade.tachiyomi.util.system.animatorDurationScale @@ -295,14 +300,36 @@ open class ReaderPageImageView @JvmOverloads constructor( }, ) - when (data) { - is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap)) - is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream())) - else -> throw IllegalArgumentException( - "Not implemented for class ${data::class.simpleName}", - ) + if (isWebtoon) { + val request = ImageRequest.Builder(context) + .data(data) + .memoryCachePolicy(CachePolicy.DISABLED) + .diskCachePolicy(CachePolicy.DISABLED) + .target( + onSuccess = { result -> + val image = result as BitmapImage + setImage(ImageSource.bitmap(image.bitmap)) + isVisible = true + }, + onError = { + this@ReaderPageImageView.onImageLoadError() + }, + ) + .size(ViewSizeResolver(this@ReaderPageImageView)) + .precision(Precision.INEXACT) + .cropBorders(config.cropBorders) + .customDecoder(true) + .crossfade(false) + .build() + context.imageLoader.enqueue(request) + } else { + when (data) { + is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap)) + is BufferedSource -> setImage(ImageSource.inputStream(data.inputStream())) + else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}") + } + isVisible = true } - isVisible = true } private fun prepareAnimatedImageView() { From c04e039d362da3a240d760643732139e3b0021d9 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:11:00 +0200 Subject: [PATCH 07/74] Update subsampling-scale-image-view Update libs.versions.toml Co-authored-by: w <19401176+wwww-wwww@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 99fd9fde1b..a4c6c6bc8d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,7 +49,7 @@ coil-gif = { module = "io.coil-kt.coil3:coil-gif" } coil-compose = { module = "io.coil-kt.coil3:coil-compose" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" } -subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:aeaa170036" +subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:b8e1b0ed2b" image-decoder = "com.github.tachiyomiorg:image-decoder:e08e9be535" natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1" From a10fc6dcb7f5437e663bd5f3327a2be778c2c4d9 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:12:33 +0200 Subject: [PATCH 08/74] Log app crash exceptions in dumped crash logs Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt | 2 +- app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt index 4fb22201a5..153d311a74 100644 --- a/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/crash/CrashScreen.kt @@ -40,7 +40,7 @@ fun CrashScreen( acceptText = stringResource(MR.strings.pref_dump_crash_logs), onAcceptClick = { scope.launch { - CrashLogUtil(context).dumpLogs() + CrashLogUtil(context).dumpLogs(exception) } }, rejectText = stringResource(MR.strings.crash_screen_restart_application), diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt index 4eb57d0a12..6fda6e37e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt @@ -22,13 +22,14 @@ class CrashLogUtil( private val animeExtensionManager: AnimeExtensionManager = Injekt.get(), ) { - suspend fun dumpLogs() = withNonCancellableContext { + suspend fun dumpLogs(exception: Throwable? = null) = withNonCancellableContext { try { val file = context.createFileInCacheDir("aniyomi_crash_logs.txt") file.appendText(getDebugInfo() + "\n\n") getMangaExtensionsInfo()?.let { file.appendText("$it\n\n") } getAnimeExtensionsInfo()?.let { file.appendText("$it\n\n") } + exception?.let { file.appendText("$it\n\n") } Runtime.getRuntime().exec("logcat *:E -d -f ${file.absolutePath}").waitFor() From 1fb04f3e2194954f8a069852cf6c917233d2edf1 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 21:14:32 +0200 Subject: [PATCH 09/74] chore(deps): update actions/dependency-review-action action to v4.3.2 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index a73d487767..98d90b1910 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -28,7 +28,7 @@ jobs: uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 - name: Dependency Review - uses: actions/dependency-review-action@5bbc3ba658137598168acb2ab73b21c432dd411b # v4.2.5 + uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 - name: Set up JDK uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 From 482425f7b2ea19e0b1907b21a351e94cbf7b5102 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 22:02:56 +0200 Subject: [PATCH 10/74] Fix some extension related issue and cleanups - Extension being marked as not installed instead of untrusted after updating with private installer - Extension update counter not updating due to extension being marked as untrusted - Minimize `Key "extension-XXX-YYY" was already used` crash Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../interactor/GetAnimeExtensionsByType.kt | 6 +- .../interactor/GetMangaExtensionsByType.kt | 6 +- .../extension/anime/AnimeExtensionManager.kt | 163 ++++++++---------- .../util/AnimeExtensionInstallReceiver.kt | 61 +++---- .../extension/manga/MangaExtensionManager.kt | 157 ++++++++--------- .../util/MangaExtensionInstallReceiver.kt | 61 +++---- 6 files changed, 186 insertions(+), 268 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt index 62d38df6e7..86f898f26e 100644 --- a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt +++ b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/GetAnimeExtensionsByType.kt @@ -20,7 +20,7 @@ class GetAnimeExtensionsByType( extensionManager.installedExtensionsFlow, extensionManager.untrustedExtensionsFlow, extensionManager.availableExtensionsFlow, - ) { _activeLanguages, _installed, _untrusted, _available -> + ) { enabledLanguages, _installed, _untrusted, _available -> val (updates, installed) = _installed .filter { (showNsfwSources || !it.isNsfw) } .sortedWith( @@ -40,9 +40,9 @@ class GetAnimeExtensionsByType( } .flatMap { ext -> if (ext.sources.isEmpty()) { - return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList() + return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList() } - ext.sources.filter { it.lang in _activeLanguages } + ext.sources.filter { it.lang in enabledLanguages } .map { ext.copy( name = it.name, diff --git a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt index f035338048..408b2fa643 100644 --- a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt +++ b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/GetMangaExtensionsByType.kt @@ -20,7 +20,7 @@ class GetMangaExtensionsByType( extensionManager.installedExtensionsFlow, extensionManager.untrustedExtensionsFlow, extensionManager.availableExtensionsFlow, - ) { _activeLanguages, _installed, _untrusted, _available -> + ) { enabledLanguages, _installed, _untrusted, _available -> val (updates, installed) = _installed .filter { (showNsfwSources || !it.isNsfw) } .sortedWith( @@ -40,9 +40,9 @@ class GetMangaExtensionsByType( } .flatMap { ext -> if (ext.sources.isEmpty()) { - return@flatMap if (ext.lang in _activeLanguages) listOf(ext) else emptyList() + return@flatMap if (ext.lang in enabledLanguages) listOf(ext) else emptyList() } - ext.sources.filter { it.lang in _activeLanguages } + ext.sources.filter { it.lang in enabledLanguages } .map { ext.copy( name = it.name, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt index e2bf607937..ad51eda0a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt @@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstallReceiver import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionInstaller import eu.kanade.tachiyomi.extension.anime.util.AnimeExtensionLoader import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.async +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import logcat.LogPriority -import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.source.anime.model.StubAnimeSource @@ -45,6 +48,8 @@ class AnimeExtensionManager( private val trustExtension: TrustAnimeExtension = Injekt.get(), ) { + val scope = CoroutineScope(SupervisorJob()) + private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -60,29 +65,36 @@ class AnimeExtensionManager( private val iconMap = mutableMapOf() - private val _installedAnimeExtensionsFlow = MutableStateFlow( - emptyList(), - ) - val installedExtensionsFlow = _installedAnimeExtensionsFlow.asStateFlow() + private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap()) + val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope) + + private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap()) + val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope) + + private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap()) + val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope) + + init { + initAnimeExtensions() + AnimeExtensionInstallReceiver(AnimeInstallationListener()).register(context) + } private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() fun getAppIconForSource(sourceId: Long): Drawable? { - val pkgName = _installedAnimeExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName - if (pkgName != null) { - return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { - AnimeExtensionLoader.getAnimeExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo - .loadIcon(context.packageManager) + val pkgName = _installedExtensionsMapFlow.value.values + .find { ext -> + ext.sources.any { it.id == sourceId } } + ?.pkgName + ?: return null + + return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { + AnimeExtensionLoader.getAnimeExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo + .loadIcon(context.packageManager) } - return null } - private val _availableExtensionsFlow = MutableStateFlow( - emptyList(), - ) - val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() - private var availableAnimeExtensionsSourcesData: Map = emptyMap() private fun setupAvailableAnimeExtensionsSourcesDataMap( @@ -96,35 +108,25 @@ class AnimeExtensionManager( fun getSourceData(id: Long) = availableAnimeExtensionsSourcesData[id] - private val _untrustedExtensionsFlow = MutableStateFlow( - emptyList(), - ) - val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow() - - init { - initAnimeExtensions() - AnimeExtensionInstallReceiver(AnimeInstallationListener()).register(context) - } - /** * Loads and registers the installed animeextensions. */ private fun initAnimeExtensions() { val animeextensions = AnimeExtensionLoader.loadExtensions(context) - _installedAnimeExtensionsFlow.value = animeextensions + _installedExtensionsMapFlow.value = animeextensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } - _untrustedExtensionsFlow.value = animeextensions + _untrustedExtensionsMapFlow.value = animeextensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } _isInitialized.value = true } /** - * Finds the available anime extensions in the [api] and updates [availableExtensions]. + * Finds the available anime extensions in the [api] and updates [_availableExtensionsMapFlow]. */ suspend fun findAvailableExtensions() { val extensions: List = try { @@ -137,7 +139,7 @@ class AnimeExtensionManager( enableAdditionalSubLanguages(extensions) - _availableExtensionsFlow.value = extensions + _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } updatedInstalledAnimeExtensionsStatuses(extensions) setupAvailableAnimeExtensionsSourcesDataMap(extensions) } @@ -185,35 +187,32 @@ class AnimeExtensionManager( return } - val mutInstalledExtensions = _installedAnimeExtensionsFlow.value.toMutableList() + val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap() var changed = false - for ((index, installedExt) in mutInstalledExtensions.withIndex()) { - val pkgName = installedExt.pkgName + for ((pkgName, extension) in installedExtensionsMap) { val availableExt = availableExtensions.find { it.pkgName == pkgName } - if (availableExt == null && !installedExt.isObsolete) { - mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) + if (availableExt == null && !extension.isObsolete) { + installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) changed = true } else if (availableExt != null) { - val hasUpdate = installedExt.updateExists(availableExt) - - if (installedExt.hasUpdate != hasUpdate) { - mutInstalledExtensions[index] = installedExt.copy( + val hasUpdate = extension.updateExists(availableExt) + if (extension.hasUpdate != hasUpdate) { + installedExtensionsMap[pkgName] = extension.copy( hasUpdate = hasUpdate, repoUrl = availableExt.repoUrl, ) - changed = true } else { - mutInstalledExtensions[index] = installedExt.copy( + installedExtensionsMap[pkgName] = extension.copy( repoUrl = availableExt.repoUrl, ) - changed = true } + changed = true } } if (changed) { - _installedAnimeExtensionsFlow.value = mutInstalledExtensions + _installedExtensionsMapFlow.value = installedExtensionsMap } updatePendingUpdatesCount() } @@ -237,8 +236,7 @@ class AnimeExtensionManager( * @param extension The anime extension to be updated. */ fun updateExtension(extension: AnimeExtension.Installed): Flow { - val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } - ?: return emptyFlow() + val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() return installExtension(availableExt) } @@ -275,28 +273,15 @@ class AnimeExtensionManager( * @param extension the extension to trust */ fun trust(extension: AnimeExtension.Untrusted) { - val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet() - if (extension.pkgName !in untrustedPkgNames) return + _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) - val nowTrustedExtensions = _untrustedExtensionsFlow.value - .filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode } - _untrustedExtensionsFlow.value -= nowTrustedExtensions - - launchNow { - nowTrustedExtensions - .map { extension -> - async { - AnimeExtensionLoader.loadExtensionFromPkgName( - context, - extension.pkgName, - ) - }.await() - } - .filterIsInstance() - .forEach { registerNewExtension(it.extension) } - } + _untrustedExtensionsMapFlow.value -= extension.pkgName + + AnimeExtensionLoader.loadExtensionFromPkgName(context, extension.pkgName) + .let { it as? AnimeLoadResult.Success } + ?.let { registerNewExtension(it.extension) } } /** @@ -305,7 +290,7 @@ class AnimeExtensionManager( * @param extension The anime extension to be registered. */ private fun registerNewExtension(extension: AnimeExtension.Installed) { - _installedAnimeExtensionsFlow.value += extension + _installedExtensionsMapFlow.value += extension } /** @@ -315,13 +300,7 @@ class AnimeExtensionManager( * @param extension The anime extension to be registered. */ private fun registerUpdatedExtension(extension: AnimeExtension.Installed) { - val mutInstalledAnimeExtensions = _installedAnimeExtensionsFlow.value.toMutableList() - val oldAnimeExtension = mutInstalledAnimeExtensions.find { it.pkgName == extension.pkgName } - if (oldAnimeExtension != null) { - mutInstalledAnimeExtensions -= oldAnimeExtension - } - mutInstalledAnimeExtensions += extension - _installedAnimeExtensionsFlow.value = mutInstalledAnimeExtensions + _installedExtensionsMapFlow.value += extension } /** @@ -331,14 +310,8 @@ class AnimeExtensionManager( * @param pkgName The package name of the uninstalled application. */ private fun unregisterAnimeExtension(pkgName: String) { - val installedAnimeExtension = _installedAnimeExtensionsFlow.value.find { it.pkgName == pkgName } - if (installedAnimeExtension != null) { - _installedAnimeExtensionsFlow.value -= installedAnimeExtension - } - val untrustedAnimeExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName } - if (untrustedAnimeExtension != null) { - _untrustedExtensionsFlow.value -= untrustedAnimeExtension - } + _installedExtensionsMapFlow.value -= pkgName + _untrustedExtensionsMapFlow.value -= pkgName } /** @@ -357,14 +330,9 @@ class AnimeExtensionManager( } override fun onExtensionUntrusted(extension: AnimeExtension.Untrusted) { - val installedExtension = _installedAnimeExtensionsFlow.value - .find { it.pkgName == extension.pkgName } - - if (installedExtension != null) { - _installedAnimeExtensionsFlow.value -= installedExtension - } else { - _untrustedExtensionsFlow.value += extension - } + _installedExtensionsMapFlow.value -= extension.pkgName + _untrustedExtensionsMapFlow.value += extension + updatePendingUpdatesCount() } override fun onPackageUninstalled(pkgName: String) { @@ -388,17 +356,26 @@ class AnimeExtensionManager( private fun AnimeExtension.Installed.updateExists( availableExtension: AnimeExtension.Available? = null, ): Boolean { - val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } + val availableExt = availableExtension + ?: _availableExtensionsMapFlow.value[pkgName] ?: return false return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) } private fun updatePendingUpdatesCount() { - val pendingUpdateCount = _installedAnimeExtensionsFlow.value.count { it.hasUpdate } + val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate } preferences.animeExtensionUpdatesCount().set(pendingUpdateCount) if (pendingUpdateCount == 0) { ExtensionUpdateNotifier(context).dismiss() } } + + private operator fun Map.plus(extension: T) = plus(extension.pkgName to extension) + + private fun StateFlow>.mapExtensions( + scope: CoroutineScope, + ): StateFlow> { + return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList()) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt index 76a2a91531..d8323868d7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt @@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async import logcat.LogPriority -import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.system.logcat /** @@ -23,8 +18,7 @@ import tachiyomi.core.common.util.system.logcat * * @param listener The listener that should be notified of extension installation events. */ -internal class AnimeExtensionInstallReceiver(private val listener: Listener) : - BroadcastReceiver() { +internal class AnimeExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { /** * Registers this broadcast receiver @@ -36,16 +30,15 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : /** * Returns the intent filter this receiver should subscribe to. */ - private val filter - get() = IntentFilter().apply { - addAction(Intent.ACTION_PACKAGE_ADDED) - addAction(Intent.ACTION_PACKAGE_REPLACED) - addAction(Intent.ACTION_PACKAGE_REMOVED) - addAction(ACTION_EXTENSION_ADDED) - addAction(ACTION_EXTENSION_REPLACED) - addAction(ACTION_EXTENSION_REMOVED) - addDataScheme("package") - } + private val filter = IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(ACTION_EXTENSION_ADDED) + addAction(ACTION_EXTENSION_REPLACED) + addAction(ACTION_EXTENSION_REMOVED) + addDataScheme("package") + } /** * Called when one of the events of the [filter] is received. When the package is an extension, @@ -58,26 +51,17 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { if (isReplacing(intent)) return - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is AnimeLoadResult.Success -> listener.onExtensionInstalled( - result.extension, - ) - - is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted( - result.extension, - ) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is AnimeLoadResult.Success -> listener.onExtensionInstalled(result.extension) + is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension) - is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension) + is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -106,18 +90,13 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : * @param context The application context. * @param intent The intent containing the package name of the extension. */ - private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult { + private fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult { val pkgName = getPackageNameFromIntent(intent) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } return AnimeLoadResult.Error } - return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { - AnimeExtensionLoader.loadExtensionFromPkgName( - context, - pkgName, - ) - }.await() + return AnimeExtensionLoader.loadExtensionFromPkgName(context, pkgName) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt index e1bd67ff5f..7c1b3235e7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt @@ -13,14 +13,17 @@ import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstallReceiver import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionInstaller import eu.kanade.tachiyomi.extension.manga.util.MangaExtensionLoader import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.async +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import logcat.LogPriority -import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.lang.withUIContext import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.source.manga.model.StubMangaSource @@ -42,6 +45,8 @@ class MangaExtensionManager( private val trustExtension: TrustMangaExtension = Injekt.get(), ) { + val scope = CoroutineScope(SupervisorJob()) + private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -57,25 +62,36 @@ class MangaExtensionManager( private val iconMap = mutableMapOf() - private val _installedExtensionsFlow = MutableStateFlow(emptyList()) - val installedExtensionsFlow = _installedExtensionsFlow.asStateFlow() + private val _installedExtensionsMapFlow = MutableStateFlow(emptyMap()) + val installedExtensionsFlow = _installedExtensionsMapFlow.mapExtensions(scope) + + private val _availableExtensionsMapFlow = MutableStateFlow(emptyMap()) + val availableExtensionsFlow = _availableExtensionsMapFlow.mapExtensions(scope) + + private val _untrustedExtensionsMapFlow = MutableStateFlow(emptyMap()) + val untrustedExtensionsFlow = _untrustedExtensionsMapFlow.mapExtensions(scope) + + init { + initExtensions() + MangaExtensionInstallReceiver(InstallationListener()).register(context) + } private var subLanguagesEnabledOnFirstRun = preferences.enabledLanguages().isSet() fun getAppIconForSource(sourceId: Long): Drawable? { - val pkgName = _installedExtensionsFlow.value.find { ext -> ext.sources.any { it.id == sourceId } }?.pkgName - if (pkgName != null) { - return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { - MangaExtensionLoader.getMangaExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo - .loadIcon(context.packageManager) + val pkgName = _installedExtensionsMapFlow.value.values + .find { ext -> + ext.sources.any { it.id == sourceId } } + ?.pkgName + ?: return null + + return iconMap[pkgName] ?: iconMap.getOrPut(pkgName) { + MangaExtensionLoader.getMangaExtensionPackageInfoFromPkgName(context, pkgName)!!.applicationInfo + .loadIcon(context.packageManager) } - return null } - private val _availableExtensionsFlow = MutableStateFlow(emptyList()) - val availableExtensionsFlow = _availableExtensionsFlow.asStateFlow() - private var availableExtensionsSourcesData: Map = emptyMap() private fun setupAvailableExtensionsSourcesDataMap(extensions: List) { @@ -87,33 +103,25 @@ class MangaExtensionManager( fun getSourceData(id: Long) = availableExtensionsSourcesData[id] - private val _untrustedExtensionsFlow = MutableStateFlow(emptyList()) - val untrustedExtensionsFlow = _untrustedExtensionsFlow.asStateFlow() - - init { - initExtensions() - MangaExtensionInstallReceiver(InstallationListener()).register(context) - } - /** * Loads and registers the installed extensions. */ private fun initExtensions() { val extensions = MangaExtensionLoader.loadMangaExtensions(context) - _installedExtensionsFlow.value = extensions + _installedExtensionsMapFlow.value = extensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } - _untrustedExtensionsFlow.value = extensions + _untrustedExtensionsMapFlow.value = extensions .filterIsInstance() - .map { it.extension } + .associate { it.extension.pkgName to it.extension } _isInitialized.value = true } /** - * Finds the available extensions in the [api] and updates [availableExtensions]. + * Finds the available extensions in the [api] and updates [_availableExtensionsMapFlow]. */ suspend fun findAvailableExtensions() { val extensions: List = try { @@ -126,7 +134,7 @@ class MangaExtensionManager( enableAdditionalSubLanguages(extensions) - _availableExtensionsFlow.value = extensions + _availableExtensionsMapFlow.value = extensions.associateBy { it.pkgName } updatedInstalledExtensionsStatuses(extensions) setupAvailableExtensionsSourcesDataMap(extensions) } @@ -174,35 +182,32 @@ class MangaExtensionManager( return } - val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() + val installedExtensionsMap = _installedExtensionsMapFlow.value.toMutableMap() var changed = false - for ((index, installedExt) in mutInstalledExtensions.withIndex()) { - val pkgName = installedExt.pkgName + for ((pkgName, extension) in installedExtensionsMap) { val availableExt = availableExtensions.find { it.pkgName == pkgName } - if (availableExt == null && !installedExt.isObsolete) { - mutInstalledExtensions[index] = installedExt.copy(isObsolete = true) + if (availableExt == null && !extension.isObsolete) { + installedExtensionsMap[pkgName] = extension.copy(isObsolete = true) changed = true } else if (availableExt != null) { - val hasUpdate = installedExt.updateExists(availableExt) - - if (installedExt.hasUpdate != hasUpdate) { - mutInstalledExtensions[index] = installedExt.copy( + val hasUpdate = extension.updateExists(availableExt) + if (extension.hasUpdate != hasUpdate) { + installedExtensionsMap[pkgName] = extension.copy( hasUpdate = hasUpdate, repoUrl = availableExt.repoUrl, ) - changed = true } else { - mutInstalledExtensions[index] = installedExt.copy( + installedExtensionsMap[pkgName] = extension.copy( repoUrl = availableExt.repoUrl, ) - changed = true } + changed = true } } if (changed) { - _installedExtensionsFlow.value = mutInstalledExtensions + _installedExtensionsMapFlow.value = installedExtensionsMap } updatePendingUpdatesCount() } @@ -226,8 +231,7 @@ class MangaExtensionManager( * @param extension The extension to be updated. */ fun updateExtension(extension: MangaExtension.Installed): Flow { - val availableExt = _availableExtensionsFlow.value.find { it.pkgName == extension.pkgName } - ?: return emptyFlow() + val availableExt = _availableExtensionsMapFlow.value[extension.pkgName] ?: return emptyFlow() return installExtension(availableExt) } @@ -264,28 +268,15 @@ class MangaExtensionManager( * @param extension the extension to trust */ fun trust(extension: MangaExtension.Untrusted) { - val untrustedPkgNames = _untrustedExtensionsFlow.value.map { it.pkgName }.toSet() - if (extension.pkgName !in untrustedPkgNames) return + _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) - val nowTrustedExtensions = _untrustedExtensionsFlow.value - .filter { it.pkgName == extension.pkgName && it.versionCode == extension.versionCode } - _untrustedExtensionsFlow.value -= nowTrustedExtensions - - launchNow { - nowTrustedExtensions - .map { extension -> - async { - MangaExtensionLoader.loadMangaExtensionFromPkgName( - context, - extension.pkgName, - ) - }.await() - } - .filterIsInstance() - .forEach { registerNewExtension(it.extension) } - } + _untrustedExtensionsMapFlow.value -= extension.pkgName + + MangaExtensionLoader.loadMangaExtensionFromPkgName(context, extension.pkgName) + .let { it as? MangaLoadResult.Success } + ?.let { registerNewExtension(it.extension) } } /** @@ -294,7 +285,7 @@ class MangaExtensionManager( * @param extension The extension to be registered. */ private fun registerNewExtension(extension: MangaExtension.Installed) { - _installedExtensionsFlow.value += extension + _installedExtensionsMapFlow.value += extension } /** @@ -304,13 +295,7 @@ class MangaExtensionManager( * @param extension The extension to be registered. */ private fun registerUpdatedExtension(extension: MangaExtension.Installed) { - val mutInstalledExtensions = _installedExtensionsFlow.value.toMutableList() - val oldExtension = mutInstalledExtensions.find { it.pkgName == extension.pkgName } - if (oldExtension != null) { - mutInstalledExtensions -= oldExtension - } - mutInstalledExtensions += extension - _installedExtensionsFlow.value = mutInstalledExtensions + _installedExtensionsMapFlow.value += extension } /** @@ -320,14 +305,8 @@ class MangaExtensionManager( * @param pkgName The package name of the uninstalled application. */ private fun unregisterExtension(pkgName: String) { - val installedExtension = _installedExtensionsFlow.value.find { it.pkgName == pkgName } - if (installedExtension != null) { - _installedExtensionsFlow.value -= installedExtension - } - val untrustedExtension = _untrustedExtensionsFlow.value.find { it.pkgName == pkgName } - if (untrustedExtension != null) { - _untrustedExtensionsFlow.value -= untrustedExtension - } + _installedExtensionsMapFlow.value -= pkgName + _untrustedExtensionsMapFlow.value -= pkgName } /** @@ -346,14 +325,9 @@ class MangaExtensionManager( } override fun onExtensionUntrusted(extension: MangaExtension.Untrusted) { - val installedExtension = _installedExtensionsFlow.value - .find { it.pkgName == extension.pkgName } - - if (installedExtension != null) { - _installedExtensionsFlow.value -= installedExtension - } else { - _untrustedExtensionsFlow.value += extension - } + _installedExtensionsMapFlow.value -= extension.pkgName + _untrustedExtensionsMapFlow.value += extension + updatePendingUpdatesCount() } override fun onPackageUninstalled(pkgName: String) { @@ -377,17 +351,26 @@ class MangaExtensionManager( private fun MangaExtension.Installed.updateExists( availableExtension: MangaExtension.Available? = null, ): Boolean { - val availableExt = availableExtension ?: _availableExtensionsFlow.value.find { it.pkgName == pkgName } + val availableExt = availableExtension + ?: _availableExtensionsMapFlow.value[pkgName] ?: return false return (availableExt.versionCode > versionCode || availableExt.libVersion > libVersion) } private fun updatePendingUpdatesCount() { - val pendingUpdateCount = _installedExtensionsFlow.value.count { it.hasUpdate } + val pendingUpdateCount = _installedExtensionsMapFlow.value.values.count { it.hasUpdate } preferences.mangaExtensionUpdatesCount().set(pendingUpdateCount) if (pendingUpdateCount == 0) { ExtensionUpdateNotifier(context).dismiss() } } + + private operator fun Map.plus(extension: T) = plus(extension.pkgName to extension) + + private fun StateFlow>.mapExtensions( + scope: CoroutineScope, + ): StateFlow> { + return map { it.values.toList() }.stateIn(scope, SharingStarted.Lazily, value.values.toList()) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt index 2a94580bdd..1bb300ba39 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt @@ -9,12 +9,7 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async import logcat.LogPriority -import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.system.logcat /** @@ -23,8 +18,7 @@ import tachiyomi.core.common.util.system.logcat * * @param listener The listener that should be notified of extension installation events. */ -internal class MangaExtensionInstallReceiver(private val listener: Listener) : - BroadcastReceiver() { +internal class MangaExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { /** * Registers this broadcast receiver @@ -36,16 +30,15 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) : /** * Returns the intent filter this receiver should subscribe to. */ - private val filter - get() = IntentFilter().apply { - addAction(Intent.ACTION_PACKAGE_ADDED) - addAction(Intent.ACTION_PACKAGE_REPLACED) - addAction(Intent.ACTION_PACKAGE_REMOVED) - addAction(ACTION_EXTENSION_ADDED) - addAction(ACTION_EXTENSION_REPLACED) - addAction(ACTION_EXTENSION_REMOVED) - addDataScheme("package") - } + private val filter = IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(ACTION_EXTENSION_ADDED) + addAction(ACTION_EXTENSION_REPLACED) + addAction(ACTION_EXTENSION_REMOVED) + addDataScheme("package") + } /** * Called when one of the events of the [filter] is received. When the package is an extension, @@ -58,26 +51,17 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) : Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { if (isReplacing(intent)) return - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is MangaLoadResult.Success -> listener.onExtensionInstalled( - result.extension, - ) - - is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted( - result.extension, - ) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionInstalled(result.extension) + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - launchNow { - when (val result = getExtensionFromIntent(context, intent)) { - is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) - is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} - } + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -106,18 +90,13 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) : * @param context The application context. * @param intent The intent containing the package name of the extension. */ - private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult { + private fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult { val pkgName = getPackageNameFromIntent(intent) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } return MangaLoadResult.Error } - return GlobalScope.async(Dispatchers.Default, CoroutineStart.DEFAULT) { - MangaExtensionLoader.loadMangaExtensionFromPkgName( - context, - pkgName, - ) - }.await() + return MangaExtensionLoader.loadMangaExtensionFromPkgName(context, pkgName) } /** From d07bd716789568827ebdb67f3b2a431067096183 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 22:27:52 +0200 Subject: [PATCH 11/74] Trust extension by repo Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../java/eu/kanade/domain/DomainModule.kt | 4 +-- .../anime/interactor/TrustAnimeExtension.kt | 14 ++++---- .../manga/interactor/TrustMangaExtension.kt | 14 ++++---- .../extension/anime/AnimeExtensionManager.kt | 2 +- .../util/AnimeExtensionInstallReceiver.kt | 33 ++++++++++--------- .../anime/util/AnimeExtensionLoader.kt | 7 ++-- .../extension/manga/MangaExtensionManager.kt | 2 +- .../util/MangaExtensionInstallReceiver.kt | 33 ++++++++++--------- .../manga/util/MangaExtensionLoader.kt | 7 ++-- .../extension/AnimeExtensionsScreenModel.kt | 5 ++- .../extension/MangaExtensionsScreenModel.kt | 5 ++- 11 files changed, 70 insertions(+), 56 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index e3e1767704..c54db27683 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -345,8 +345,8 @@ class DomainModule : InjektModule { addFactory { ToggleLanguage(get()) } addFactory { ToggleMangaSource(get()) } addFactory { ToggleMangaSourcePin(get()) } - addFactory { TrustAnimeExtension(get()) } - addFactory { TrustMangaExtension(get()) } + addFactory { TrustAnimeExtension(get(), get()) } + addFactory { TrustMangaExtension(get(), get()) } addFactory { ExtensionRepoService(get(), get()) } diff --git a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt index 2cf6cd99d8..ad31d132a8 100644 --- a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt +++ b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt @@ -3,16 +3,18 @@ package eu.kanade.domain.extension.anime.interactor import android.content.pm.PackageInfo import androidx.core.content.pm.PackageInfoCompat import eu.kanade.domain.source.service.SourcePreferences +import mihon.domain.extensionrepo.anime.repository.AnimeExtensionRepoRepository import tachiyomi.core.common.preference.getAndSet class TrustAnimeExtension( + private val animeExtensionRepoRepository: AnimeExtensionRepoRepository, private val preferences: SourcePreferences, ) { - fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean { - val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash" - return key in preferences.trustedExtensions().get() || - signatureHash == officialSignature + suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List): Boolean { + val trustedFingerprints = animeExtensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet() + val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}" + return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get() } fun trust(pkgName: String, versionCode: Long, signatureHash: String) { @@ -20,9 +22,7 @@ class TrustAnimeExtension( // Remove previously trusted versions val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet() - removed.also { - it += "$pkgName:$versionCode:$signatureHash" - } + removed.also { it += "$pkgName:$versionCode:$signatureHash" } } } diff --git a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt index daa5fb8631..a6e3afb087 100644 --- a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt +++ b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt @@ -3,16 +3,18 @@ package eu.kanade.domain.extension.manga.interactor import android.content.pm.PackageInfo import androidx.core.content.pm.PackageInfoCompat import eu.kanade.domain.source.service.SourcePreferences +import mihon.domain.extensionrepo.manga.repository.MangaExtensionRepoRepository import tachiyomi.core.common.preference.getAndSet class TrustMangaExtension( + private val mangaExtensionRepoRepository: MangaExtensionRepoRepository, private val preferences: SourcePreferences, ) { - fun isTrusted(pkgInfo: PackageInfo, signatureHash: String): Boolean { - val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:$signatureHash" - return key in preferences.trustedExtensions().get() || - signatureHash == officialSignature + suspend fun isTrusted(pkgInfo: PackageInfo, fingerprints: List): Boolean { + val trustedFingerprints = mangaExtensionRepoRepository.getAll().map { it.signingKeyFingerprint }.toHashSet() + val key = "${pkgInfo.packageName}:${PackageInfoCompat.getLongVersionCode(pkgInfo)}:${fingerprints.last()}" + return trustedFingerprints.any { fingerprints.contains(it) } || key in preferences.trustedExtensions().get() } fun trust(pkgName: String, versionCode: Long, signatureHash: String) { @@ -20,9 +22,7 @@ class TrustMangaExtension( // Remove previously trusted versions val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet() - removed.also { - it += "$pkgName:$versionCode:$signatureHash" - } + removed.also { it += "$pkgName:$versionCode:$signatureHash" } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt index ad51eda0a1..502e85fe3b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/AnimeExtensionManager.kt @@ -272,7 +272,7 @@ class AnimeExtensionManager( * * @param extension the extension to trust */ - fun trust(extension: AnimeExtension.Untrusted) { + suspend fun trust(extension: AnimeExtension.Untrusted) { _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt index d8323868d7..9405016701 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionInstallReceiver.kt @@ -9,6 +9,9 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import logcat.LogPriority import tachiyomi.core.common.util.system.logcat @@ -20,16 +23,12 @@ import tachiyomi.core.common.util.system.logcat */ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { - /** - * Registers this broadcast receiver - */ + val scope = CoroutineScope(SupervisorJob()) + fun register(context: Context) { ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED) } - /** - * Returns the intent filter this receiver should subscribe to. - */ private val filter = IntentFilter().apply { addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_REPLACED) @@ -51,17 +50,21 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : B Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { if (isReplacing(intent)) return - when (val result = getExtensionFromIntent(context, intent)) { - is AnimeLoadResult.Success -> listener.onExtensionInstalled(result.extension) - is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} + scope.launch { + when (val result = getExtensionFromIntent(context, intent)) { + is AnimeLoadResult.Success -> listener.onExtensionInstalled(result.extension) + is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} + } } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - when (val result = getExtensionFromIntent(context, intent)) { - is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension) - is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} + scope.launch { + when (val result = getExtensionFromIntent(context, intent)) { + is AnimeLoadResult.Success -> listener.onExtensionUpdated(result.extension) + is AnimeLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} + } } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -90,7 +93,7 @@ internal class AnimeExtensionInstallReceiver(private val listener: Listener) : B * @param context The application context. * @param intent The intent containing the package name of the extension. */ - private fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult { + private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): AnimeLoadResult { val pkgName = getPackageNameFromIntent(intent) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt index 9995d97d20..2206683351 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/anime/util/AnimeExtensionLoader.kt @@ -177,7 +177,7 @@ internal object AnimeExtensionLoader { * Attempts to load an extension from the given package name. It checks if the extension * contains the required feature flag before trying to load it. */ - fun loadExtensionFromPkgName(context: Context, pkgName: String): AnimeLoadResult { + suspend fun loadExtensionFromPkgName(context: Context, pkgName: String): AnimeLoadResult { val extensionPackage = getAnimeExtensionInfoFromPkgName(context, pkgName) if (extensionPackage == null) { logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" } @@ -234,7 +234,8 @@ internal object AnimeExtensionLoader { * @param context The application context. * @param extensionInfo The extension to load. */ - private fun loadExtension(context: Context, extensionInfo: AnimeExtensionInfo): AnimeLoadResult { + @Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount") + private suspend fun loadExtension(context: Context, extensionInfo: AnimeExtensionInfo): AnimeLoadResult { val pkgManager = context.packageManager val pkgInfo = extensionInfo.packageInfo @@ -264,7 +265,7 @@ internal object AnimeExtensionLoader { if (signatures.isNullOrEmpty()) { logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } return AnimeLoadResult.Error - } else if (!trustExtension.isTrusted(pkgInfo, signatures.last())) { + } else if (!trustExtension.isTrusted(pkgInfo, signatures)) { val extension = AnimeExtension.Untrusted( extName, pkgName, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt index 7c1b3235e7..cd2018434d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/MangaExtensionManager.kt @@ -267,7 +267,7 @@ class MangaExtensionManager( * * @param extension the extension to trust */ - fun trust(extension: MangaExtension.Untrusted) { + suspend fun trust(extension: MangaExtension.Untrusted) { _untrustedExtensionsMapFlow.value[extension.pkgName] ?: return trustExtension.trust(extension.pkgName, extension.versionCode, extension.signatureHash) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt index 1bb300ba39..e0592999c7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionInstallReceiver.kt @@ -9,6 +9,9 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch import logcat.LogPriority import tachiyomi.core.common.util.system.logcat @@ -20,16 +23,12 @@ import tachiyomi.core.common.util.system.logcat */ internal class MangaExtensionInstallReceiver(private val listener: Listener) : BroadcastReceiver() { - /** - * Registers this broadcast receiver - */ + val scope = CoroutineScope(SupervisorJob()) + fun register(context: Context) { ContextCompat.registerReceiver(context, this, filter, ContextCompat.RECEIVER_NOT_EXPORTED) } - /** - * Returns the intent filter this receiver should subscribe to. - */ private val filter = IntentFilter().apply { addAction(Intent.ACTION_PACKAGE_ADDED) addAction(Intent.ACTION_PACKAGE_REPLACED) @@ -51,17 +50,21 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) : B Intent.ACTION_PACKAGE_ADDED, ACTION_EXTENSION_ADDED -> { if (isReplacing(intent)) return - when (val result = getExtensionFromIntent(context, intent)) { - is MangaLoadResult.Success -> listener.onExtensionInstalled(result.extension) - is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} + scope.launch { + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionInstalled(result.extension) + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} + } } } Intent.ACTION_PACKAGE_REPLACED, ACTION_EXTENSION_REPLACED -> { - when (val result = getExtensionFromIntent(context, intent)) { - is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) - is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) - else -> {} + scope.launch { + when (val result = getExtensionFromIntent(context, intent)) { + is MangaLoadResult.Success -> listener.onExtensionUpdated(result.extension) + is MangaLoadResult.Untrusted -> listener.onExtensionUntrusted(result.extension) + else -> {} + } } } Intent.ACTION_PACKAGE_REMOVED, ACTION_EXTENSION_REMOVED -> { @@ -90,7 +93,7 @@ internal class MangaExtensionInstallReceiver(private val listener: Listener) : B * @param context The application context. * @param intent The intent containing the package name of the extension. */ - private fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult { + private suspend fun getExtensionFromIntent(context: Context, intent: Intent?): MangaLoadResult { val pkgName = getPackageNameFromIntent(intent) if (pkgName == null) { logcat(LogPriority.WARN) { "Package name not found" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt index a7b6cfa222..92fd6e4663 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/manga/util/MangaExtensionLoader.kt @@ -186,7 +186,7 @@ internal object MangaExtensionLoader { * Attempts to load an extension from the given package name. It checks if the extension * contains the required feature flag before trying to load it. */ - fun loadMangaExtensionFromPkgName(context: Context, pkgName: String): MangaLoadResult { + suspend fun loadMangaExtensionFromPkgName(context: Context, pkgName: String): MangaLoadResult { val extensionPackage = getMangaExtensionInfoFromPkgName(context, pkgName) if (extensionPackage == null) { logcat(LogPriority.ERROR) { "Extension package is not found ($pkgName)" } @@ -243,7 +243,8 @@ internal object MangaExtensionLoader { * @param context The application context. * @param extensionInfo The extension to load. */ - private fun loadMangaExtension(context: Context, extensionInfo: MangaExtensionInfo): MangaLoadResult { + @Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount") + private suspend fun loadMangaExtension(context: Context, extensionInfo: MangaExtensionInfo): MangaLoadResult { val pkgManager = context.packageManager val pkgInfo = extensionInfo.packageInfo val appInfo = pkgInfo.applicationInfo @@ -274,7 +275,7 @@ internal object MangaExtensionLoader { if (signatures.isNullOrEmpty()) { logcat(LogPriority.WARN) { "Package $pkgName isn't signed" } return MangaLoadResult.Error - } else if (!trustExtension.isTrusted(pkgInfo, signatures.last())) { + } else if (!trustExtension.isTrusted(pkgInfo, signatures)) { val extension = MangaExtension.Untrusted( extName, pkgName, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt index 6a3104b32b..33b04d08fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/extension/AnimeExtensionsScreenModel.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import tachiyomi.core.common.util.lang.launchIO import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt @@ -212,7 +213,9 @@ class AnimeExtensionsScreenModel( } fun trustExtension(extension: AnimeExtension.Untrusted) { - extensionManager.trust(extension) + screenModelScope.launch { + extensionManager.trust(extension) + } } @Immutable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt index 0a25d030cc..83ca025312 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/extension/MangaExtensionsScreenModel.kt @@ -27,6 +27,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import tachiyomi.core.common.util.lang.launchIO import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt @@ -214,7 +215,9 @@ class MangaExtensionsScreenModel( } fun trustExtension(extension: MangaExtension.Untrusted) { - extensionManager.trust(extension) + screenModelScope.launch { + extensionManager.trust(extension) + } } @Immutable From 5419d987f836e5209e1e3a6ee4c99adbe633c57b Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 22:29:43 +0200 Subject: [PATCH 12/74] Remove some legacy folder/file name lookup for download Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../data/download/manga/MangaDownloadProvider.kt | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt index ac562b3464..d7e0c35933 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt @@ -165,21 +165,12 @@ class MangaDownloadProvider( */ fun getValidChapterDirNames(chapterName: String, chapterScanlator: String?): List { val chapterDirName = getChapterDirName(chapterName, chapterScanlator) - return buildList(4) { + return buildList(2) { // Folder of images add(chapterDirName) // Archived chapters add("$chapterDirName.cbz") - - if (chapterScanlator.isNullOrBlank()) { - // Previously null scanlator fields were converted to "" due to a bug - add("_$chapterDirName") - add("_$chapterDirName.cbz") - } else { - // Legacy chapter directory name used in v0.9.2 and before - add(DiskUtil.buildValidFilename(chapterName)) - } } } } From f9d340819a0ec4b5de0e84fe1127f007f009b605 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 22:39:59 +0200 Subject: [PATCH 13/74] Massively improve findFile performance * Massively improve findFile performance * Update libs.versions.toml --------- Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Co-authored-by: Radon Rosborough <6559064+raxod502@users.noreply.github.com> --- .../data/download/anime/AnimeDownloadProvider.kt | 8 ++++---- .../data/download/manga/MangaDownloadProvider.kt | 8 ++++---- .../tachiyomi/data/download/manga/MangaDownloader.kt | 2 +- .../eu/kanade/tachiyomi/ui/player/loader/EpisodeLoader.kt | 4 ++-- gradle/libs.versions.toml | 2 +- .../source/local/entries/manga/LocalMangaSource.kt | 4 ++-- .../source/local/io/anime/LocalAnimeSourceFileSystem.kt | 4 ++-- .../source/local/io/manga/LocalMangaSourceFileSystem.kt | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt index 52ddf08f3d..215692097f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadProvider.kt @@ -57,7 +57,7 @@ class AnimeDownloadProvider( * @param source the source to query. */ fun findSourceDir(source: AnimeSource): UniFile? { - return downloadsDir?.findFile(getSourceDirName(source), true) + return downloadsDir?.findFile(getSourceDirName(source)) } /** @@ -68,7 +68,7 @@ class AnimeDownloadProvider( */ fun findAnimeDir(animeTitle: String, source: AnimeSource): UniFile? { val sourceDir = findSourceDir(source) - return sourceDir?.findFile(getAnimeDirName(animeTitle), true) + return sourceDir?.findFile(getAnimeDirName(animeTitle)) } /** @@ -87,7 +87,7 @@ class AnimeDownloadProvider( ): UniFile? { val animeDir = findAnimeDir(animeTitle, source) return getValidEpisodeDirNames(episodeName, episodeScanlator).asSequence() - .mapNotNull { animeDir?.findFile(it, true) } + .mapNotNull { animeDir?.findFile(it) } .firstOrNull() } @@ -102,7 +102,7 @@ class AnimeDownloadProvider( val animeDir = findAnimeDir(anime.title, source) ?: return null to emptyList() return animeDir to episodes.mapNotNull { episode -> getValidEpisodeDirNames(episode.name, episode.scanlator).asSequence() - .mapNotNull { animeDir.findFile(it, true) } + .mapNotNull { animeDir.findFile(it) } .firstOrNull() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt index d7e0c35933..d1b6b53007 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadProvider.kt @@ -57,7 +57,7 @@ class MangaDownloadProvider( * @param source the source to query. */ fun findSourceDir(source: MangaSource): UniFile? { - return downloadsDir?.findFile(getSourceDirName(source), true) + return downloadsDir?.findFile(getSourceDirName(source)) } /** @@ -68,7 +68,7 @@ class MangaDownloadProvider( */ fun findMangaDir(mangaTitle: String, source: MangaSource): UniFile? { val sourceDir = findSourceDir(source) - return sourceDir?.findFile(getMangaDirName(mangaTitle), true) + return sourceDir?.findFile(getMangaDirName(mangaTitle)) } /** @@ -87,7 +87,7 @@ class MangaDownloadProvider( ): UniFile? { val mangaDir = findMangaDir(mangaTitle, source) return getValidChapterDirNames(chapterName, chapterScanlator).asSequence() - .mapNotNull { mangaDir?.findFile(it, true) } + .mapNotNull { mangaDir?.findFile(it) } .firstOrNull() } @@ -102,7 +102,7 @@ class MangaDownloadProvider( val mangaDir = findMangaDir(manga.title, source) ?: return null to emptyList() return mangaDir to chapters.mapNotNull { chapter -> getValidChapterDirNames(chapter.name, chapter.scanlator).asSequence() - .mapNotNull { mangaDir.findFile(it, true) } + .mapNotNull { mangaDir.findFile(it) } .firstOrNull() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 254b2ebd89..0432c33da4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -694,7 +694,7 @@ class MangaDownloader( ) // Remove the old file - dir.findFile(COMIC_INFO_FILE, true)?.delete() + dir.findFile(COMIC_INFO_FILE)?.delete() dir.createFile(COMIC_INFO_FILE)!!.openOutputStream().use { val comicInfoString = xml.encodeToString(ComicInfo.serializer(), comicInfo) it.write(comicInfoString.toByteArray()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/loader/EpisodeLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/loader/EpisodeLoader.kt index de4e1b0890..d8ea7c6aa5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/loader/EpisodeLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/loader/EpisodeLoader.kt @@ -111,8 +111,8 @@ class EpisodeLoader { val (animeDirName, episodeName) = episode.url.split('/', limit = 2) val fileSystem: LocalAnimeSourceFileSystem = Injekt.get() val videoFile = fileSystem.getBaseDirectory() - ?.findFile(animeDirName, true) - ?.findFile(episodeName, true) + ?.findFile(animeDirName) + ?.findFile(episodeName) val videoUri = videoFile!!.uri val video = Video( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a4c6c6bc8d..3e0bf6da11 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,7 +31,7 @@ quickjs-android = "app.cash.quickjs:quickjs-android:0.9.2" jsoup = "org.jsoup:jsoup:1.17.2" disklrucache = "com.jakewharton:disklrucache:2.0.2" -unifile = "com.github.tachiyomiorg:unifile:7c257e1c64" +unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc" common-compress = "org.apache.commons:commons-compress:1.26.2" junrar = "com.github.junrar:junrar:7.5.5" diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt index ab7023e345..9dfd46db59 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt @@ -337,8 +337,8 @@ actual class LocalMangaSource( try { val (mangaDirName, chapterName) = chapter.url.split('/', limit = 2) return fileSystem.getBaseDirectory() - ?.findFile(mangaDirName, true) - ?.findFile(chapterName, true) + ?.findFile(mangaDirName) + ?.findFile(chapterName) ?.let(Format.Companion::valueOf) ?: throw Exception(context.stringResource(MR.strings.chapter_not_found)) } catch (e: Format.UnknownFormatException) { diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt index a04daa69a7..acc2bc0986 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/anime/LocalAnimeSourceFileSystem.kt @@ -17,13 +17,13 @@ actual class LocalAnimeSourceFileSystem( actual fun getAnimeDirectory(name: String): UniFile? { return getBaseDirectory() - ?.findFile(name, true) + ?.findFile(name) ?.takeIf { it.isDirectory } } actual fun getFilesInAnimeDirectory(name: String): List { return getBaseDirectory() - ?.findFile(name, true) + ?.findFile(name) ?.takeIf { it.isDirectory } ?.listFiles().orEmpty().toList() } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt index e75fbcbb53..2856108a89 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/io/manga/LocalMangaSourceFileSystem.kt @@ -17,7 +17,7 @@ actual class LocalMangaSourceFileSystem( actual fun getMangaDirectory(name: String): UniFile? { return getBaseDirectory() - ?.findFile(name, true) + ?.findFile(name) ?.takeIf { it.isDirectory } } From 3eab4e6b9f7be0266b93d1c46ca23e7d449fef94 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 22:40:51 +0200 Subject: [PATCH 14/74] Fix badge count getting cut off on tab title Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../tachiyomi/presentation/core/components/material/Tabs.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt index f74ff909c2..cf6c20da81 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt @@ -6,6 +6,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.sp import tachiyomi.presentation.core.components.Pill @@ -24,6 +25,7 @@ fun TabText( text = text, maxLines = 1, overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, fill = false), ) if (badgeCount != null) { Pill( From 02ccbfd172d7949f15bed59b99bb16b841c7ca71 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 23:04:41 +0200 Subject: [PATCH 15/74] Bump compose version Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../browse/GlobalSearchResultItems.kt | 3 +- .../anime/AnimeExtensionDetailsScreen.kt | 2 +- .../anime/AnimeExtensionFilterScreen.kt | 2 +- .../browse/anime/AnimeExtensionsScreen.kt | 6 +- .../browse/anime/AnimeSourcesFilterScreen.kt | 4 +- .../browse/anime/AnimeSourcesScreen.kt | 4 +- .../browse/anime/GlobalAnimeSearchScreen.kt | 2 + .../browse/anime/MigrateAnimeSourceScreen.kt | 2 +- .../browse/manga/GlobalMangaSearchScreen.kt | 2 + .../manga/MangaExtensionDetailsScreen.kt | 2 +- .../manga/MangaExtensionFilterScreen.kt | 2 +- .../browse/manga/MangaExtensionsScreen.kt | 6 +- .../browse/manga/MangaSourcesFilterScreen.kt | 4 +- .../browse/manga/MangaSourcesScreen.kt | 4 +- .../browse/manga/MigrateMangaSourceScreen.kt | 2 +- .../category/AnimeCategoryScreen.kt | 2 +- .../category/MangaCategoryScreen.kt | 2 +- .../CategoryFloatingActionButton.kt | 5 +- .../presentation/entries/anime/AnimeScreen.kt | 7 +- .../anime/components/AnimeInfoHeader.kt | 4 +- .../presentation/entries/manga/MangaScreen.kt | 7 +- .../manga/components/MangaInfoHeader.kt | 4 +- .../manga/components/ScanlatorFilterDialog.kt | 6 +- .../history/anime/AnimeHistoryScreen.kt | 4 +- .../history/manga/MangaHistoryScreen.kt | 4 +- .../more/onboarding/PermissionStep.kt | 2 +- .../components/ExtensionReposContent.kt | 2 +- .../settings/widget/ListPreferenceWidget.kt | 14 +- .../settings/widget/TriStateListDialog.kt | 14 +- .../track/TrackInfoDialogSelector.kt | 14 +- .../updates/anime/AnimeUpdatesUiItem.kt | 6 +- .../updates/manga/MangaUpdatesUiItem.kt | 6 +- .../kanade/presentation/util/Permissions.kt | 2 +- gradle/compose.versions.toml | 3 +- .../core/components/AdaptiveSheet.kt | 24 +- .../core/components/material/PullRefresh.kt | 269 ++---------------- .../presentation/core/util/LazyListState.kt | 59 +--- 37 files changed, 98 insertions(+), 409 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchResultItems.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchResultItems.kt index b489f9bb58..afda5497ab 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchResultItems.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchResultItems.kt @@ -32,9 +32,10 @@ fun GlobalSearchResultItem( title: String, subtitle: String, onClick: () -> Unit, + modifier: Modifier = Modifier, content: @Composable () -> Unit, ) { - Column { + Column(modifier = modifier) { Row( modifier = Modifier .padding( diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt index a0089a9633..0ed3010298 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionDetailsScreen.kt @@ -189,7 +189,7 @@ private fun AnimeExtensionDetails( key = { it.source.id }, ) { source -> SourceSwitchPreference( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = source, onClickSourcePreferences = onClickSourcePreferences, onClickSource = onClickSource, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionFilterScreen.kt index 048d772d24..de8e87eb5f 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionFilterScreen.kt @@ -58,7 +58,7 @@ private fun AnimeExtensionFilterContent( ) { items(state.languages) { language -> SwitchPreferenceWidget( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), title = LocaleHelper.getSourceDisplayName(language, context), checked = language in state.enabledLanguages, onCheckedChanged = { onClickLang(language) }, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt index d4d48b2e44..8acb39b970 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt @@ -185,14 +185,14 @@ private fun AnimeExtensionContent( } ExtensionHeader( textRes = header.textRes, - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), action = action, ) } is AnimeExtensionUiModel.Header.Text -> { ExtensionHeader( text = header.text, - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), ) } } @@ -211,7 +211,7 @@ private fun AnimeExtensionContent( ) { item -> AnimeExtensionItem( item = item, - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), onClickItem = { when (it) { is AnimeExtension.Available -> onInstallExtension(it) diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesFilterScreen.kt index 2606b68336..8aafc1a4e2 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesFilterScreen.kt @@ -68,7 +68,7 @@ private fun AnimeSourcesFilterContent( contentType = "source-filter-header", ) { AnimeSourcesFilterHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), language = language, enabled = enabled, onClickItem = onClickLanguage, @@ -81,7 +81,7 @@ private fun AnimeSourcesFilterContent( contentType = { "source-filter-item" }, ) { source -> AnimeSourcesFilterItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = source, isEnabled = "${source.id}" !in state.disabledSources, onClickItem = onClickSource, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt index 3fb20cb6d4..d2ce30aa4f 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeSourcesScreen.kt @@ -74,12 +74,12 @@ fun AnimeSourcesScreen( when (model) { is AnimeSourceUiModel.Header -> { AnimeSourceHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), language = model.language, ) } is AnimeSourceUiModel.Item -> AnimeSourceItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = model.source, onClickItem = onClickItem, onLongClickItem = onLongClickItem, diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt index 54c87954a4..8b8eba66f5 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/GlobalAnimeSearchScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.ui.Modifier import eu.kanade.presentation.browse.GlobalSearchErrorResultItem import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem import eu.kanade.presentation.browse.GlobalSearchResultItem @@ -79,6 +80,7 @@ internal fun GlobalSearchContent( } ?: source.name, subtitle = LocaleHelper.getLocalizedDisplayName(source.lang), onClick = { onClickSource(source) }, + modifier = Modifier.animateItem(), ) { when (result) { AnimeSearchItemResult.Loading -> { diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSourceScreen.kt index 3cf8d3161b..c0e4454a06 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/MigrateAnimeSourceScreen.kt @@ -133,7 +133,7 @@ private fun MigrateAnimeSourceList( key = { (source, _) -> "migrate-${source.id}" }, ) { (source, count) -> MigrateAnimeSourceItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = source, count = count, onClickItem = { onClickItem(source) }, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt index 68be3ddf6e..89f1602197 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/GlobalMangaSearchScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.ui.Modifier import eu.kanade.presentation.browse.GlobalSearchErrorResultItem import eu.kanade.presentation.browse.GlobalSearchLoadingResultItem import eu.kanade.presentation.browse.GlobalSearchResultItem @@ -79,6 +80,7 @@ internal fun GlobalSearchContent( } ?: source.name, subtitle = LocaleHelper.getLocalizedDisplayName(source.lang), onClick = { onClickSource(source) }, + modifier = Modifier.animateItem(), ) { when (result) { MangaSearchItemResult.Loading -> { diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt index be6c07fffb..92423923c6 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionDetailsScreen.kt @@ -190,7 +190,7 @@ private fun ExtensionDetails( key = { it.source.id }, ) { source -> SourceSwitchPreference( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = source, onClickSourcePreferences = onClickSourcePreferences, onClickSource = onClickSource, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionFilterScreen.kt index 53263a4d6c..eb0f2903c0 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionFilterScreen.kt @@ -58,7 +58,7 @@ private fun ExtensionFilterContent( ) { items(state.languages) { language -> SwitchPreferenceWidget( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), title = LocaleHelper.getSourceDisplayName(language, context), checked = language in state.enabledLanguages, onCheckedChanged = { onClickLang(language) }, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt index b7d3abfc71..d84c287244 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt @@ -187,14 +187,14 @@ private fun ExtensionContent( } ExtensionHeader( textRes = header.textRes, - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), action = action, ) } is MangaExtensionUiModel.Header.Text -> { ExtensionHeader( text = header.text, - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), ) } } @@ -212,7 +212,7 @@ private fun ExtensionContent( }, ) { item -> ExtensionItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), item = item, onClickItem = { when (it) { diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesFilterScreen.kt index 4455d37cff..b61f9c4106 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesFilterScreen.kt @@ -68,7 +68,7 @@ private fun SourcesFilterContent( contentType = "source-filter-header", ) { SourcesFilterHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), language = language, enabled = enabled, onClickItem = onClickLanguage, @@ -81,7 +81,7 @@ private fun SourcesFilterContent( contentType = { "source-filter-item" }, ) { source -> SourcesFilterItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = source, enabled = "${source.id}" !in state.disabledSources, onClickItem = onClickSource, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt index 181c98c84c..8640bf6bc8 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaSourcesScreen.kt @@ -74,12 +74,12 @@ fun MangaSourcesScreen( when (model) { is MangaSourceUiModel.Header -> { SourceHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), language = model.language, ) } is MangaSourceUiModel.Item -> SourceItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = model.source, onClickItem = onClickItem, onLongClickItem = onLongClickItem, diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSourceScreen.kt index 0c126ed9f4..e68142bb42 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MigrateMangaSourceScreen.kt @@ -133,7 +133,7 @@ private fun MigrateSourceList( key = { (source, _) -> "migrate-${source.id}" }, ) { (source, count) -> MigrateSourceItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), source = source, count = count, onClickItem = { onClickItem(source) }, diff --git a/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt index 97ae103ab2..5184eaa22f 100644 --- a/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/AnimeCategoryScreen.kt @@ -84,7 +84,7 @@ private fun CategoryContent( key = { _, category -> "category-${category.id}" }, ) { index, category -> CategoryListItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), category = category, canMoveUp = index != 0, canMoveDown = index != categories.lastIndex, diff --git a/app/src/main/java/eu/kanade/presentation/category/MangaCategoryScreen.kt b/app/src/main/java/eu/kanade/presentation/category/MangaCategoryScreen.kt index a93c3897e5..459c9ad41e 100644 --- a/app/src/main/java/eu/kanade/presentation/category/MangaCategoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/category/MangaCategoryScreen.kt @@ -84,7 +84,7 @@ private fun CategoryContent( key = { _, category -> "category-${category.id}" }, ) { index, category -> CategoryListItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), category = category, canMoveUp = index != 0, canMoveDown = index != categories.lastIndex, diff --git a/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt b/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt index 17ceff1a30..e3fcadc150 100644 --- a/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt +++ b/app/src/main/java/eu/kanade/presentation/category/components/CategoryFloatingActionButton.kt @@ -10,8 +10,7 @@ import androidx.compose.ui.Modifier import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrollingUp +import tachiyomi.presentation.core.util.shouldExpandFAB @Composable fun CategoryFloatingActionButton( @@ -23,7 +22,7 @@ fun CategoryFloatingActionButton( text = { Text(text = stringResource(MR.strings.action_add)) }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = "") }, onClick = onCreate, - expanded = lazyListState.isScrollingUp() || lazyListState.isScrolledToEnd(), + expanded = lazyListState.shouldExpandFAB(), modifier = modifier, ) } diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt index c24dc654bd..55b22ff105 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt @@ -88,8 +88,7 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrollingUp +import tachiyomi.presentation.core.util.shouldExpandFAB import tachiyomi.source.local.entries.anime.isLocal import java.time.Instant import java.util.concurrent.TimeUnit @@ -393,7 +392,7 @@ private fun AnimeScreenSmallImpl( ) }, onClick = onContinueWatching, - expanded = episodeListState.isScrollingUp() || episodeListState.isScrolledToEnd(), + expanded = episodeListState.shouldExpandFAB(), ) } }, @@ -680,7 +679,7 @@ fun AnimeScreenLargeImpl( }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, onClick = onContinueWatching, - expanded = episodeListState.isScrollingUp() || episodeListState.isScrolledToEnd(), + expanded = episodeListState.shouldExpandFAB(), ) } }, diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt index d143b98369..0d2c9317bc 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeInfoHeader.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Brush import androidx.compose.material.icons.filled.Favorite @@ -43,6 +42,7 @@ import androidx.compose.material.icons.outlined.Sync import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle @@ -651,7 +651,7 @@ private fun TagsChip( modifier: Modifier = Modifier, onClick: () -> Unit, ) { - CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { + CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { SuggestionChip( modifier = modifier, onClick = onClick, diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt index d20848fc58..d957a501ab 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt @@ -81,8 +81,7 @@ import tachiyomi.presentation.core.components.material.ExtendedFloatingActionBut import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrollingUp +import tachiyomi.presentation.core.util.shouldExpandFAB import tachiyomi.source.local.entries.manga.isLocal import java.time.Instant @@ -363,7 +362,7 @@ private fun MangaScreenSmallImpl( }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, onClick = onContinueReading, - expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), + expanded = chapterListState.shouldExpandFAB(), ) } }, @@ -616,7 +615,7 @@ fun MangaScreenLargeImpl( }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, onClick = onContinueReading, - expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), + expanded = chapterListState.shouldExpandFAB(), ) } }, diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt index 2080940cd7..56d79df5f1 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaInfoHeader.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.text.selection.SelectionContainer -import androidx.compose.material.LocalMinimumInteractiveComponentEnforcement import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Brush import androidx.compose.material.icons.filled.Favorite @@ -43,6 +42,7 @@ import androidx.compose.material.icons.outlined.Sync import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ProvideTextStyle @@ -653,7 +653,7 @@ private fun TagsChip( modifier: Modifier = Modifier, onClick: () -> Unit, ) { - CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { + CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) { SuggestionChip( modifier = modifier, onClick = onClick, diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt index d773ef7572..aef644c6c0 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/ScanlatorFilterDialog.kt @@ -31,8 +31,6 @@ import androidx.compose.ui.window.DialogProperties import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrolledToStart @Composable fun ScanlatorFilterDialog( @@ -96,8 +94,8 @@ fun ScanlatorFilterDialog( } } } - if (!state.isScrolledToStart()) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) - if (!state.isScrolledToEnd()) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) + if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) + if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) } }, properties = DialogProperties( diff --git a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt index 8f1add91b7..5aa141c742 100644 --- a/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/anime/AnimeHistoryScreen.kt @@ -84,14 +84,14 @@ private fun AnimeHistoryScreenContent( when (item) { is AnimeHistoryUiModel.Header -> { ListGroupHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), text = relativeDateText(item.date), ) } is AnimeHistoryUiModel.Item -> { val value = item.item AnimeHistoryItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), history = value, onClickCover = { onClickCover(value) }, onClickResume = { onClickResume(value) }, diff --git a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt index 7734578b90..eceb1bd695 100644 --- a/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/history/manga/MangaHistoryScreen.kt @@ -84,14 +84,14 @@ private fun MangaHistoryScreenContent( when (item) { is MangaHistoryUiModel.Header -> { ListGroupHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), text = relativeDateText(item.date), ) } is MangaHistoryUiModel.Item -> { val value = item.item MangaHistoryItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), history = value, onClickCover = { onClickCover(value) }, onClickResume = { onClickResume(value) }, diff --git a/app/src/main/java/eu/kanade/presentation/more/onboarding/PermissionStep.kt b/app/src/main/java/eu/kanade/presentation/more/onboarding/PermissionStep.kt index 79e45159f9..8b3d9c07bc 100644 --- a/app/src/main/java/eu/kanade/presentation/more/onboarding/PermissionStep.kt +++ b/app/src/main/java/eu/kanade/presentation/more/onboarding/PermissionStep.kt @@ -28,11 +28,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.unit.dp import androidx.core.content.getSystemService import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner import eu.kanade.presentation.util.rememberRequestPackageInstallsPermissionState import eu.kanade.tachiyomi.util.system.launchRequestPackageInstallsPermission import tachiyomi.i18n.MR diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt index 20a924d112..e6b2a227f6 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposContent.kt @@ -46,7 +46,7 @@ fun ExtensionReposContent( repos.forEach { item { ExtensionRepoListItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), repo = it, onOpenWebsite = { onOpenWebsite(it) }, onDelete = { onClickDelete(it.baseUrl) }, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt index afd4bc45b8..7fd38fcbc4 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt @@ -26,8 +26,6 @@ import androidx.compose.ui.unit.dp import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrolledToStart @Composable fun ListPreferenceWidget( @@ -69,16 +67,8 @@ fun ListPreferenceWidget( } } } - if (!state.isScrolledToStart()) { - HorizontalDivider( - modifier = Modifier.align(Alignment.TopCenter), - ) - } - if (!state.isScrolledToEnd()) { - HorizontalDivider( - modifier = Modifier.align(Alignment.BottomCenter), - ) - } + if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) + if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) } }, confirmButton = { diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt index 4366806a8c..5f30ae245a 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/TriStateListDialog.kt @@ -31,8 +31,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import tachiyomi.i18n.MR import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrolledToStart private enum class State { CHECKED, INVERSED, UNCHECKED @@ -117,16 +115,8 @@ fun TriStateListDialog( } } - if (!listState.isScrolledToStart()) { - HorizontalDivider( - modifier = Modifier.align(Alignment.TopCenter), - ) - } - if (!listState.isScrolledToEnd()) { - HorizontalDivider( - modifier = Modifier.align(Alignment.BottomCenter), - ) - } + if (listState.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) + if (listState.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) } } }, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index b415dedcb5..aafa400639 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -43,8 +43,6 @@ import tachiyomi.presentation.core.components.WheelTextPicker import tachiyomi.presentation.core.components.material.AlertDialogContent import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource -import tachiyomi.presentation.core.util.isScrolledToEnd -import tachiyomi.presentation.core.util.isScrolledToStart @Composable fun TrackStatusSelector( @@ -86,16 +84,8 @@ fun TrackStatusSelector( } } } - if (!state.isScrolledToStart()) { - HorizontalDivider( - modifier = Modifier.align(Alignment.TopCenter), - ) - } - if (!state.isScrolledToEnd()) { - HorizontalDivider( - modifier = Modifier.align(Alignment.BottomCenter), - ) - } + if (state.canScrollBackward) HorizontalDivider(modifier = Modifier.align(Alignment.TopCenter)) + if (state.canScrollForward) HorizontalDivider(modifier = Modifier.align(Alignment.BottomCenter)) }, onConfirm = onConfirm, onDismissRequest = onDismissRequest, diff --git a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt index 6b876a9605..d42157e139 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesUiItem.kt @@ -55,7 +55,7 @@ internal fun LazyListScope.animeUpdatesLastUpdatedItem( item(key = "animeUpdates-lastUpdated") { Box( modifier = Modifier - .animateItemPlacement() + .animateItem() .padding( horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small, @@ -95,14 +95,14 @@ internal fun LazyListScope.animeUpdatesUiItems( when (item) { is AnimeUpdatesUiModel.Header -> { ListGroupHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), text = relativeDateText(item.date), ) } is AnimeUpdatesUiModel.Item -> { val updatesItem = item.item AnimeUpdatesUiItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), update = updatesItem.update, selected = updatesItem.selected, watchProgress = updatesItem.update.lastSecondSeen diff --git a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt index 4a9ba796c4..546af1f1e1 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesUiItem.kt @@ -54,7 +54,7 @@ internal fun LazyListScope.mangaUpdatesLastUpdatedItem( item(key = "mangaUpdates-lastUpdated") { Box( modifier = Modifier - .animateItemPlacement() + .animateItem() .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), ) { Text( @@ -91,14 +91,14 @@ internal fun LazyListScope.mangaUpdatesUiItems( when (item) { is MangaUpdatesUiModel.Header -> { ListGroupHeader( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), text = relativeDateText(item.date), ) } is MangaUpdatesUiModel.Item -> { val updatesItem = item.item MangaUpdatesUiItem( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), update = updatesItem.update, selected = updatesItem.selected, readProgress = updatesItem.update.lastPageRead diff --git a/app/src/main/java/eu/kanade/presentation/util/Permissions.kt b/app/src/main/java/eu/kanade/presentation/util/Permissions.kt index 6bee9203e8..80c2f90b92 100644 --- a/app/src/main/java/eu/kanade/presentation/util/Permissions.kt +++ b/app/src/main/java/eu/kanade/presentation/util/Permissions.kt @@ -7,9 +7,9 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner @Composable fun rememberRequestPackageInstallsPermissionState(initialValue: Boolean = false): Boolean { diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 9911588379..8bb7dc583d 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,7 +1,6 @@ [versions] compiler = "1.5.12" -# 2024.04.00-alpha01 has several bugs with the new animateItem() modifier -compose-bom = "2024.03.00-alpha02" +compose-bom = "2024.05.00-alpha01" accompanist = "0.35.0-alpha" [libraries] diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index 3758f173c2..a05b26a535 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -161,7 +161,9 @@ fun AdaptiveSheet( if (enableSwipeDismiss) { Modifier.nestedScroll( remember(anchoredDraggableState) { - anchoredDraggableState.preUpPostDownNestedScrollConnection() + anchoredDraggableState.preUpPostDownNestedScrollConnection( + onFling = { scope.launch { anchoredDraggableState.settle(it) } } + ) }, ) } else { @@ -209,10 +211,12 @@ fun AdaptiveSheet( } } -private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection() = object : NestedScrollConnection { +private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection( + onFling: (velocity: Float) -> Unit +) = object : NestedScrollConnection { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { val delta = available.toFloat() - return if (delta < 0 && source == NestedScrollSource.Drag) { + return if (delta < 0 && source == NestedScrollSource.UserInput) { dispatchRawDelta(delta).toOffset() } else { Offset.Zero @@ -224,7 +228,7 @@ private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection() available: Offset, source: NestedScrollSource, ): Offset { - return if (source == NestedScrollSource.Drag) { + return if (source == NestedScrollSource.UserInput) { dispatchRawDelta(available.toFloat()).toOffset() } else { Offset.Zero @@ -234,7 +238,7 @@ private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection() override suspend fun onPreFling(available: Velocity): Velocity { val toFling = available.toFloat() return if (toFling < 0 && offset > anchors.minAnchor()) { - settle(toFling) + onFling(toFling) // since we go to the anchor with tween settling, consume all for the best UX available } else { @@ -243,13 +247,8 @@ private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection() } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { - val toFling = available.toFloat() - return if (toFling > 0) { - settle(toFling) - available - } else { - Velocity.Zero - } + onFling(available.toFloat()) + return available } private fun Float.toOffset(): Offset = Offset(0f, this) @@ -257,5 +256,6 @@ private fun AnchoredDraggableState.preUpPostDownNestedScrollConnection() @JvmName("velocityToFloat") private fun Velocity.toFloat() = this.y + @JvmName("offsetToFloat") private fun Offset.toFloat(): Float = this.y } diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt index 3ce22677a6..b1987185b0 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt @@ -1,34 +1,16 @@ package tachiyomi.presentation.core.components.material -import androidx.compose.animation.core.animate import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.pulltorefresh.PullToRefreshContainer -import androidx.compose.material3.pulltorefresh.PullToRefreshState +import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults +import androidx.compose.material3.pulltorefresh.pullToRefresh +import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableFloatStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.Saver -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.input.nestedscroll.NestedScrollSource -import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.LayoutDirection -import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp -import kotlin.math.abs -import kotlin.math.pow /** * @param refreshing Whether the layout is currently refreshing @@ -46,241 +28,26 @@ fun PullRefresh( indicatorPadding: PaddingValues = PaddingValues(0.dp), content: @Composable () -> Unit, ) { - val state = rememberPullToRefreshState( - isRefreshing = refreshing, - extraVerticalOffset = indicatorPadding.calculateTopPadding(), - enabled = enabled, - onRefresh = onRefresh, - ) - - Box(modifier.nestedScroll(state.nestedScrollConnection)) { + val state = rememberPullToRefreshState() + Box( + modifier = modifier + .pullToRefresh( + isRefreshing = refreshing, + state = state, + enabled = enabled, + onRefresh = onRefresh, + ) + ) { content() - val contentPadding = remember(indicatorPadding) { - object : PaddingValues { - override fun calculateLeftPadding(layoutDirection: LayoutDirection): Dp = - indicatorPadding.calculateLeftPadding(layoutDirection) - - override fun calculateTopPadding(): Dp = 0.dp - - override fun calculateRightPadding(layoutDirection: LayoutDirection): Dp = - indicatorPadding.calculateRightPadding(layoutDirection) - - override fun calculateBottomPadding(): Dp = - indicatorPadding.calculateBottomPadding() - } - } - PullToRefreshContainer( - state = state, + PullToRefreshDefaults.Indicator( modifier = Modifier .align(Alignment.TopCenter) - .padding(contentPadding), + .padding(indicatorPadding), + isRefreshing = refreshing, + state = state, containerColor = MaterialTheme.colorScheme.surfaceVariant, - contentColor = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } -} - -@Composable -private fun rememberPullToRefreshState( - isRefreshing: Boolean, - extraVerticalOffset: Dp, - positionalThreshold: Dp = 64.dp, - enabled: () -> Boolean = { true }, - onRefresh: () -> Unit, -): PullToRefreshStateImpl { - val density = LocalDensity.current - val extraVerticalOffsetPx = with(density) { extraVerticalOffset.toPx() } - val positionalThresholdPx = with(density) { positionalThreshold.toPx() } - return rememberSaveable( - extraVerticalOffset, - positionalThresholdPx, - enabled, - saver = PullToRefreshStateImpl.Saver( - extraVerticalOffset = extraVerticalOffsetPx, - positionalThreshold = positionalThresholdPx, - enabled = enabled, - onRefresh, - ), - ) { - PullToRefreshStateImpl( - initialRefreshing = isRefreshing, - extraVerticalOffset = extraVerticalOffsetPx, - positionalThreshold = positionalThresholdPx, - enabled = enabled, - onRefresh = onRefresh, + color = MaterialTheme.colorScheme.onSurfaceVariant, ) - }.also { - LaunchedEffect(isRefreshing) { - if (isRefreshing && !it.isRefreshing) { - it.startRefreshAnimated() - } else if (!isRefreshing && it.isRefreshing) { - it.endRefreshAnimated() - } - } - } -} - -/** - * Creates a [PullToRefreshState]. - * - * @param positionalThreshold The positional threshold, in pixels, in which a refresh is triggered - * @param extraVerticalOffset Extra vertical offset, in pixels, for the "refreshing" state - * @param initialRefreshing The initial refreshing value of [PullToRefreshState] - * @param enabled a callback used to determine whether scroll events are to be handled by this - * @param onRefresh a callback to run when pull-to-refresh action is triggered by user - * [PullToRefreshState] - */ -private class PullToRefreshStateImpl( - initialRefreshing: Boolean, - private val extraVerticalOffset: Float, - override val positionalThreshold: Float, - enabled: () -> Boolean, - private val onRefresh: () -> Unit, -) : PullToRefreshState { - - override val progress get() = adjustedDistancePulled / positionalThreshold - override var verticalOffset by mutableFloatStateOf(if (initialRefreshing) refreshingVerticalOffset else 0f) - - override var isRefreshing by mutableStateOf(initialRefreshing) - - private val refreshingVerticalOffset: Float - get() = positionalThreshold + extraVerticalOffset - - override fun startRefresh() { - isRefreshing = true - verticalOffset = refreshingVerticalOffset } - - suspend fun startRefreshAnimated() { - isRefreshing = true - animateTo(refreshingVerticalOffset) - } - - override fun endRefresh() { - verticalOffset = 0f - isRefreshing = false - } - - suspend fun endRefreshAnimated() { - animateTo(0f) - isRefreshing = false - } - - override var nestedScrollConnection = object : NestedScrollConnection { - override fun onPreScroll( - available: Offset, - source: NestedScrollSource, - ): Offset = when { - !enabled() -> Offset.Zero - // Swiping up - source == NestedScrollSource.Drag && available.y < 0 -> { - consumeAvailableOffset(available) - } - else -> Offset.Zero - } - - override fun onPostScroll( - consumed: Offset, - available: Offset, - source: NestedScrollSource, - ): Offset = when { - !enabled() -> Offset.Zero - // Swiping down - source == NestedScrollSource.Drag && available.y > 0 -> { - consumeAvailableOffset(available) - } - else -> Offset.Zero - } - - override suspend fun onPreFling(available: Velocity): Velocity { - return Velocity(0f, onRelease(available.y)) - } - } - - /** Helper method for nested scroll connection */ - fun consumeAvailableOffset(available: Offset): Offset { - val y = if (isRefreshing) { - 0f - } else { - val newOffset = (distancePulled + available.y).coerceAtLeast(0f) - val dragConsumed = newOffset - distancePulled - distancePulled = newOffset - verticalOffset = calculateVerticalOffset() + (extraVerticalOffset * progress.coerceIn(0f, 1f)) - dragConsumed - } - return Offset(0f, y) - } - - /** Helper method for nested scroll connection. Calls onRefresh callback when triggered */ - suspend fun onRelease(velocity: Float): Float { - if (isRefreshing) return 0f // Already refreshing, do nothing - // Trigger refresh - if (adjustedDistancePulled > positionalThreshold) { - onRefresh() - startRefreshAnimated() - } else { - animateTo(0f) - } - - val consumed = when { - // We are flinging without having dragged the pull refresh (for example a fling inside - // a list) - don't consume - distancePulled == 0f -> 0f - // If the velocity is negative, the fling is upwards, and we don't want to prevent the - // the list from scrolling - velocity < 0f -> 0f - // We are showing the indicator, and the fling is downwards - consume everything - else -> velocity - } - distancePulled = 0f - return consumed - } - - suspend fun animateTo(offset: Float) { - animate(initialValue = verticalOffset, targetValue = offset) { value, _ -> - verticalOffset = value - } - } - - /** Provides custom vertical offset behavior for [PullToRefreshContainer] */ - fun calculateVerticalOffset(): Float = when { - // If drag hasn't gone past the threshold, the position is the adjustedDistancePulled. - adjustedDistancePulled <= positionalThreshold -> adjustedDistancePulled - else -> { - // How far beyond the threshold pull has gone, as a percentage of the threshold. - val overshootPercent = abs(progress) - 1.0f - // Limit the overshoot to 200%. Linear between 0 and 200. - val linearTension = overshootPercent.coerceIn(0f, 2f) - // Non-linear tension. Increases with linearTension, but at a decreasing rate. - val tensionPercent = linearTension - linearTension.pow(2) / 4 - // The additional offset beyond the threshold. - val extraOffset = positionalThreshold * tensionPercent - positionalThreshold + extraOffset - } - } - - companion object { - /** The default [Saver] for [PullToRefreshStateImpl]. */ - fun Saver( - extraVerticalOffset: Float, - positionalThreshold: Float, - enabled: () -> Boolean, - onRefresh: () -> Unit, - ) = Saver( - save = { it.isRefreshing }, - restore = { isRefreshing -> - PullToRefreshStateImpl( - initialRefreshing = isRefreshing, - extraVerticalOffset = extraVerticalOffset, - positionalThreshold = positionalThreshold, - enabled = enabled, - onRefresh = onRefresh, - ) - }, - ) - } - - private var distancePulled by mutableFloatStateOf(0f) - private val adjustedDistancePulled: Float get() = distancePulled * 0.5f } diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/util/LazyListState.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/util/LazyListState.kt index 1abb3205ac..cdabf1b496 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/util/LazyListState.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/util/LazyListState.kt @@ -3,63 +3,16 @@ package tachiyomi.presentation.core.util import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue @Composable -fun LazyListState.isScrolledToStart(): Boolean { +fun LazyListState.shouldExpandFAB(): Boolean { return remember { derivedStateOf { - val firstItem = layoutInfo.visibleItemsInfo.firstOrNull() - firstItem == null || firstItem.offset == layoutInfo.viewportStartOffset + (firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0) || + lastScrolledBackward || + !canScrollForward } - }.value -} - -@Composable -fun LazyListState.isScrolledToEnd(): Boolean { - return remember { - derivedStateOf { - val lastItem = layoutInfo.visibleItemsInfo.lastOrNull() - lastItem == null || lastItem.size + lastItem.offset <= layoutInfo.viewportEndOffset - } - }.value -} - -@Composable -fun LazyListState.isScrollingUp(): Boolean { - var previousIndex by remember { mutableIntStateOf(firstVisibleItemIndex) } - var previousScrollOffset by remember { mutableIntStateOf(firstVisibleItemScrollOffset) } - return remember { - derivedStateOf { - if (previousIndex != firstVisibleItemIndex) { - previousIndex > firstVisibleItemIndex - } else { - previousScrollOffset >= firstVisibleItemScrollOffset - }.also { - previousIndex = firstVisibleItemIndex - previousScrollOffset = firstVisibleItemScrollOffset - } - } - }.value -} - -@Composable -fun LazyListState.isScrollingDown(): Boolean { - var previousIndex by remember { mutableIntStateOf(firstVisibleItemIndex) } - var previousScrollOffset by remember { mutableIntStateOf(firstVisibleItemScrollOffset) } - return remember { - derivedStateOf { - if (previousIndex != firstVisibleItemIndex) { - previousIndex < firstVisibleItemIndex - } else { - previousScrollOffset <= firstVisibleItemScrollOffset - }.also { - previousIndex = firstVisibleItemIndex - previousScrollOffset = firstVisibleItemScrollOffset - } - } - }.value + } + .value } From 0534146e6f7c45576bd62d235d556ee588779ace Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 23:05:38 +0200 Subject: [PATCH 16/74] Revert "Fix badge count getting cut off on tab title" This reverts commit https://github.com/mihonapp/mihon/commit/263e467cdeb948b8f3679e2ea0282a291cf2f131 Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../tachiyomi/presentation/core/components/material/Tabs.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt index cf6c20da81..f74ff909c2 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Tabs.kt @@ -6,7 +6,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.sp import tachiyomi.presentation.core.components.Pill @@ -25,7 +24,6 @@ fun TabText( text = text, maxLines = 1, overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f, fill = false), ) if (badgeCount != null) { Pill( From 04325a832c09588a92db6ba0bbe410a12fb715c4 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 23:07:11 +0200 Subject: [PATCH 17/74] Change keyboard type in extension repo dialog Co-authored-by: Reagan <24711088+xbjfk@users.noreply.github.com> --- .../settings/screen/browse/components/ExtensionReposDialogs.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposDialogs.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposDialogs.kt index 5022f44f05..239f917d0b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/browse/components/ExtensionReposDialogs.kt @@ -1,6 +1,7 @@ package eu.kanade.presentation.more.settings.screen.browse.components import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.AlertDialog import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text @@ -14,6 +15,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.text.input.KeyboardType import kotlinx.collections.immutable.ImmutableSet import kotlinx.coroutines.delay import mihon.domain.extensionrepo.model.ExtensionRepo @@ -74,6 +76,7 @@ fun ExtensionRepoCreateDialog( Text(text = stringResource(msgRes)) }, isError = name.isNotEmpty() && nameAlreadyExists, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri), singleLine = true, ) } From 303f0f2763e12f90b929f1f7c9cab008e377f17a Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 23:08:10 +0200 Subject: [PATCH 18/74] Fix search bar style Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../main/java/eu/kanade/presentation/components/AppBar.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt index 8e743ebf5f..071711e1d1 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.TextFieldDefaults import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Close @@ -24,6 +23,7 @@ import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.TopAppBar @@ -338,7 +338,7 @@ fun SearchToolbar( visualTransformation = visualTransformation, interactionSource = interactionSource, decorationBox = { innerTextField -> - TextFieldDefaults.TextFieldDecorationBox( + TextFieldDefaults.DecorationBox( value = searchQuery, innerTextField = innerTextField, enabled = true, @@ -361,6 +361,7 @@ fun SearchToolbar( ), ) }, + container = {}, ) }, ) From 268581c84ee3b6bb4c67302e013ac40c2079549f Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 23:17:25 +0200 Subject: [PATCH 19/74] Use new SurfaceContainer color roles Non-dynamic themes need to be updated Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../eu/kanade/presentation/components/AdaptiveSheet.kt | 6 ------ .../java/eu/kanade/presentation/components/TabbedDialog.kt | 1 + .../entries/components/EntryBottomActionMenu.kt | 4 ++-- .../more/settings/widget/AppThemePreferenceWidget.kt | 3 +-- .../kanade/presentation/reader/ReaderPageActionsDialog.kt | 4 +--- .../presentation/track/anime/AnimeTrackInfoDialogHome.kt | 2 +- .../presentation/track/manga/MangaTrackInfoDialogHome.kt | 2 +- .../browse/anime/source/browse/SourceFilterAnimeDialog.kt | 4 +--- .../browse/manga/source/browse/SourceFilterMangaDialog.kt | 4 +--- .../presentation/core/components/AdaptiveSheet.kt | 6 ++---- .../java/tachiyomi/presentation/core/components/Pill.kt | 7 ++----- .../presentation/core/components/material/Button.kt | 2 -- 12 files changed, 13 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt b/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt index fac8bb989c..30ae43a43d 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AdaptiveSheet.kt @@ -7,8 +7,6 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.view.WindowInsetsControllerCompat @@ -25,7 +23,6 @@ import tachiyomi.presentation.core.components.AdaptiveSheet as AdaptiveSheetImpl @Composable fun NavigatorAdaptiveSheet( screen: Screen, - tonalElevation: Dp = 1.dp, enableSwipeDismiss: (Navigator) -> Boolean = { true }, onDismissRequest: () -> Unit, ) { @@ -33,7 +30,6 @@ fun NavigatorAdaptiveSheet( screen = screen, content = { sheetNavigator -> AdaptiveSheet( - tonalElevation = tonalElevation, enableSwipeDismiss = enableSwipeDismiss(sheetNavigator), onDismissRequest = onDismissRequest, ) { @@ -76,7 +72,6 @@ fun AdaptiveSheet( onDismissRequest: () -> Unit, modifier: Modifier = Modifier, hideSystemBars: Boolean = false, - tonalElevation: Dp = 1.dp, enableSwipeDismiss: Boolean = true, content: @Composable () -> Unit, ) { @@ -95,7 +90,6 @@ fun AdaptiveSheet( AdaptiveSheetImpl( modifier = modifier, isTabletUi = isTabletUi, - tonalElevation = tonalElevation, enableSwipeDismiss = enableSwipeDismiss, onDismissRequest = onDismissRequest, ) { diff --git a/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt b/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt index 5978727870..b9f7f0e204 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedDialog.kt @@ -63,6 +63,7 @@ fun TabbedDialog( PrimaryTabRow( modifier = Modifier.weight(1f), selectedTabIndex = pagerState.currentPage, + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh, divider = {}, ) { tabTitles.fastForEachIndexed { index, tab -> diff --git a/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt index e7f9861d42..ad21f681e2 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/components/EntryBottomActionMenu.kt @@ -93,7 +93,7 @@ fun EntryBottomActionMenu( bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize, ), - tonalElevation = 3.dp, + color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { val haptic = LocalHapticFeedback.current val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false, false, false) } @@ -284,7 +284,7 @@ fun LibraryBottomActionMenu( bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize, ), - tonalElevation = 3.dp, + color = MaterialTheme.colorScheme.surfaceContainerHigh, ) { val haptic = LocalHapticFeedback.current val confirm = remember { mutableStateListOf(false, false, false, false, false) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt index 773a3d4fb8..c1b94764ec 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/AppThemePreferenceWidget.kt @@ -223,13 +223,12 @@ fun AppThemePreviewItem( contentAlignment = Alignment.BottomCenter, ) { Surface( - tonalElevation = 3.dp, + color = MaterialTheme.colorScheme.surfaceContainer, ) { Row( modifier = Modifier .height(32.dp) .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant) .padding(horizontal = 8.dp), verticalAlignment = Alignment.CenterVertically, ) { diff --git a/app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt b/app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt index 70cc58f207..b83e8d6adf 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/ReaderPageActionsDialog.kt @@ -33,9 +33,7 @@ fun ReaderPageActionsDialog( ) { var showSetCoverDialog by remember { mutableStateOf(false) } - AdaptiveSheet( - onDismissRequest = onDismissRequest, - ) { + AdaptiveSheet(onDismissRequest = onDismissRequest) { Row( modifier = Modifier.padding(vertical = 16.dp), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.small), diff --git a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt index e6fa4ac694..dbe0d4ad3d 100644 --- a/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/anime/AnimeTrackInfoDialogHome.kt @@ -178,7 +178,7 @@ private fun TrackInfoItem( modifier = Modifier .padding(top = 12.dp) .clip(MaterialTheme.shapes.medium) - .background(MaterialTheme.colorScheme.surface) + .background(MaterialTheme.colorScheme.surfaceContainerHighest) .padding(8.dp) .clip(RoundedCornerShape(6.dp)), ) { diff --git a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt index a6545d5fce..d0e630122e 100644 --- a/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt +++ b/app/src/main/java/eu/kanade/presentation/track/manga/MangaTrackInfoDialogHome.kt @@ -189,7 +189,7 @@ private fun TrackInfoItem( modifier = Modifier .padding(top = 12.dp) .clip(MaterialTheme.shapes.medium) - .background(MaterialTheme.colorScheme.surface) + .background(MaterialTheme.colorScheme.surfaceContainerHighest) .padding(8.dp) .clip(RoundedCornerShape(6.dp)), ) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/SourceFilterAnimeDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/SourceFilterAnimeDialog.kt index a096e512e4..5e2ffb84df 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/SourceFilterAnimeDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/browse/SourceFilterAnimeDialog.kt @@ -40,9 +40,7 @@ fun SourceFilterAnimeDialog( ) { val updateFilters = { onUpdate(filters) } - AdaptiveSheet( - onDismissRequest = onDismissRequest, - ) { + AdaptiveSheet(onDismissRequest = onDismissRequest) { LazyColumn { stickyHeader { Row( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/SourceFilterMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/SourceFilterMangaDialog.kt index 6cf6848da0..9f384d5d01 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/SourceFilterMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/browse/SourceFilterMangaDialog.kt @@ -40,9 +40,7 @@ fun SourceFilterMangaDialog( ) { val updateFilters = { onUpdate(filters) } - AdaptiveSheet( - onDismissRequest = onDismissRequest, - ) { + AdaptiveSheet(onDismissRequest = onDismissRequest) { LazyColumn { stickyHeader { Row( diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt index a05b26a535..dd3a03dac8 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/AdaptiveSheet.kt @@ -40,7 +40,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp @@ -55,7 +54,6 @@ private val sheetAnimationSpec = tween(durationMillis = 350) @Composable fun AdaptiveSheet( isTabletUi: Boolean, - tonalElevation: Dp, enableSwipeDismiss: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, @@ -105,7 +103,7 @@ fun AdaptiveSheet( .padding(vertical = 16.dp) .then(modifier), shape = MaterialTheme.shapes.extraLarge, - tonalElevation = tonalElevation, + color = MaterialTheme.colorScheme.surfaceContainerHigh, content = { BackHandler(enabled = alpha > 0f, onBack = internalOnDismissRequest) content() @@ -188,7 +186,7 @@ fun AdaptiveSheet( .navigationBarsPadding() .statusBarsPadding(), shape = MaterialTheme.shapes.extraLarge, - tonalElevation = tonalElevation, + color = MaterialTheme.colorScheme.surfaceContainerHigh, content = { BackHandler( enabled = anchoredDraggableState.targetValue == 0, diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/Pill.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/Pill.kt index 4389fb482b..1d8cf2d5f5 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/Pill.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/Pill.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp @@ -18,9 +17,8 @@ import androidx.compose.ui.unit.dp fun Pill( text: String, modifier: Modifier = Modifier, - color: Color = MaterialTheme.colorScheme.background, - contentColor: Color = MaterialTheme.colorScheme.onBackground, - elevation: Dp = 1.dp, + color: Color = MaterialTheme.colorScheme.surfaceContainerHigh, + contentColor: Color = MaterialTheme.colorScheme.onSurface, fontSize: TextUnit = LocalTextStyle.current.fontSize, ) { Surface( @@ -29,7 +27,6 @@ fun Pill( shape = MaterialTheme.shapes.extraLarge, color = color, contentColor = contentColor, - tonalElevation = elevation, ) { Box( modifier = Modifier diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Button.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Button.kt index 85e41a1a66..df7eecb654 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Button.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/Button.kt @@ -102,7 +102,6 @@ fun Button( val containerColor = colors.containerColor(enabled).value val contentColor = colors.contentColor(enabled).value val shadowElevation = elevation?.shadowElevation(enabled, interactionSource)?.value ?: 0.dp - val tonalElevation = elevation?.tonalElevation(enabled, interactionSource)?.value ?: 0.dp Surface( onClick = onClick, @@ -111,7 +110,6 @@ fun Button( shape = shape, color = containerColor, contentColor = contentColor, - tonalElevation = tonalElevation, shadowElevation = shadowElevation, border = border, interactionSource = interactionSource, From cdc9e7c8b0ae411a0493edaf233a0bcfac8db29a Mon Sep 17 00:00:00 2001 From: Secozzi Date: Thu, 11 Jul 2024 23:19:25 +0200 Subject: [PATCH 20/74] Fix tap control area shifting after zooming out Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- .../tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt | 3 ++- .../kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt index f4703ce8ad..30647f6c71 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt @@ -28,7 +28,8 @@ class WebtoonRecyclerView @JvmOverloads constructor( private var atFirstPosition = false private var halfWidth = 0 private var halfHeight = 0 - private var originalHeight = 0 + var originalHeight = 0 + private set private var heightSet = false private var firstVisibleItemPosition = 0 private var lastVisibleItemPosition = 0 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt index 1542883f38..0831087c41 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt @@ -117,7 +117,7 @@ class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = tr recycler.getLocationInWindow(viewPositionRelativeToWindow) val pos = PointF( (event.rawX - viewPosition[0] + viewPositionRelativeToWindow[0]) / recycler.width, - (event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.height, + (event.rawY - viewPosition[1] + viewPositionRelativeToWindow[1]) / recycler.originalHeight, ) when (config.navigator.getAction(pos)) { NavigationRegion.MENU -> activity.toggleMenu() From 30359baaba4b1665928464b7541d72edac4d9e10 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:13:02 +0200 Subject: [PATCH 21/74] Remove dependency on compose material 2 components Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 1 - .../main/java/eu/kanade/presentation/components/AppBar.kt | 2 +- .../more/settings/widget/ListPreferenceWidget.kt | 2 +- .../kanade/presentation/track/TrackInfoDialogSelector.kt | 2 +- gradle/compose.versions.toml | 5 +---- presentation-core/build.gradle.kts | 1 - .../presentation/core/components/SettingsItems.kt | 7 ++++--- 7 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f490427d39..96c4783cea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -162,7 +162,6 @@ dependencies { implementation(compose.activity) implementation(compose.foundation) implementation(compose.material3.core) - implementation(compose.material.core) implementation(compose.material.icons) implementation(compose.animation) implementation(compose.animation.graphics) diff --git a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt index 071711e1d1..54c0aaf866 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -202,7 +202,7 @@ fun AppBarTitle( maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.basicMarquee( - repeatDelayMillis = 2_000, + delayMillis = 2_000, ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt b/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt index 7fd38fcbc4..7daf651688 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/widget/ListPreferenceWidget.kt @@ -6,13 +6,13 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.selection.selectable -import androidx.compose.material.minimumInteractiveComponentSize import androidx.compose.material3.AlertDialog import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt index aafa400639..a96885186f 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.minimumInteractiveComponentSize import androidx.compose.material3.DatePicker import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -24,6 +23,7 @@ import androidx.compose.material3.SelectableDates import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TextButton +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.material3.rememberDatePickerState import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 8bb7dc583d..00baa130b0 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -9,7 +9,7 @@ compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compi activity = "androidx.activity:activity-compose:1.9.0" bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" } foundation = { module = "androidx.compose.foundation:foundation" } -animation = { module = "androidx.compose.animation:animation", version = "1.7.0-beta04" } +animation = { module = "androidx.compose.animation:animation" } animation-graphics = { module = "androidx.compose.animation:animation-graphics" } ui-tooling = { module = "androidx.compose.ui:ui-tooling" } ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } @@ -18,9 +18,6 @@ ui-util = { module = "androidx.compose.ui:ui-util" } material3-core = { module = "androidx.compose.material3:material3" } material-icons = { module = "androidx.compose.material:material-icons-extended" } -# Some components aren't available in Material3 -material-core = { module = "androidx.compose.material:material", version = "1.7.0-beta04" } - glance = "androidx.glance:glance-appwidget:1.1.0" accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanist" } diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index a5f8a585f6..0478394961 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -33,7 +33,6 @@ dependencies { implementation(compose.activity) implementation(compose.foundation) implementation(compose.material3.core) - implementation(compose.material.core) implementation(compose.material.icons) implementation(compose.animation) implementation(compose.animation.graphics) diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt index c3e138f652..b95efe62e9 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/SettingsItems.kt @@ -18,7 +18,6 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material.ContentAlpha import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDownward import androidx.compose.material.icons.filled.ArrowUpward @@ -68,6 +67,8 @@ object SettingsItemsPaddings { val Vertical = 10.dp } +private const val DisabledContentAlpha = 0.38f + @Composable fun HeadingItem( labelRes: StringResource, @@ -313,7 +314,7 @@ fun TriStateItem( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large), ) { - val stateAlpha = if (enabled && onClick != null) 1f else ContentAlpha.disabled + val stateAlpha = if (enabled && onClick != null) 1f else DisabledContentAlpha Icon( imageVector = when (state) { @@ -326,7 +327,7 @@ fun TriStateItem( MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = stateAlpha) } else { when (onClick) { - null -> MaterialTheme.colorScheme.onSurface.copy(alpha = ContentAlpha.disabled) + null -> MaterialTheme.colorScheme.onSurface.copy(alpha = DisabledContentAlpha) else -> MaterialTheme.colorScheme.primary } }, From 5bb444f7219eb6b65af4ffe2f90671a5b2a252ee Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 12:52:05 +0600 Subject: [PATCH 22/74] chore(deps): update actions/checkout action to v4.1.5 (#771) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 9a62e4fba33a8f49e0ba1c46419823cd3618878e) --- .github/workflows/build_pull_request.yml | 2 +- .github/workflows/build_push.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 98d90b1910..0630a48a63 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index b98a9043a3..84799608f2 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 From 5ea7de572570ef3e4f3c01d648de35d86fa59837 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 08:52:18 +0200 Subject: [PATCH 23/74] chore(deps): update softprops/action-gh-release action to v2.0.5 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 84799608f2..0e07debe3b 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -96,7 +96,7 @@ jobs: - name: Create Release if: startsWith(github.ref, 'refs/tags/') && github.repository == 'aniyomiorg/aniyomi' - uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4 + uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87 # v2.0.5 with: tag_name: ${{ env.VERSION_TAG }} name: Aniyomi ${{ env.VERSION_TAG }} From e3226196a13f6a1bc24a1320e1214dd86a53bc8c Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:16:16 +0200 Subject: [PATCH 24/74] update r8 rules for `MultipartBody.Builder` in extensions Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> --- app/proguard-rules.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 1d0bdb05a1..0906f6c415 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -48,6 +48,10 @@ -dontnote rx.internal.util.PlatformDependent ##---------------End: proguard configuration for RxJava 1.x ---------- +##---------------Begin: proguard configuration for okhttp ---------- +-keepclasseswithmembers class okhttp3.MultipartBody$Builder { *; } +##---------------End: proguard configuration for okhttp ---------- + ##---------------Begin: proguard configuration for kotlinx.serialization ---------- -keepattributes *Annotation*, InnerClasses -dontnote kotlinx.serialization.** # core serialization annotations From aa2f4ec302959f298da5daf7436d9e7e0e9c849f Mon Sep 17 00:00:00 2001 From: CrepeTF <70870719+CrepeTF@users.noreply.github.com> Date: Mon, 13 May 2024 21:44:40 +0100 Subject: [PATCH 25/74] Update themes to follow new compose update changes (#766) * Update Green Apple theme * Add some Green Apple theme comments * Update Lavender theme * Update Midnight Dusk theme * Update Nord theme * Update Strawberry Daiquiri theme * Update Tako theme * Update Teal & Turquoise theme * Update Lavender secondaryContainer and onSecondaryContainer colour * Update M.Dusk secondaryContainer and onSecondaryContainer colour * Update Tako secondaryContainer and onSecondaryContainer colour * Comments * Update Tidal Wave theme * Update Yin Yang theme * Update Yotsuba theme * Fix navbar tinted background on pure black * Add surfaceContainer levels to Lavender theme * Resolve detekt issues * Add surfaceContainer levels to Midnight Dusk theme * Add surfaceContainer levels to Nord theme * Add surfaceContainer levels to Tako theme * Add surfaceContainer levels to Teal & Turquoise theme * Add surfaceContainer levels to Tidal Wave theme * Add surfaceContainer levels to Yin Yang theme * Add surfaceContainer levels to Yotsuba theme * Add dark theme surfaceContainer levels to Yotsuba theme * surfaceContainer tweaks to Yotsuba theme * surfaceContainer tweaks to Strawberry Daiquiri theme * surfaceContainer tweaks to Nord theme * surfaceContainer tweaks to Lavender theme * Update Tachiyomi theme * Update Pure Black theme * Resolve detekt issues * Oopsie (cherry picked from commit 16392adcbba4027cbed0a44e2fc62df330af6385) --- .../theme/colorscheme/BaseColorScheme.kt | 12 ++ .../colorscheme/GreenAppleColorScheme.kt | 110 +++++++++++------- .../theme/colorscheme/LavenderColorScheme.kt | 102 +++++++++------- .../colorscheme/MidnightDuskColorScheme.kt | 38 +++--- .../theme/colorscheme/NordColorScheme.kt | 40 ++++--- .../colorscheme/StrawberryColorScheme.kt | 110 +++++++++++------- .../theme/colorscheme/TachiyomiColorScheme.kt | 38 +++--- .../theme/colorscheme/TakoColorScheme.kt | 38 +++--- .../colorscheme/TealTurqoiseColorScheme.kt | 38 +++--- .../theme/colorscheme/TidalWaveColorScheme.kt | 38 +++--- .../theme/colorscheme/YinYangColorScheme.kt | 38 +++--- .../theme/colorscheme/YotsubaColorScheme.kt | 38 +++--- 12 files changed, 402 insertions(+), 238 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/BaseColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/BaseColorScheme.kt index 97455fd9e4..22dd9a0a79 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/BaseColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/BaseColorScheme.kt @@ -8,6 +8,12 @@ internal abstract class BaseColorScheme { abstract val darkScheme: ColorScheme abstract val lightScheme: ColorScheme + // Cannot be pure black as there's content scrolling behind it + // https://m3.material.io/components/navigation-bar/guidelines#90615a71-607e-485e-9e09-778bfc080563 + private val surfaceContainer = Color(0xFF0C0C0C) + private val surfaceContainerHigh = Color(0xFF131313) + private val surfaceContainerHighest = Color(0xFF1B1B1B) + fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme { if (!isDark) return lightScheme @@ -18,6 +24,12 @@ internal abstract class BaseColorScheme { onBackground = Color.White, surface = Color.Black, onSurface = Color.White, + surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget) + surfaceContainerLowest = surfaceContainer, + surfaceContainerLow = surfaceContainer, + surfaceContainer = surfaceContainer, // Navigation bar background + surfaceContainerHigh = surfaceContainerHigh, + surfaceContainerHighest = surfaceContainerHighest, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/GreenAppleColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/GreenAppleColorScheme.kt index 354faed5f2..566cce91bc 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/GreenAppleColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/GreenAppleColorScheme.kt @@ -19,53 +19,77 @@ internal object GreenAppleColorScheme : BaseColorScheme() { override val darkScheme = darkColorScheme( primary = Color(0xFF7ADB8F), - onPrimary = Color(0xFF003915), - primaryContainer = Color(0xFF005322), - onPrimaryContainer = Color(0xFF96F8A9), - inversePrimary = Color(0xFF006D2F), - secondary = Color(0xFF7ADB8F), - onSecondary = Color(0xFF003915), - secondaryContainer = Color(0xFF005322), - onSecondaryContainer = Color(0xFF96F8A9), - tertiary = Color(0xFFFFB3AA), - onTertiary = Color(0xFF680006), - tertiaryContainer = Color(0xFF93000D), - onTertiaryContainer = Color(0xFFFFDAD5), - background = Color(0xFF1A1C19), - onBackground = Color(0xFFE1E3DD), - surface = Color(0xFF1A1C19), - onSurface = Color(0xFFE1E3DD), - surfaceVariant = Color(0xFF414941), - onSurfaceVariant = Color(0xFFC1C8BE), - surfaceTint = Color(0xFF7ADB8F), - inverseSurface = Color(0xFFE1E3DD), - inverseOnSurface = Color(0xFF1A1C19), - outline = Color(0xFF8B9389), + onPrimary = Color(0xFF003917), + primaryContainer = Color(0xFF017737), + onPrimaryContainer = Color(0xFFFFFFFF), + secondary = Color(0xFF7ADB8F), // Unread badge + onSecondary = Color(0xFF003917), // Unread badge text + secondaryContainer = Color(0xFF017737), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selected icon + tertiary = Color(0xFFFFB3AC), // Downloaded badge + onTertiary = Color(0xFF680008), // Downloaded badge text + tertiaryContainer = Color(0xFFC7282A), + onTertiaryContainer = Color(0xFFFFFFFF), + error = Color(0xFFFFB4AB), + onError = Color(0xFF690005), + errorContainer = Color(0xFF93000A), + onErrorContainer = Color(0xFFFFDAD6), + background = Color(0xFF0F1510), + onBackground = Color(0xFFDFE4DB), + surface = Color(0xFF0F1510), + onSurface = Color(0xFFDFE4DB), + surfaceVariant = Color(0xFF3F493F), // Navigation bar background (ThemePrefWidget) + onSurfaceVariant = Color(0xFFBECABC), + outline = Color(0xFF889487), + outlineVariant = Color(0xFF3F493F), + scrim = Color(0xFF000000), + inverseSurface = Color(0xFFDFE4DB), + inverseOnSurface = Color(0xFF2C322C), + inversePrimary = Color(0xFF006D32), + surfaceDim = Color(0xFF0F1510), + surfaceBright = Color(0xFF353B35), + surfaceContainerLowest = Color(0xFF0A0F0B), + surfaceContainerLow = Color(0xFF181D18), + surfaceContainer = Color(0xFF1C211C), // Navigation bar background + surfaceContainerHigh = Color(0xFF262B26), + surfaceContainerHighest = Color(0xFF313630), ) override val lightScheme = lightColorScheme( - primary = Color(0xFF006D2F), + primary = Color(0xFF005927), onPrimary = Color(0xFFFFFFFF), - primaryContainer = Color(0xFF96F8A9), - onPrimaryContainer = Color(0xFF002109), + primaryContainer = Color(0xFF188140), + onPrimaryContainer = Color(0xFFFFFFFF), + secondary = Color(0xFF005927), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFF97f7a9), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF000000), // Navigation bar selected icon + tertiary = Color(0xFF9D0012), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text + tertiaryContainer = Color(0xFFD33131), + onTertiaryContainer = Color(0xFFFFFFFF), + error = Color(0xFFBA1A1A), + onError = Color(0xFFFFFFFF), + errorContainer = Color(0xFFFFDAD6), + onErrorContainer = Color(0xFF410002), + background = Color(0xFFF6FBF2), + onBackground = Color(0xFF181D18), + surface = Color(0xFFF6FBF2), + onSurface = Color(0xFF181D18), + surfaceVariant = Color(0xFFDAE6D7), // Navigation bar background (ThemePrefWidget) + onSurfaceVariant = Color(0xFF3F493F), + outline = Color(0xFF6F7A6E), + outlineVariant = Color(0xFFBECABC), + scrim = Color(0xFF000000), + inverseSurface = Color(0xFF2C322C), + inverseOnSurface = Color(0xFFEDF2E9), inversePrimary = Color(0xFF7ADB8F), - secondary = Color(0xFF006D2F), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFF96F8A9), - onSecondaryContainer = Color(0xFF002109), - tertiary = Color(0xFFB91D22), - onTertiary = Color(0xFFFFFFFF), - tertiaryContainer = Color(0xFFFFDAD5), - onTertiaryContainer = Color(0xFF410003), - background = Color(0xFFFBFDF7), - onBackground = Color(0xFF1A1C19), - surface = Color(0xFFFBFDF7), - onSurface = Color(0xFF1A1C19), - surfaceVariant = Color(0xFFDDE5DA), - onSurfaceVariant = Color(0xFF414941), - surfaceTint = Color(0xFF006D2F), - inverseSurface = Color(0xFF2F312E), - inverseOnSurface = Color(0xFFF0F2EC), - outline = Color(0xFF717970), + surfaceDim = Color(0xFFD6DCD3), + surfaceBright = Color(0xFFF6FBF2), + surfaceContainerLowest = Color(0xFFFFFFFF), + surfaceContainerLow = Color(0xFFF0F5EC), + surfaceContainer = Color(0xFFEAEFE6), // Navigation bar background + surfaceContainerHigh = Color(0xFFE4EAE1), + surfaceContainerHighest = Color(0xFFDFE4DB), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/LavenderColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/LavenderColorScheme.kt index 70a9bd196f..e1bb6b3ee4 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/LavenderColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/LavenderColorScheme.kt @@ -18,53 +18,77 @@ internal object LavenderColorScheme : BaseColorScheme() { override val darkScheme = darkColorScheme( primary = Color(0xFFA177FF), - onPrimary = Color(0xFF111129), + onPrimary = Color(0xFF3D0090), primaryContainer = Color(0xFFA177FF), - onPrimaryContainer = Color(0xFF111129), - inversePrimary = Color(0xFF006D2F), - secondary = Color(0xFFA177FF), - onSecondary = Color(0xFF111129), - secondaryContainer = Color(0xFFA177FF), - onSecondaryContainer = Color(0xFF111129), - tertiary = Color(0xFF5E25E1), - onTertiary = Color(0xFFE8E8E8), - tertiaryContainer = Color(0xFF111129), - onTertiaryContainer = Color(0xFFDEE8FF), + onPrimaryContainer = Color(0xFFFFFFFF), + secondary = Color(0xFFA177FF), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFF423271), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFA177FF), // Navigation bar selected icon + tertiary = Color(0xFFCDBDFF), // Downloaded badge + onTertiary = Color(0xFF360096), // Downloaded badge text + tertiaryContainer = Color(0xFF5512D8), + onTertiaryContainer = Color(0xFFEFE6FF), + error = Color(0xFFFFB4AB), + onError = Color(0xFF690005), + errorContainer = Color(0xFF93000A), + onErrorContainer = Color(0xFFFFDAD6), background = Color(0xFF111129), - onBackground = Color(0xFFDEE8FF), + onBackground = Color(0xFFE7E0EC), surface = Color(0xFF111129), - onSurface = Color(0xFFDEE8FF), - surfaceVariant = Color(0x2CB6B6B6), - onSurfaceVariant = Color(0xFFE8E8E8), - surfaceTint = Color(0xFFA177FF), - inverseSurface = Color(0xFF221247), - inverseOnSurface = Color(0xFFDEE8FF), - outline = Color(0xA8905FFF), + onSurface = Color(0xFFE7E0EC), + surfaceVariant = Color(0xFF3D2F6B), // Navigation bar background (ThemePrefWidget) + onSurfaceVariant = Color(0xFFCBC3D6), + outline = Color(0xFF958E9F), + outlineVariant = Color(0xFF4A4453), + scrim = Color(0xFF000000), + inverseSurface = Color(0xFFE7E0EC), + inverseOnSurface = Color(0xFF322F38), + inversePrimary = Color(0xFF6D41C8), + surfaceDim = Color(0xFF111129), + surfaceBright = Color(0xFF3B3841), + surfaceContainerLowest = Color(0xFF15132d), + surfaceContainerLow = Color(0xFF171531), + surfaceContainer = Color(0xFF1D193B), // Navigation bar background + surfaceContainerHigh = Color(0xFF241f41), + surfaceContainerHighest = Color(0xFF282446), ) override val lightScheme = lightColorScheme( - primary = Color(0xFF7B46AF), - onPrimary = Color(0xFFEDE2FF), + primary = Color(0xFF6D41C8), + onPrimary = Color(0xFFFFFFFF), primaryContainer = Color(0xFF7B46AF), - onPrimaryContainer = Color(0xFFEDE2FF), - inversePrimary = Color(0xFFD6BAFF), - secondary = Color(0xFF7B46AF), - onSecondary = Color(0xFFEDE2FF), - secondaryContainer = Color(0xFF7B46AF), - onSecondaryContainer = Color(0xFFEDE2FF), - tertiary = Color(0xFFEDE2FF), - onTertiary = Color(0xFF7B46AF), - tertiaryContainer = Color(0xFFEDE2FF), - onTertiaryContainer = Color(0xFF7B46AF), + onPrimaryContainer = Color(0xFF130038), + secondary = Color(0xFF7B46AF), // Unread badge + onSecondary = Color(0xFFEDE2FF), // Unread badge text + secondaryContainer = Color(0xFFC9B0E6), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF7B46AF), // Navigation bar selector icon + tertiary = Color(0xFFEDE2FF), // Downloaded badge + onTertiary = Color(0xFF7B46AF), // Downloaded badge text + tertiaryContainer = Color(0xFF6D3BF0), + onTertiaryContainer = Color(0xFFFFFFFF), + error = Color(0xFFBA1A1A), + onError = Color(0xFFFFFFFF), + errorContainer = Color(0xFFFFDAD6), + onErrorContainer = Color(0xFF410002), background = Color(0xFFEDE2FF), - onBackground = Color(0xFF1B1B22), + onBackground = Color(0xFF1D1A22), surface = Color(0xFFEDE2FF), - onSurface = Color(0xFF1B1B22), - surfaceVariant = Color(0xFFB9B0CC), - onSurfaceVariant = Color(0xD849454E), - surfaceTint = Color(0xFF7B46AF), - inverseSurface = Color(0xFF313033), - inverseOnSurface = Color(0xFFF3EFF4), - outline = Color(0xFF7B46AF), + onSurface = Color(0xFF1D1A22), + surfaceVariant = Color(0xFFE4D5F8), // Navigation bar background (ThemePrefWidget) + onSurfaceVariant = Color(0xFF4A4453), + outline = Color(0xFF7B7485), + outlineVariant = Color(0xFFCBC3D6), + scrim = Color(0xFF000000), + inverseSurface = Color(0xFF322F38), + inverseOnSurface = Color(0xFFF5EEFA), + inversePrimary = Color(0xFFA177FF), + surfaceDim = Color(0xFFDED7E3), + surfaceBright = Color(0xFFEDE2FF), + surfaceContainerLowest = Color(0xFFDACCEC), + surfaceContainerLow = Color(0xFFDED0F1), + surfaceContainer = Color(0xFFE4D5F8), // Navigation bar background + surfaceContainerHigh = Color(0xFFEADCFD), + surfaceContainerHighest = Color(0xFFEEE2FF), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MidnightDuskColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MidnightDuskColorScheme.kt index 7feaae333a..5ae86aa34f 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MidnightDuskColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MidnightDuskColorScheme.kt @@ -23,24 +23,29 @@ internal object MidnightDuskColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFBD1C5C), onPrimaryContainer = Color(0xFFFFFFFF), inversePrimary = Color(0xFFF02475), - secondary = Color(0xFFF02475), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFF02475), - onSecondaryContainer = Color(0xFFFFFFFF), - tertiary = Color(0xFF55971C), - onTertiary = Color(0xFFFFFFFF), + secondary = Color(0xFFF02475), // Unread badge + onSecondary = Color(0xFF16151D), // Unread badge text + secondaryContainer = Color(0xFF66183C), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFF02475), // Navigation bar selector icon + tertiary = Color(0xFF55971C), // Downloaded badge + onTertiary = Color(0xFF16151D), // Downloaded badge text tertiaryContainer = Color(0xFF386412), onTertiaryContainer = Color(0xFFE5E1E5), background = Color(0xFF16151D), onBackground = Color(0xFFE5E1E5), surface = Color(0xFF16151D), onSurface = Color(0xFFE5E1E5), - surfaceVariant = Color(0xFF524346), + surfaceVariant = Color(0xFF281624), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFD6C1C4), surfaceTint = Color(0xFFF02475), inverseSurface = Color(0xFF333043), inverseOnSurface = Color(0xFFFFFFFF), outline = Color(0xFF9F8C8F), + surfaceContainerLowest = Color(0xFF221320), + surfaceContainerLow = Color(0xFF251522), + surfaceContainer = Color(0xFF281624), // Navigation bar background + surfaceContainerHigh = Color(0xFF2D1C2A), + surfaceContainerHighest = Color(0xFF2F1F2C), ) override val lightScheme = lightColorScheme( @@ -49,23 +54,28 @@ internal object MidnightDuskColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFFFD9E1), onPrimaryContainer = Color(0xFF3F0017), inversePrimary = Color(0xFFFFB1C4), - secondary = Color(0xFFBB0054), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFFFD9E1), - onSecondaryContainer = Color(0xFF3F0017), - tertiary = Color(0xFF006638), - onTertiary = Color(0xFFFFFFFF), + secondary = Color(0xFFBB0054), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFFEFBAD4), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFD1377C), // Navigation bar selector icon + tertiary = Color(0xFF006638), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text tertiaryContainer = Color(0xFF00894b), onTertiaryContainer = Color(0xFF2D1600), background = Color(0xFFFFFBFF), onBackground = Color(0xFF1C1B1F), surface = Color(0xFFFFFBFF), onSurface = Color(0xFF1C1B1F), - surfaceVariant = Color(0xFFF3DDE0), + surfaceVariant = Color(0xFFF9E6F1), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF524346), surfaceTint = Color(0xFFBB0054), inverseSurface = Color(0xFF313033), inverseOnSurface = Color(0xFFF4F0F4), outline = Color(0xFF847376), + surfaceContainerLowest = Color(0xFFDAC0CD), + surfaceContainerLow = Color(0xFFE8D1DD), + surfaceContainer = Color(0xFFF9E6F1), // Navigation bar background + surfaceContainerHigh = Color(0xFFFCF3F8), + surfaceContainerHighest = Color(0xFFFEF9FC), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt index d493e2d626..2e89b9ea4b 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt @@ -17,19 +17,19 @@ internal object NordColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF88C0D0), onPrimaryContainer = Color(0xFF2E3440), inversePrimary = Color(0xFF397E91), - secondary = Color(0xFF81A1C1), - onSecondary = Color(0xFF2E3440), - secondaryContainer = Color(0xFF81A1C1), - onSecondaryContainer = Color(0xFF2E3440), - tertiary = Color(0xFF5E81AC), - onTertiary = Color(0xFF000000), + secondary = Color(0xFF81A1C1), // Unread badge + onSecondary = Color(0xFF2E3440), // Unread badge text + secondaryContainer = Color(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon + tertiary = Color(0xFF5E81AC), // Downloaded badge + onTertiary = Color(0xFF000000), // Downloaded badge text tertiaryContainer = Color(0xFF5E81AC), onTertiaryContainer = Color(0xFF000000), background = Color(0xFF2E3440), onBackground = Color(0xFFECEFF4), - surface = Color(0xFF3B4252), + surface = Color(0xFF2E3440), onSurface = Color(0xFFECEFF4), - surfaceVariant = Color(0xFF2E3440), + surfaceVariant = Color(0xFF414C5C), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFECEFF4), surfaceTint = Color(0xFF88C0D0), inverseSurface = Color(0xFFD8DEE9), @@ -39,6 +39,11 @@ internal object NordColorScheme : BaseColorScheme() { onError = Color(0xFF2E3440), errorContainer = Color(0xFFBF616A), onErrorContainer = Color(0xFF000000), + surfaceContainerLowest = Color(0xFF373F4D), + surfaceContainerLow = Color(0xFF3E4756), + surfaceContainer = Color(0xFF414C5C), + surfaceContainerHigh = Color(0xFF4E5766), + surfaceContainerHighest = Color(0xFF505968), // Navigation bar background ) override val lightScheme = lightColorScheme( @@ -47,19 +52,19 @@ internal object NordColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF5E81AC), onPrimaryContainer = Color(0xFF000000), inversePrimary = Color(0xFF8CA8CD), - secondary = Color(0xFF81A1C1), - onSecondary = Color(0xFF2E3440), - secondaryContainer = Color(0xFF81A1C1), - onSecondaryContainer = Color(0xFF2E3440), - tertiary = Color(0xFF88C0D0), - onTertiary = Color(0xFF2E3440), + secondary = Color(0xFF81A1C1), // Unread badge + onSecondary = Color(0xFF2E3440), // Unread badge text + secondaryContainer = Color(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon + tertiary = Color(0xFF88C0D0), // Downloaded badge + onTertiary = Color(0xFF2E3440), // Downloaded badge text tertiaryContainer = Color(0xFF88C0D0), onTertiaryContainer = Color(0xFF2E3440), background = Color(0xFFECEFF4), onBackground = Color(0xFF2E3440), surface = Color(0xFFE5E9F0), onSurface = Color(0xFF2E3440), - surfaceVariant = Color(0xFFffffff), + surfaceVariant = Color(0xFFDAE0EA), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF2E3440), surfaceTint = Color(0xFF5E81AC), inverseSurface = Color(0xFF3B4252), @@ -68,5 +73,10 @@ internal object NordColorScheme : BaseColorScheme() { onError = Color(0xFFECEFF4), errorContainer = Color(0xFFBF616A), onErrorContainer = Color(0xFF000000), + surfaceContainerLowest = Color(0xFFD1D7E0), + surfaceContainerLow = Color(0xFFD6DCE6), + surfaceContainer = Color(0xFFDAE0EA), // Navigation bar background + surfaceContainerHigh = Color(0xFFE9EDF3), + surfaceContainerHighest = Color(0xFFF2F4F8), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/StrawberryColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/StrawberryColorScheme.kt index 98417e3364..e1b096e259 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/StrawberryColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/StrawberryColorScheme.kt @@ -18,54 +18,78 @@ import androidx.compose.ui.graphics.Color internal object StrawberryColorScheme : BaseColorScheme() { override val darkScheme = darkColorScheme( - primary = Color(0xFFFFB2B9), - onPrimary = Color(0xFF67001B), - primaryContainer = Color(0xFF91002A), - onPrimaryContainer = Color(0xFFFFDADD), - inversePrimary = Color(0xFFB61E40), - secondary = Color(0xFFFFB2B9), - onSecondary = Color(0xFF67001B), - secondaryContainer = Color(0xFF91002A), - onSecondaryContainer = Color(0xFFFFDADD), - tertiary = Color(0xFFE8C08E), - onTertiary = Color(0xFF432C06), - tertiaryContainer = Color(0xFF5D421B), - onTertiaryContainer = Color(0xFFFFDDB1), + primary = Color(0xFFFFB2B8), + onPrimary = Color(0xFF67001D), + primaryContainer = Color(0xFFD53855), + onPrimaryContainer = Color(0xFFFFFFFF), + secondary = Color(0xFFED4A65), // Unread badge + onSecondary = Color(0xFF201A1A), // Unread badge text + secondaryContainer = Color(0xFF91002A), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFFFFFFF), // Navigation bar selector icon + tertiary = Color(0xFFE8C08E), // Downloaded badge + onTertiary = Color(0xFF201A1A), // Downloaded badge text + tertiaryContainer = Color(0xFF775930), + onTertiaryContainer = Color(0xFFFFF7F1), + error = Color(0xFFFFB4AB), + onError = Color(0xFF690005), + errorContainer = Color(0xFF93000A), + onErrorContainer = Color(0xFFFFDAD6), background = Color(0xFF201A1A), - onBackground = Color(0xFFECDFDF), + onBackground = Color(0xFFF7DCDD), surface = Color(0xFF201A1A), - onSurface = Color(0xFFECDFDF), - surfaceVariant = Color(0xFF534344), - onSurfaceVariant = Color(0xFFD7C1C2), - surfaceTint = Color(0xFFFFB2B9), - inverseSurface = Color(0xFFECDFDF), - inverseOnSurface = Color(0xFF201A1A), - outline = Color(0xFFA08C8D), + onSurface = Color(0xFFF7DCDD), + surfaceVariant = Color(0xFF322727), // Navigation bar background (ThemePrefWidget) + onSurfaceVariant = Color(0xFFE1BEC0), + outline = Color(0xFFA9898B), + outlineVariant = Color(0xFF594042), + scrim = Color(0xFF000000), + inverseSurface = Color(0xFFF7DCDD), + inverseOnSurface = Color(0xFF3D2C2D), + inversePrimary = Color(0xFFB61F40), + surfaceDim = Color(0xFF1D1011), + surfaceBright = Color(0xFF463536), + surfaceContainerLowest = Color(0xFF2C2222), + surfaceContainerLow = Color(0xFF302525), + surfaceContainer = Color(0xFF322727), // Navigation bar background + surfaceContainerHigh = Color(0xFF3C2F2F), + surfaceContainerHighest = Color(0xFF463737), ) override val lightScheme = lightColorScheme( - primary = Color(0xFFB61E40), + primary = Color(0xFFA10833), onPrimary = Color(0xFFFFFFFF), - primaryContainer = Color(0xFFFFDADD), - onPrimaryContainer = Color(0xFF40000D), - inversePrimary = Color(0xFFFFB2B9), - secondary = Color(0xFFB61E40), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFFFDADD), - onSecondaryContainer = Color(0xFF40000D), - tertiary = Color(0xFF775930), - onTertiary = Color(0xFFFFFFFF), - tertiaryContainer = Color(0xFFFFDDB1), - onTertiaryContainer = Color(0xFF2A1800), - background = Color(0xFFFCFCFC), - onBackground = Color(0xFF201A1A), - surface = Color(0xFFFCFCFC), - onSurface = Color(0xFF201A1A), - surfaceVariant = Color(0xFFF4DDDD), - onSurfaceVariant = Color(0xFF534344), - surfaceTint = Color(0xFFB61E40), - inverseSurface = Color(0xFF362F2F), - inverseOnSurface = Color(0xFFFBEDED), - outline = Color(0xFF857374), + primaryContainer = Color(0xFFD53855), + onPrimaryContainer = Color(0xFFFFFFFF), + secondary = Color(0xFFA10833), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFFD53855), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFF6EAED), // Navigation bar selector icon + tertiary = Color(0xFF5F441D), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text + tertiaryContainer = Color(0xFF87683D), + onTertiaryContainer = Color(0xFFFFFFFF), + error = Color(0xFFBA1A1A), + onError = Color(0xFFFFFFFF), + errorContainer = Color(0xFFFFDAD6), + onErrorContainer = Color(0xFF410002), + background = Color(0xFFFAFAFA), + onBackground = Color(0xFF261819), + surface = Color(0xFFFAFAFA), + onSurface = Color(0xFF261819), + surfaceVariant = Color(0xFFF6EAED), // Navigation bar background (ThemePrefWidget) + onSurfaceVariant = Color(0xFF594042), + outline = Color(0xFF8D7071), + outlineVariant = Color(0xFFE1BEC0), + scrim = Color(0xFF000000), + inverseSurface = Color(0xFF3D2C2D), + inverseOnSurface = Color(0xFFFFECED), + inversePrimary = Color(0xFFFFB2B8), + surfaceDim = Color(0xFFEED4D5), + surfaceBright = Color(0xFFFFF8F7), + surfaceContainerLowest = Color(0xFFF7DCDD), + surfaceContainerLow = Color(0xFFFDE2E3), + surfaceContainer = Color(0xFFF6EAED), // Navigation bar background + surfaceContainerHigh = Color(0xFFFFF0F0), + surfaceContainerHighest = Color(0xFFFFFFFF), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TachiyomiColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TachiyomiColorScheme.kt index 974d5f22d3..5faeb7df01 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TachiyomiColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TachiyomiColorScheme.kt @@ -22,19 +22,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF00429B), onPrimaryContainer = Color(0xFFD9E2FF), inversePrimary = Color(0xFF0058CA), - secondary = Color(0xFFB0C6FF), - onSecondary = Color(0xFF002D6E), - secondaryContainer = Color(0xFF00429B), - onSecondaryContainer = Color(0xFFD9E2FF), - tertiary = Color(0xFF7ADC77), - onTertiary = Color(0xFF003909), + secondary = Color(0xFFB0C6FF), // Unread badge + onSecondary = Color(0xFF002D6E), // Unread badge text + secondaryContainer = Color(0xFF00429B), // Navigation bar selector pill & pro + onSecondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector icon + tertiary = Color(0xFF7ADC77), // Downloaded badge + onTertiary = Color(0xFF003909), // Downloaded badge text tertiaryContainer = Color(0xFF005312), onTertiaryContainer = Color(0xFF95F990), background = Color(0xFF1B1B1F), onBackground = Color(0xFFE3E2E6), surface = Color(0xFF1B1B1F), onSurface = Color(0xFFE3E2E6), - surfaceVariant = Color(0xFF44464F), + surfaceVariant = Color(0xFF211F26), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFC5C6D0), surfaceTint = Color(0xFFB0C6FF), inverseSurface = Color(0xFFE3E2E6), @@ -45,6 +45,11 @@ internal object TachiyomiColorScheme : BaseColorScheme() { onErrorContainer = Color(0xFFFFDAD6), outline = Color(0xFF8F9099), outlineVariant = Color(0xFF44464F), + surfaceContainerLowest = Color(0xFF1A181D), + surfaceContainerLow = Color(0xFF1E1C22), + surfaceContainer = Color(0xFF211F26), // Navigation bar background + surfaceContainerHigh = Color(0xFF292730), + surfaceContainerHighest = Color(0xFF302E38), ) override val lightScheme = lightColorScheme( @@ -53,19 +58,19 @@ internal object TachiyomiColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFD9E2FF), onPrimaryContainer = Color(0xFF001945), inversePrimary = Color(0xFFB0C6FF), - secondary = Color(0xFF0058CA), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFD9E2FF), - onSecondaryContainer = Color(0xFF001945), - tertiary = Color(0xFF006E1B), - onTertiary = Color(0xFFFFFFFF), + secondary = Color(0xFF0058CA), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFFD9E2FF), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF001945), // Navigation bar selector icon + tertiary = Color(0xFF006E1B), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text tertiaryContainer = Color(0xFF95F990), onTertiaryContainer = Color(0xFF002203), background = Color(0xFFFEFBFF), onBackground = Color(0xFF1B1B1F), surface = Color(0xFFFEFBFF), onSurface = Color(0xFF1B1B1F), - surfaceVariant = Color(0xFFE1E2EC), + surfaceVariant = Color(0xFFF3EDF7), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF44464F), surfaceTint = Color(0xFF0058CA), inverseSurface = Color(0xFF303034), @@ -76,5 +81,10 @@ internal object TachiyomiColorScheme : BaseColorScheme() { onErrorContainer = Color(0xFF410002), outline = Color(0xFF757780), outlineVariant = Color(0xFFC5C6D0), + surfaceContainerLowest = Color(0xFFF5F1F8), + surfaceContainerLow = Color(0xFFF7F2FA), + surfaceContainer = Color(0xFFF3EDF7), // Navigation bar background + surfaceContainerHigh = Color(0xFFFCF7FF), + surfaceContainerHighest = Color(0xFFFCF7FF), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TakoColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TakoColorScheme.kt index 244e769d4c..7988129638 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TakoColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TakoColorScheme.kt @@ -23,24 +23,29 @@ internal object TakoColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFF3B375), onPrimaryContainer = Color(0xFF38294E), inversePrimary = Color(0xFF84531E), - secondary = Color(0xFFF3B375), - onSecondary = Color(0xFF38294E), - secondaryContainer = Color(0xFFF3B375), - onSecondaryContainer = Color(0xFF38294E), - tertiary = Color(0xFF66577E), - onTertiary = Color(0xFFF3B375), + secondary = Color(0xFFF3B375), // Unread badge + onSecondary = Color(0xFF38294E), // Unread badge text + secondaryContainer = Color(0xFF5C4D4B), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFF3B375), // Navigation bar selector icon + tertiary = Color(0xFF66577E), // Downloaded badge + onTertiary = Color(0xFFF3B375), // Downloaded badge text tertiaryContainer = Color(0xFF4E4065), onTertiaryContainer = Color(0xFFEDDCFF), background = Color(0xFF21212E), onBackground = Color(0xFFE3E0F2), surface = Color(0xFF21212E), onSurface = Color(0xFFE3E0F2), - surfaceVariant = Color(0xFF49454E), + surfaceVariant = Color(0xFF2A2A3C), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFCBC4CE), surfaceTint = Color(0xFF66577E), inverseSurface = Color(0xFFE5E1E6), inverseOnSurface = Color(0xFF1B1B1E), outline = Color(0xFF958F99), + surfaceContainerLowest = Color(0xFF20202E), + surfaceContainerLow = Color(0xFF262636), + surfaceContainer = Color(0xFF2A2A3C), // Navigation bar background + surfaceContainerHigh = Color(0xFF303044), + surfaceContainerHighest = Color(0xFF36364D), ) override val lightScheme = lightColorScheme( @@ -49,23 +54,28 @@ internal object TakoColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF66577E), onPrimaryContainer = Color(0xFFF3B375), inversePrimary = Color(0xFFD6BAFF), - secondary = Color(0xFF66577E), - onSecondary = Color(0xFFF3B375), - secondaryContainer = Color(0xFF66577E), - onSecondaryContainer = Color(0xFFF3B375), - tertiary = Color(0xFFF3B375), - onTertiary = Color(0xFF574360), + secondary = Color(0xFF66577E), // Unread badge + onSecondary = Color(0xFFF3B375), // Unread badge text + secondaryContainer = Color(0xFFC8BED0), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF66577E), // Navigation bar selector icon + tertiary = Color(0xFFF3B375), // Downloaded badge + onTertiary = Color(0xFF574360), // Downloaded badge text tertiaryContainer = Color(0xFFFDD6B0), onTertiaryContainer = Color(0xFF221437), background = Color(0xFFF7F5FF), onBackground = Color(0xFF1B1B22), surface = Color(0xFFF7F5FF), onSurface = Color(0xFF1B1B22), - surfaceVariant = Color(0xFFE8E0EB), + surfaceVariant = Color(0xFFE8E0EB), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF49454E), surfaceTint = Color(0xFF66577E), inverseSurface = Color(0xFF313033), inverseOnSurface = Color(0xFFF3EFF4), outline = Color(0xFF7A757E), + surfaceContainerLowest = Color(0xFFD7D0DA), + surfaceContainerLow = Color(0xFFDFD8E2), + surfaceContainer = Color(0xFFE8E0EB), // Navigation bar background + surfaceContainerHigh = Color(0xFFEEE6F1), + surfaceContainerHighest = Color(0xFFF7EEFA), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TealTurqoiseColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TealTurqoiseColorScheme.kt index e914b49fcb..28811fa6a3 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TealTurqoiseColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TealTurqoiseColorScheme.kt @@ -15,24 +15,29 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF40E0D0), onPrimaryContainer = Color(0xFF000000), inversePrimary = Color(0xFF008080), - secondary = Color(0xFF40E0D0), - onSecondary = Color(0xFF000000), - secondaryContainer = Color(0xFF18544E), - onSecondaryContainer = Color(0xFF40E0D0), - tertiary = Color(0xFFBF1F2F), - onTertiary = Color(0xFFFFFFFF), + secondary = Color(0xFF40E0D0), // Unread badge + onSecondary = Color(0xFF000000), // Unread badge text + secondaryContainer = Color(0xFF18544E), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF40E0D0), // Navigation bar selector icon + tertiary = Color(0xFFBF1F2F), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text tertiaryContainer = Color(0xFF200508), onTertiaryContainer = Color(0xFFBF1F2F), background = Color(0xFF202125), onBackground = Color(0xFFDFDEDA), surface = Color(0xFF202125), onSurface = Color(0xFFDFDEDA), - surfaceVariant = Color(0xFF3F4947), + surfaceVariant = Color(0xFF233133), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFDFDEDA), surfaceTint = Color(0xFF40E0D0), inverseSurface = Color(0xFFDFDEDA), inverseOnSurface = Color(0xFF202125), outline = Color(0xFF899391), + surfaceContainerLowest = Color(0xFF202C2E), + surfaceContainerLow = Color(0xFF222F31), + surfaceContainer = Color(0xFF233133), // Navigation bar background + surfaceContainerHigh = Color(0xFF28383A), + surfaceContainerHighest = Color(0xFF2F4244), ) override val lightScheme = lightColorScheme( @@ -41,23 +46,28 @@ internal object TealTurqoiseColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF008080), onPrimaryContainer = Color(0xFFFFFFFF), inversePrimary = Color(0xFF40E0D0), - secondary = Color(0xFF008080), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFBFDFDF), - onSecondaryContainer = Color(0xFF008080), - tertiary = Color(0xFFFF7F7F), - onTertiary = Color(0xFF000000), + secondary = Color(0xFF008080), // Unread badge text + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFFCFE5E4), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF008080), // Navigation bar selector icon + tertiary = Color(0xFFFF7F7F), // Downloaded badge + onTertiary = Color(0xFF000000), // Downloaded badge text tertiaryContainer = Color(0xFF2A1616), onTertiaryContainer = Color(0xFFFF7F7F), background = Color(0xFFFAFAFA), onBackground = Color(0xFF050505), surface = Color(0xFFFAFAFA), onSurface = Color(0xFF050505), - surfaceVariant = Color(0xFFDAE5E2), + surfaceVariant = Color(0xFFEBF3F1), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF050505), surfaceTint = Color(0xFFBFDFDF), inverseSurface = Color(0xFF050505), inverseOnSurface = Color(0xFFFAFAFA), outline = Color(0xFF6F7977), + surfaceContainerLowest = Color(0xFFE1E9E7), + surfaceContainerLow = Color(0xFFE6EEEC), + surfaceContainer = Color(0xFFEBF3F1), // Navigation bar background + surfaceContainerHigh = Color(0xFFF0F8F6), + surfaceContainerHighest = Color(0xFFF7FFFD), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TidalWaveColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TidalWaveColorScheme.kt index c56a1fa57e..09dc248c0a 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TidalWaveColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/TidalWaveColorScheme.kt @@ -22,24 +22,29 @@ internal object TidalWaveColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF004d61), onPrimaryContainer = Color(0xFFb8eaff), inversePrimary = Color(0xFFa12b03), - secondary = Color(0xFF5ed4fc), - onSecondary = Color(0xFF003544), - secondaryContainer = Color(0xFF004d61), - onSecondaryContainer = Color(0xFFb8eaff), - tertiary = Color(0xFF92f7bc), - onTertiary = Color(0xFF001c3b), + secondary = Color(0xFF5ed4fc), // Unread badge + onSecondary = Color(0xFF003544), // Unread badge text + secondaryContainer = Color(0xFF004d61), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFb8eaff), // Navigation bar selector icon + tertiary = Color(0xFF92f7bc), // Downloaded badge + onTertiary = Color(0xFF001c3b), // Downloaded badge text tertiaryContainer = Color(0xFFc3fada), onTertiaryContainer = Color(0xFF78ffd6), background = Color(0xFF001c3b), onBackground = Color(0xFFd5e3ff), surface = Color(0xFF001c3b), onSurface = Color(0xFFd5e3ff), - surfaceVariant = Color(0xFF40484c), + surfaceVariant = Color(0xFF082b4b), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFbfc8cc), surfaceTint = Color(0xFF5ed4fc), inverseSurface = Color(0xFFffe3c4), inverseOnSurface = Color(0xFF001c3b), outline = Color(0xFF8a9296), + surfaceContainerLowest = Color(0xFF072642), + surfaceContainerLow = Color(0xFF072947), + surfaceContainer = Color(0xFF082b4b), // Navigation bar background + surfaceContainerHigh = Color(0xFF093257), + surfaceContainerHighest = Color(0xFF0A3861), ) override val lightScheme = lightColorScheme( @@ -48,23 +53,28 @@ internal object TidalWaveColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFB4D4DF), onPrimaryContainer = Color(0xFF001f28), inversePrimary = Color(0xFFff987f), - secondary = Color(0xFF006780), - onSecondary = Color(0xFFffffff), - secondaryContainer = Color(0xFFb8eaff), - onSecondaryContainer = Color(0xFF001f28), - tertiary = Color(0xFF92f7bc), - onTertiary = Color(0xFF001c3b), + secondary = Color(0xFF006780), // Unread badge + onSecondary = Color(0xFFffffff), // Unread badge text + secondaryContainer = Color(0xFF9AE1FF), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF001f28), // Navigation bar selector icon + tertiary = Color(0xFF92f7bc), // Downloaded badge + onTertiary = Color(0xFF001c3b), // Downloaded badge text tertiaryContainer = Color(0xFFc3fada), onTertiaryContainer = Color(0xFF78ffd6), background = Color(0xFFfdfbff), onBackground = Color(0xFF001c3b), surface = Color(0xFFfdfbff), onSurface = Color(0xFF001c3b), - surfaceVariant = Color(0xFFdce4e8), + surfaceVariant = Color(0xFFe8eff5), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF40484c), surfaceTint = Color(0xFF006780), inverseSurface = Color(0xFF020400), inverseOnSurface = Color(0xFFffe3c4), outline = Color(0xFF70787c), + surfaceContainerLowest = Color(0xFFe2e8ec), + surfaceContainerLow = Color(0xFFe5ecf1), + surfaceContainer = Color(0xFFe8eff5), // Navigation bar background + surfaceContainerHigh = Color(0xFFedf4fA), + surfaceContainerHighest = Color(0xFFf5faff), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YinYangColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YinYangColorScheme.kt index da9dee4241..1e9b129785 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YinYangColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YinYangColorScheme.kt @@ -17,24 +17,29 @@ internal object YinYangColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFFFFFFF), onPrimaryContainer = Color(0xFF000000), inversePrimary = Color(0xFFCECECE), - secondary = Color(0xFFFFFFFF), - onSecondary = Color(0xFF5A5A5A), - secondaryContainer = Color(0xFF717171), - onSecondaryContainer = Color(0xFFE4E4E4), - tertiary = Color(0xFF000000), - onTertiary = Color(0xFFFFFFFF), + secondary = Color(0xFFFFFFFF), // Unread badge + onSecondary = Color(0xFF5A5A5A), // Unread badge text + secondaryContainer = Color(0xFF717171), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFE4E4E4), // Navigation bar selector icon + tertiary = Color(0xFF000000), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text tertiaryContainer = Color(0xFF00419E), onTertiaryContainer = Color(0xFFD8E2FF), background = Color(0xFF1E1E1E), onBackground = Color(0xFFE6E6E6), surface = Color(0xFF1E1E1E), onSurface = Color(0xFFE6E6E6), - surfaceVariant = Color(0xFF4E4E4E), + surfaceVariant = Color(0xFF313131), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFD1D1D1), surfaceTint = Color(0xFFFFFFFF), inverseSurface = Color(0xFFE6E6E6), inverseOnSurface = Color(0xFF1E1E1E), outline = Color(0xFF999999), + surfaceContainerLowest = Color(0xFF2A2A2A), + surfaceContainerLow = Color(0xFF2D2D2D), + surfaceContainer = Color(0xFF313131), // Navigation bar background + surfaceContainerHigh = Color(0xFF383838), + surfaceContainerHighest = Color(0xFF3F3F3F), ) override val lightScheme = lightColorScheme( @@ -43,23 +48,28 @@ internal object YinYangColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF000000), onPrimaryContainer = Color(0xFFFFFFFF), inversePrimary = Color(0xFFA6A6A6), - secondary = Color(0xFF000000), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFDDDDDD), - onSecondaryContainer = Color(0xFF0C0C0C), - tertiary = Color(0xFFFFFFFF), - onTertiary = Color(0xFF000000), + secondary = Color(0xFF000000), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFFDDDDDD), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF0C0C0C), // Navigation bar selector icon + tertiary = Color(0xFFFFFFFF), // Downloaded badge + onTertiary = Color(0xFF000000), // Downloaded badge text tertiaryContainer = Color(0xFFD8E2FF), onTertiaryContainer = Color(0xFF001947), background = Color(0xFFFDFDFD), onBackground = Color(0xFF222222), surface = Color(0xFFFDFDFD), onSurface = Color(0xFF222222), - surfaceVariant = Color(0xFFEDEDED), + surfaceVariant = Color(0xFFE8E8E8), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF515151), surfaceTint = Color(0xFF000000), inverseSurface = Color(0xFF333333), inverseOnSurface = Color(0xFFF4F4F4), outline = Color(0xFF838383), + surfaceContainerLowest = Color(0xFFCFCFCF), + surfaceContainerLow = Color(0xFFDADADA), + surfaceContainer = Color(0xFFE8E8E8), // Navigation bar background + surfaceContainerHigh = Color(0xFFECECEC), + surfaceContainerHighest = Color(0xFFEFEFEF), ) } diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YotsubaColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YotsubaColorScheme.kt index fdda6b7dca..58007a6802 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YotsubaColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/YotsubaColorScheme.kt @@ -23,24 +23,29 @@ internal object YotsubaColorScheme : BaseColorScheme() { primaryContainer = Color(0xFF862200), onPrimaryContainer = Color(0xFFFFDBCF), inversePrimary = Color(0xFFAE3200), - secondary = Color(0xFFFFB59D), - onSecondary = Color(0xFF5F1600), - secondaryContainer = Color(0xFF862200), - onSecondaryContainer = Color(0xFFFFDBCF), - tertiary = Color(0xFFD7C68D), - onTertiary = Color(0xFF3A2F05), + secondary = Color(0xFFFFB59D), // Unread badge + onSecondary = Color(0xFF5F1600), // Unread badge text + secondaryContainer = Color(0xFF862200), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFFFFDBCF), // Navigation bar selector icon + tertiary = Color(0xFFD7C68D), // Downloaded badge + onTertiary = Color(0xFF3A2F05), // Downloaded badge text tertiaryContainer = Color(0xFF524619), onTertiaryContainer = Color(0xFFF5E2A7), background = Color(0xFF211A18), onBackground = Color(0xFFEDE0DD), surface = Color(0xFF211A18), onSurface = Color(0xFFEDE0DD), - surfaceVariant = Color(0xFF53433F), + surfaceVariant = Color(0xFF332723), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFFD8C2BC), surfaceTint = Color(0xFFFFB59D), inverseSurface = Color(0xFFEDE0DD), inverseOnSurface = Color(0xFF211A18), outline = Color(0xFFA08C87), + surfaceContainerLowest = Color(0xFF2E221F), + surfaceContainerLow = Color(0xFF312521), + surfaceContainer = Color(0xFF332723), // Navigation bar background + surfaceContainerHigh = Color(0xFF413531), + surfaceContainerHighest = Color(0xFF4C403D), ) override val lightScheme = lightColorScheme( @@ -49,23 +54,28 @@ internal object YotsubaColorScheme : BaseColorScheme() { primaryContainer = Color(0xFFFFDBCF), onPrimaryContainer = Color(0xFF3B0A00), inversePrimary = Color(0xFFFFB59D), - secondary = Color(0xFFAE3200), - onSecondary = Color(0xFFFFFFFF), - secondaryContainer = Color(0xFFFFDBCF), - onSecondaryContainer = Color(0xFF3B0A00), - tertiary = Color(0xFF6B5E2F), - onTertiary = Color(0xFFFFFFFF), + secondary = Color(0xFFAE3200), // Unread badge + onSecondary = Color(0xFFFFFFFF), // Unread badge text + secondaryContainer = Color(0xFFEBCDC2), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF3B0A00), // Navigation bar selector icon + tertiary = Color(0xFF6B5E2F), // Downloaded badge + onTertiary = Color(0xFFFFFFFF), // Downloaded badge text tertiaryContainer = Color(0xFFF5E2A7), onTertiaryContainer = Color(0xFF231B00), background = Color(0xFFFCFCFC), onBackground = Color(0xFF211A18), surface = Color(0xFFFCFCFC), onSurface = Color(0xFF211A18), - surfaceVariant = Color(0xFFF5DED8), + surfaceVariant = Color(0xFFF6EBE7), // Navigation bar background (ThemePrefWidget) onSurfaceVariant = Color(0xFF53433F), surfaceTint = Color(0xFFAE3200), inverseSurface = Color(0xFF362F2D), inverseOnSurface = Color(0xFFFBEEEB), outline = Color(0xFF85736E), + surfaceContainerLowest = Color(0xFFECE3E0), + surfaceContainerLow = Color(0xFFF1E7E4), + surfaceContainer = Color(0xFFF6EBE7), // Navigation bar background + surfaceContainerHigh = Color(0xFFFAF4F2), + surfaceContainerHighest = Color(0xFFFBF6F4), ) } From 7af51d6c48000a7a4503e68d0125046ae5db5433 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:18:07 +0200 Subject: [PATCH 26/74] Update renovate config Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .github/renovate.json5 | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0424c4ed9d..68076daf35 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,21 +1,17 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:base" - ], - "schedule": ["every friday"], + "extends": ["config:base"], "labels": ["Dependencies"], "packageRules": [ { - "groupName": "Compose BOM", + "groupName": "Compose BOM (Alpha)", "matchPackageNames": [ "dev.chrisbanes.compose:compose-bom" ], "ignoreUnstable": false }, { - // Compiler plugins are tightly coupled to Kotlin version - "groupName": "Kotlin", + "groupName": "Kotlin and Compose Compiler", "matchPackagePrefixes": [ "androidx.compose.compiler", "org.jetbrains.kotlin.", From efccb5a6f6d8073e39f0b5399320059b63f92c8d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 16:09:21 +0600 Subject: [PATCH 27/74] chore(deps): update actions/checkout action to v4.1.6 (#796) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 84ea5166dee476db771e8b5b38839a34a80de976) --- .github/workflows/build_pull_request.yml | 2 +- .github/workflows/build_push.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 0630a48a63..f15999d3ba 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 0e07debe3b..c79489c72b 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@44c2b7a8a4ea60a981eaca3cf939b5f4305c123b # v4.1.5 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 From 5945693068ce521abd90e29feca98db0b1808564 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:37:58 +0200 Subject: [PATCH 28/74] chore(deps): update kotlin and compose compiler Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/compose.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 00baa130b0..4f01257f12 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,6 +1,6 @@ [versions] -compiler = "1.5.12" -compose-bom = "2024.05.00-alpha01" +compiler = "1.5.14" +compose-bom = "2024.05.00-alpha02" accompanist = "0.35.0-alpha" [libraries] From 8b406fd4eb4186f1092a849c86902e9d81279d05 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:42:29 +0200 Subject: [PATCH 29/74] fix: storage permission request for non-conforming devices * fix: storage permission request for non-conforming devices * fix: catch more specific exception * chore: add toast message to indicate missing persistent permissions * chore: correct newly introduced translaction string * Change error toast message --------- Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Co-authored-by: Sven <5113257+mainrs@users.noreply.github.com> --- .../more/settings/screen/SettingsDataScreen.kt | 12 +++++++++++- i18n/src/commonMain/moko-resources/base/strings.xml | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 422bd3ae34..d15d6ccf5c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -113,7 +113,17 @@ object SettingsDataScreen : SearchableSettings { val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION - context.contentResolver.takePersistableUriPermission(uri, flags) + // For some reason InkBook devices do not implement the SAF properly. Persistable URI grants do not + // work. However, simply retrieving the URI and using it works fine for these devices. Access is not + // revoked after the app is closed or the device is restarted. + // This also holds for some Samsung devices. Thus, we simply execute inside of a try-catch block and + // ignore the exception if it is thrown. + try { + context.contentResolver.takePersistableUriPermission(uri, flags) + } catch (e: SecurityException) { + logcat(LogPriority.ERROR, e) + context.toast(MR.strings.file_picker_uri_permission_unsupported) + } UniFile.fromUri(context, uri)?.let { storageDirPref.set(it.uri.toString()) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 735644f35f..4f1f053633 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -751,6 +751,7 @@ Select cover image Select backup file No file picker app found + Failed to acquire persistent folder access. The app may behave unexpectedly. No file selected Download Open on GitHub From fc5fc7ed6526246303a2bb54aef8374f018abfce Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:46:12 +0200 Subject: [PATCH 30/74] Fix MigratorTest after update to io.mockk v1.13.11 * Fix MigratorTest after update to io.mockk v1.13.11 Causing error: io.mockk.MockKException: was not can only be called on a mocked object * remove import Co-authored-by: Tran M. Cuong <16017808+cuong-tran@users.noreply.github.com> --- app/src/test/java/mihon/core/migration/MigratorTest.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/mihon/core/migration/MigratorTest.kt b/app/src/test/java/mihon/core/migration/MigratorTest.kt index e0feff3fd3..47c4bc7c2e 100644 --- a/app/src/test/java/mihon/core/migration/MigratorTest.kt +++ b/app/src/test/java/mihon/core/migration/MigratorTest.kt @@ -1,6 +1,5 @@ package mihon.core.migration -import io.mockk.Called import io.mockk.slot import io.mockk.spyk import io.mockk.verify @@ -59,7 +58,7 @@ class MigratorTest { val result = execute.await() assertFalse(result) - verify { migrationJobFactory.create(any()) wasNot Called } + verify(exactly = 0) { migrationJobFactory.create(any()) } } @Test @@ -72,7 +71,7 @@ class MigratorTest { val result = execute.await() assertFalse(result) - verify { migrationJobFactory.create(any()) wasNot Called } + verify(exactly = 0) { migrationJobFactory.create(any()) } } @Test From d09c85ea0c7dd7899535046c6eb54c55012ce1de Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 18:47:07 +0200 Subject: [PATCH 31/74] chore(deps): update actions/dependency-review-action action to v4.3.3 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index f15999d3ba..c4faa0ca88 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -28,7 +28,7 @@ jobs: uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 - name: Dependency Review - uses: actions/dependency-review-action@0c155c5e8556a497adf53f2c18edabf945ed8e70 # v4.3.2 + uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3 - name: Set up JDK uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 From 639d56364fb54e7072517dc64d2e1374d4051c69 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 19:10:02 +0200 Subject: [PATCH 32/74] chore(deps): update kotlin and compose compiler to v2 (major) * chore(deps): update kotlin and compose compiler to v2 * Update .gitignore * Fix build --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .github/renovate.json5 | 8 -- .gitignore | 1 + app/build.gradle.kts | 1 - build.gradle.kts | 1 - buildSrc/build.gradle.kts | 10 ++- ...hon.android.application.compose.gradle.kts | 1 - .../kotlin/mihon/buildlogic/AndroidConfig.kt | 4 + .../mihon/buildlogic/ProjectExtensions.kt | 75 +++++++++---------- gradle/compose.versions.toml | 3 - gradle/kotlinx.versions.toml | 7 +- presentation-core/build.gradle.kts | 12 --- presentation-widget/build.gradle.kts | 1 - 12 files changed, 49 insertions(+), 75 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 68076daf35..61ca4b433b 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -9,14 +9,6 @@ "dev.chrisbanes.compose:compose-bom" ], "ignoreUnstable": false - }, - { - "groupName": "Kotlin and Compose Compiler", - "matchPackagePrefixes": [ - "androidx.compose.compiler", - "org.jetbrains.kotlin.", - "org.jetbrains.kotlin:" - ], } ] } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7308c56468..0c5f5664ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .gradle +.kotlin /local.properties /acra.properties /.idea/workspace.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 96c4783cea..ebb3f365a0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -9,7 +9,6 @@ plugins { id("mihon.android.application") id("mihon.android.application.compose") id("com.mikepenz.aboutlibraries.plugin") - kotlin("plugin.compose") // TODO(kotlin2): Remove id("com.github.zellius.shortcut-helper") kotlin("plugin.serialization") } diff --git a/build.gradle.kts b/build.gradle.kts index 5fb8c52973..ea6a7b9c49 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,6 @@ buildscript { plugins { alias(kotlinx.plugins.serialization) apply false - alias(kotlinx.plugins.compose.compiler) apply false } tasks.register("clean") { diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 0ce5d68de6..b72b8a98b3 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,14 +3,16 @@ plugins { } dependencies { - implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) - implementation(files(androidx.javaClass.superclass.protectionDomain.codeSource.location)) - implementation(files(compose.javaClass.superclass.protectionDomain.codeSource.location)) - implementation(files(kotlinx.javaClass.superclass.protectionDomain.codeSource.location)) implementation(androidx.gradle) implementation(kotlinx.gradle) + implementation(kotlinx.compose.compiler.gradle) implementation(libs.detekt.gradlePlugin) implementation(gradleApi()) + + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(androidx.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(compose.javaClass.superclass.protectionDomain.codeSource.location)) + implementation(files(kotlinx.javaClass.superclass.protectionDomain.codeSource.location)) } repositories { diff --git a/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts b/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts index eda5fb22e3..6b330b1d99 100644 --- a/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts +++ b/buildSrc/src/main/kotlin/mihon.android.application.compose.gradle.kts @@ -1,4 +1,3 @@ -import mihon.buildlogic.AndroidConfig import mihon.buildlogic.configureCompose plugins { diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt index afea120de2..23f0d2b9af 100644 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt +++ b/buildSrc/src/main/kotlin/mihon/buildlogic/AndroidConfig.kt @@ -1,11 +1,15 @@ package mihon.buildlogic import org.gradle.api.JavaVersion as GradleJavaVersion +import org.jetbrains.kotlin.gradle.dsl.JvmTarget as KotlinJvmTarget object AndroidConfig { const val COMPILE_SDK = 34 const val TARGET_SDK = 34 const val MIN_SDK = 26 const val NDK = "26.1.10909125" + + // https://youtrack.jetbrains.com/issue/KT-66995/JvmTarget-and-JavaVersion-compatibility-for-easier-JVM-version-setup val JavaVersion = GradleJavaVersion.VERSION_17 + val JvmTarget = KotlinJvmTarget.JVM_17 } diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt index fcd8a67e1f..2f78b61a37 100644 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt +++ b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt @@ -8,9 +8,12 @@ import org.gradle.accessors.dm.LibrariesForLibs import org.gradle.api.Project import org.gradle.api.tasks.testing.Test import org.gradle.api.tasks.testing.logging.TestLogEvent +import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.the import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradlePluginExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile val Project.androidx get() = the() @@ -37,15 +40,18 @@ internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, } tasks.withType().configureEach { - kotlinOptions { - jvmTarget = AndroidConfig.JavaVersion.toString() - // freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" - // freeCompilerArgs += "-Xcontext-receivers" + compilerOptions { + jvmTarget.set(AndroidConfig.JvmTarget) + freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + "-Xcontext-receivers", + ) // Treat all Kotlin warnings as errors (disabled by default) // Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties - // val warningsAsErrors: String? by project - // allWarningsAsErrors = warningsAsErrors.toBoolean() + val warningsAsErrors: String? by project + allWarningsAsErrors.set(warningsAsErrors.toBoolean()) + } } @@ -55,52 +61,39 @@ internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, } internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, *, *, *>) { + pluginManager.apply(kotlinx.plugins.compose.compiler.get().pluginId) + commonExtension.apply { buildFeatures { compose = true } - composeOptions { - kotlinCompilerExtensionVersion = compose.versions.compiler.get() - } - dependencies { "implementation"(platform(compose.bom)) } } - tasks.withType().configureEach { - kotlinOptions { - freeCompilerArgs += buildComposeMetricsParameters() - - // Enable experimental compiler opts - // https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.5.9 - // TODO(kotlin2): add back in --> - // freeCompilerArgs += listOf( - // "-P", - // "plugin:androidx.compose.compiler.plugins.kotlin:nonSkippingGroupOptimization=true", - // ) - // <-- TODO(kotlin2) - } - } -} + extensions.configure { + // Enable strong skipping mode + enableStrongSkippingMode.set(true) + + // Enable experimental compiler opts + // https://developer.android.com/jetpack/androidx/releases/compose-compiler#1.5.9 + enableNonSkippingGroupOptimization.set(true) -private fun Project.buildComposeMetricsParameters(): List { - val rootProjectDir = rootProject.layout.buildDirectory.asFile.get() - val relativePath = projectDir.relativeTo(rootDir) - - val enableMetrics = project.providers.gradleProperty("enableComposeCompilerMetrics").orNull.toBoolean() - val enableReports = project.providers.gradleProperty("enableComposeCompilerReports").orNull.toBoolean() - - return listOfNotNull( - ("metricsDestination" to "compose-metrics").takeIf { enableMetrics }, - ("reportsDestination" to "compose-reports").takeIf { enableReports }, - ).flatMap { (flag, dirName) -> - val buildDirPath = rootProjectDir.resolve(dirName).resolve(relativePath).absolutePath - listOf( - "-P", - "plugin:androidx.compose.compiler.plugins.kotlin:$flag=$buildDirPath" - ) + val enableMetrics = project.providers.gradleProperty("enableComposeCompilerMetrics").orNull.toBoolean() + val enableReports = project.providers.gradleProperty("enableComposeCompilerReports").orNull.toBoolean() + + val rootProjectDir = rootProject.layout.buildDirectory.asFile.get() + val relativePath = projectDir.relativeTo(rootDir) + if (enableMetrics) { + val buildDirPath = rootProjectDir.resolve("compose-metrics").resolve(relativePath) + metricsDestination.set(buildDirPath) + } + if (enableReports) { + val buildDirPath = rootProjectDir.resolve("compose-reports").resolve(relativePath) + reportsDestination.set(buildDirPath) + } } } diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 4f01257f12..981bfab83d 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,11 +1,8 @@ [versions] -compiler = "1.5.14" compose-bom = "2024.05.00-alpha02" accompanist = "0.35.0-alpha" [libraries] -compiler = { module = "androidx.compose.compiler:compiler", version.ref = "compiler" } - activity = "androidx.activity:activity-compose:1.9.0" bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "compose-bom" } foundation = { module = "androidx.compose.foundation:foundation" } diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index d7b1f29804..4f4a6cec88 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,11 +1,12 @@ [versions] -kotlin_version = "2.0.20-Beta1" +kotlin_version = "2.0.0" serialization_version = "1.7.1" xml_serialization_version = "0.90.1" [libraries] reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin_version" } gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin_version" } +compose-compiler-gradle = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin_version" } immutables = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.3.7" } @@ -27,5 +28,5 @@ serialization = ["serialization-json", "serialization-json-okio", "serialization [plugins] android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin_version" } -serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin_version" } -compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin_version" } \ No newline at end of file +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin_version" } +serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin_version" } \ No newline at end of file diff --git a/presentation-core/build.gradle.kts b/presentation-core/build.gradle.kts index 0478394961..ea2710cc44 100644 --- a/presentation-core/build.gradle.kts +++ b/presentation-core/build.gradle.kts @@ -2,7 +2,6 @@ plugins { id("mihon.library") id("mihon.library.compose") kotlin("android") - kotlin("plugin.compose") // TODO(kotlin2): remove } android { @@ -12,16 +11,6 @@ android { testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") } - - // TODO(kotlin2): Remove --> - buildFeatures { - compose = true - } - - composeOptions { - kotlinCompilerExtensionVersion = compose.versions.compiler.get() - } - // <-- TODO(kotlin2) } dependencies { @@ -29,7 +18,6 @@ dependencies { api(projects.i18n) // Compose - implementation(platform(compose.bom)) // TODO(kotlin2): remove implementation(compose.activity) implementation(compose.foundation) implementation(compose.material3.core) diff --git a/presentation-widget/build.gradle.kts b/presentation-widget/build.gradle.kts index bddb24668f..292b67240b 100644 --- a/presentation-widget/build.gradle.kts +++ b/presentation-widget/build.gradle.kts @@ -2,7 +2,6 @@ plugins { id("mihon.library") id("mihon.library.compose") kotlin("android") - kotlin("plugin.compose") // TODO(kotlin2): remove } android { From d1cf93bed1703ca0caee7d5b3b112789724089e8 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 19:19:21 +0200 Subject: [PATCH 33/74] =?UTF-8?q?fix(deps):=20update=20dependency=20dev.ch?= =?UTF-8?q?risbanes.compose:compose-bom=20to=20v2=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …024.05.00-alpha03 * fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2024.05.00-alpha03 * Fix build --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../kanade/presentation/browse/anime/AnimeExtensionsScreen.kt | 2 +- .../kanade/presentation/browse/manga/MangaExtensionsScreen.kt | 2 +- app/src/main/java/eu/kanade/presentation/components/AppBar.kt | 2 +- .../java/eu/kanade/presentation/entries/anime/AnimeScreen.kt | 4 ++-- .../java/eu/kanade/presentation/entries/manga/MangaScreen.kt | 4 ++-- .../kanade/presentation/library/anime/AnimeLibraryContent.kt | 2 +- .../kanade/presentation/library/manga/MangaLibraryContent.kt | 2 +- .../kanade/presentation/updates/anime/AnimeUpdatesScreen.kt | 2 +- .../kanade/presentation/updates/manga/MangaUpdatesScreen.kt | 2 +- gradle/compose.versions.toml | 4 ++-- .../presentation/core/components/material/PullRefresh.kt | 4 ++-- 11 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt index 8acb39b970..bc0ae578dd 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/anime/AnimeExtensionsScreen.kt @@ -88,7 +88,7 @@ fun AnimeExtensionScreen( PullRefresh( refreshing = state.isRefreshing, onRefresh = onRefresh, - enabled = { !state.isLoading }, + enabled = !state.isLoading, ) { when { state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) diff --git a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt index d84c287244..f3862c14c6 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/manga/MangaExtensionsScreen.kt @@ -90,7 +90,7 @@ fun MangaExtensionScreen( PullRefresh( refreshing = state.isRefreshing, onRefresh = onRefresh, - enabled = { !state.isLoading }, + enabled = !state.isLoading, ) { when { state.isLoading -> LoadingScreen(Modifier.padding(contentPadding)) diff --git a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt index 54c0aaf866..071711e1d1 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -202,7 +202,7 @@ fun AppBarTitle( maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.basicMarquee( - delayMillis = 2_000, + repeatDelayMillis = 2_000, ), ) } diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt index 55b22ff105..25cc09595d 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/AnimeScreen.kt @@ -402,7 +402,7 @@ private fun AnimeScreenSmallImpl( PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, - enabled = { !isAnySelected }, + enabled = !isAnySelected, indicatorPadding = PaddingValues(top = topPadding), ) { val layoutDirection = LocalLayoutDirection.current @@ -687,7 +687,7 @@ fun AnimeScreenLargeImpl( PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, - enabled = { !isAnySelected }, + enabled = !isAnySelected, indicatorPadding = PaddingValues( start = insetPadding.calculateStartPadding(layoutDirection), top = with(density) { topBarHeight.toDp() }, diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt index d957a501ab..ce0041890c 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/MangaScreen.kt @@ -372,7 +372,7 @@ private fun MangaScreenSmallImpl( PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, - enabled = { !isAnySelected }, + enabled = !isAnySelected, indicatorPadding = PaddingValues(top = topPadding), ) { val layoutDirection = LocalLayoutDirection.current @@ -623,7 +623,7 @@ fun MangaScreenLargeImpl( PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, - enabled = { !isAnySelected }, + enabled = !isAnySelected, indicatorPadding = PaddingValues( start = insetPadding.calculateStartPadding(layoutDirection), top = with(density) { topBarHeight.toDp() }, diff --git a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt index a0762420e1..035c3ccfdc 100644 --- a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibraryContent.kt @@ -94,7 +94,7 @@ fun AnimeLibraryContent( isRefreshing = false } }, - enabled = { notSelectionMode }, + enabled = notSelectionMode, ) { AnimeLibraryPager( state = pagerState, diff --git a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt index 9fb0d7c3d9..1ddb4a26d4 100644 --- a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt +++ b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibraryContent.kt @@ -94,7 +94,7 @@ fun MangaLibraryContent( isRefreshing = false } }, - enabled = { notSelectionMode }, + enabled = notSelectionMode, ) { MangaLibraryPager( state = pagerState, diff --git a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt index 91ceed2f30..36428dfc5d 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/anime/AnimeUpdatesScreen.kt @@ -86,7 +86,7 @@ fun AnimeUpdateScreen( isRefreshing = false } }, - enabled = { !state.selectionMode }, + enabled = !state.selectionMode, indicatorPadding = contentPadding, ) { FastScrollLazyColumn( diff --git a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt index 3f5d5ce030..818f38bb91 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/manga/MangaUpdatesScreen.kt @@ -82,7 +82,7 @@ fun MangaUpdateScreen( isRefreshing = false } }, - enabled = { !state.selectionMode }, + enabled = !state.selectionMode, indicatorPadding = contentPadding, ) { FastScrollLazyColumn( diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 981bfab83d..b5e306abe1 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,6 +1,6 @@ [versions] -compose-bom = "2024.05.00-alpha02" -accompanist = "0.35.0-alpha" +compose-bom = "2024.05.00-alpha03" +accompanist = "0.35.1-alpha" [libraries] activity = "androidx.activity:activity-compose:1.9.0" diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt index b1987185b0..08b10cdaaa 100644 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt +++ b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/PullRefresh.kt @@ -22,7 +22,7 @@ import androidx.compose.ui.unit.dp @Composable fun PullRefresh( refreshing: Boolean, - enabled: () -> Boolean, + enabled: Boolean, onRefresh: () -> Unit, modifier: Modifier = Modifier, indicatorPadding: PaddingValues = PaddingValues(0.dp), @@ -36,7 +36,7 @@ fun PullRefresh( state = state, enabled = enabled, onRefresh = onRefresh, - ) + ), ) { content() From 162471c7fbd12969ca1e43a46c949985de70bf62 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 19:21:03 +0200 Subject: [PATCH 34/74] fix(deps): update okhttp monorepo to v5.0.0-alpha.14 * fix(deps): update okhttp monorepo to v5.0.0-alpha.14 * Fix build --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt | 3 ++- .../java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt index 588378ec73..12c6b1e6d4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/AnimeCoverFetcher.kt @@ -34,7 +34,6 @@ import tachiyomi.domain.source.anime.service.AnimeSourceManager import uy.kohesive.injekt.injectLazy import java.io.File import java.io.IOException -import java.net.HttpURLConnection.HTTP_NOT_MODIFIED /** * A [Fetcher] that fetches cover image for [Anime] object. @@ -344,5 +343,7 @@ class AnimeCoverFetcher( private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build() private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build() + + private const val HTTP_NOT_MODIFIED = 304 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt index a05540c125..e959c3e6c1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt @@ -34,7 +34,6 @@ import tachiyomi.domain.source.manga.service.MangaSourceManager import uy.kohesive.injekt.injectLazy import java.io.File import java.io.IOException -import java.net.HttpURLConnection.HTTP_NOT_MODIFIED /** * A [Fetcher] that fetches cover image for [Manga] object. @@ -344,5 +343,7 @@ class MangaCoverFetcher( private val CACHE_CONTROL_NO_STORE = CacheControl.Builder().noStore().build() private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build() + + private const val HTTP_NOT_MODIFIED = 304 } } From 3e2498828d2f03945f4889988bd3cef619e02f53 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 19:24:54 +0200 Subject: [PATCH 35/74] MangaChapterListItem: Don't use alpha modifier Possibly fixes #822 Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../anime/components/AnimeEpisodeListItem.kt | 14 ++++++++------ .../manga/components/MangaChapterListItem.kt | 15 ++++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt index 7afedad0b0..14393baa31 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeEpisodeListItem.kt @@ -69,9 +69,6 @@ fun AnimeEpisodeListItem( onEpisodeSwipe: (LibraryPreferences.EpisodeSwipeAction) -> Unit, modifier: Modifier = Modifier, ) { - val textAlpha = if (seen) ReadItemAlpha else 1f - val textSubtitleAlpha = if (seen) ReadItemAlpha else SecondaryItemAlpha - val start = getSwipeAction( action = episodeSwipeStartAction, seen = seen, @@ -136,15 +133,20 @@ fun AnimeEpisodeListItem( Text( text = title, style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current.copy(alpha = textAlpha), maxLines = 1, overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, + color = LocalContentColor.current.copy(alpha = if (seen) ReadItemAlpha else 1f), ) } - Row(modifier = Modifier.alpha(textSubtitleAlpha)) { - ProvideTextStyle(value = MaterialTheme.typography.bodySmall) { + Row { + val subtitleStyle = MaterialTheme.typography.bodySmall + .merge( + color = LocalContentColor.current + .copy(alpha = if (seen) ReadItemAlpha else SecondaryItemAlpha) + ) + ProvideTextStyle(value = subtitleStyle) { if (date != null) { Text( text = date, diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt index 3b66d35721..79a1da0705 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaChapterListItem.kt @@ -30,7 +30,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector @@ -67,9 +66,6 @@ fun MangaChapterListItem( onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit, modifier: Modifier = Modifier, ) { - val textAlpha = if (read) ReadItemAlpha else 1f - val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha - val start = getSwipeAction( action = chapterSwipeStartAction, read = read, @@ -135,15 +131,20 @@ fun MangaChapterListItem( Text( text = title, style = MaterialTheme.typography.bodyMedium, - color = LocalContentColor.current.copy(alpha = textAlpha), maxLines = 1, overflow = TextOverflow.Ellipsis, onTextLayout = { textHeight = it.size.height }, + color = LocalContentColor.current.copy(alpha = if (read) ReadItemAlpha else 1f), ) } - Row(modifier = Modifier.alpha(textSubtitleAlpha)) { - ProvideTextStyle(value = MaterialTheme.typography.bodySmall) { + Row { + val subtitleStyle = MaterialTheme.typography.bodySmall + .merge( + color = LocalContentColor.current + .copy(alpha = if (read) ReadItemAlpha else SecondaryItemAlpha) + ) + ProvideTextStyle(value = subtitleStyle) { if (date != null) { Text( text = date, From f01773e15fd8fa823a3912bdf4061bfead895dd3 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Fri, 7 Jun 2024 15:30:00 +0200 Subject: [PATCH 36/74] Translations update from Hosted Weblate (#611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Malayalam) Currently translated at 12.9% (104 of 803 strings) Translated using Weblate (Malayalam) Currently translated at 94.4% (17 of 18 strings) Translated using Weblate (Malayalam) Currently translated at 11.8% (95 of 803 strings) Added translation using Weblate (Malayalam) Added translation using Weblate (Malayalam) Co-authored-by: Akhil Raj Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ml/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals * Translated using Weblate (Italian) Currently translated at 99.6% (800 of 803 strings) Co-authored-by: Federico Pierantoni Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/ Translation: Mihon/Mihon * Translated using Weblate (Hungarian) Currently translated at 100.0% (803 of 803 strings) Translated using Weblate (Hungarian) Currently translated at 100.0% (803 of 803 strings) Co-authored-by: B4LiN7 Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hu/ Translation: Mihon/Mihon * Translated using Weblate (Javanese) Currently translated at 38.7% (311 of 803 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (803 of 803 strings) Translated using Weblate (Indonesian) Currently translated at 98.7% (793 of 803 strings) Co-authored-by: TheKingTermux Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ja/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/jv/ Translation: Mihon/Mihon * Translated using Weblate (Greek) Currently translated at 100.0% (803 of 803 strings) Co-authored-by: Pitpe11 Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/el/ Translation: Mihon/Mihon * Translated using Weblate (Serbian) Currently translated at 99.2% (797 of 803 strings) Co-authored-by: Rikishaaa Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sr/ Translation: Mihon/Mihon * Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (803 of 803 strings) Co-authored-by: Blackiezin Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/ Translation: Mihon/Mihon * Translated using Weblate (French) Currently translated at 100.0% (18 of 18 strings) Translated using Weblate (French) Currently translated at 99.0% (795 of 803 strings) Co-authored-by: LaQuiche426 Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fr/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals * Translated using Weblate (Portuguese) Currently translated at 99.8% (802 of 803 strings) Co-authored-by: ssantos Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt/ Translation: Mihon/Mihon * Translated using Weblate (Vietnamese) Currently translated at 100.0% (18 of 18 strings) Translated using Weblate (Vietnamese) Currently translated at 96.8% (778 of 803 strings) Co-authored-by: Karuto Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/vi/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals * Translated using Weblate (Croatian) Currently translated at 99.5% (799 of 803 strings) Co-authored-by: Milo Ivir Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/ Translation: Mihon/Mihon * Translated using Weblate (Indonesian) Currently translated at 100.0% (803 of 803 strings) Co-authored-by: Eji-san Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/id/ Translation: Mihon/Mihon * Translated using Weblate (Galician) Currently translated at 100.0% (803 of 803 strings) Co-authored-by: kevans Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/gl/ Translation: Mihon/Mihon * Translated using Weblate (Ukrainian) Currently translated at 99.8% (802 of 803 strings) Co-authored-by: Kodekiro Kodekihara Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/uk/ Translation: Mihon/Mihon * Translated using Weblate (Malay) Currently translated at 98.6% (792 of 803 strings) Co-authored-by: Farith Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ms/ Translation: Mihon/Mihon * Translated using Weblate (Nepali) Currently translated at 100.0% (18 of 18 strings) Translated using Weblate (Nepali) Currently translated at 100.0% (803 of 803 strings) Co-authored-by: FateXBlood Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ne/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals * Translated using Weblate (Vietnamese) Currently translated at 100.0% (803 of 803 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/vi/ * Translated using Weblate (Croatian) Currently translated at 100.0% (803 of 803 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/ * Translated using Weblate (Spanish) Currently translated at 100.0% (18 of 18 strings) Translation: Mihon/Mihon Plurals Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/es/ * Translated using Weblate (Romanian) Currently translated at 99.6% (800 of 803 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ro/ * Translated using Weblate (Romanian) Currently translated at 100.0% (18 of 18 strings) Translation: Mihon/Mihon Plurals Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ro/ * Translated using Weblate (Italian) Currently translated at 100.0% (803 of 803 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/ * Translated using Weblate (Polish) Currently translated at 99.5% (799 of 803 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pl/ * Translated using Weblate (Spanish) Currently translated at 100.0% (803 of 803 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/ * Translated using Weblate (German) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/de/ * Translated using Weblate (Russian) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ru/ * Translated using Weblate (French) Currently translated at 99.5% (800 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/ * Translated using Weblate (Filipino) Currently translated at 99.8% (803 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/ * Translated using Weblate (Nepali) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ne/ * Translated using Weblate (Catalan) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/ * Translated using Weblate (Spanish) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/es/ * Translated using Weblate (Catalan) Currently translated at 100.0% (18 of 18 strings) Translation: Mihon/Mihon Plurals Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ca/ --------- Co-authored-by: Akhil Raj Co-authored-by: Federico Pierantoni Co-authored-by: B4LiN7 Co-authored-by: TheKingTermux Co-authored-by: Pitpe11 Co-authored-by: Rikishaaa Co-authored-by: Blackiezin Co-authored-by: LaQuiche426 Co-authored-by: ssantos Co-authored-by: Karuto Co-authored-by: Milo Ivir Co-authored-by: Eji-san Co-authored-by: kevans Co-authored-by: Kodekiro Kodekihara Co-authored-by: Farith Co-authored-by: FateXBlood Co-authored-by: Nguyễn Trung Đức Co-authored-by: Chrono Lux Co-authored-by: Saft Octavian Co-authored-by: Giorgio Sanna Co-authored-by: sebastians17 Co-authored-by: Tim Schneeberger Co-authored-by: Dexroneum Co-authored-by: Naga Co-authored-by: Infy's Tagalog Translations Co-authored-by: Eduard Ereza Martínez Co-authored-by: gallegonovato (cherry picked from commit 87fe64468ca08466af5b9fcc7f9e17e9a23021e6) --- .../commonMain/moko-resources/es/plurals.xml | 12 +-- .../commonMain/moko-resources/ne/plurals.xml | 4 + .../commonMain/moko-resources/ro/plurals.xml | 5 + .../commonMain/moko-resources/vi/plurals.xml | 6 ++ .../commonMain/resources/MR/ml/plurals.xml | 71 ++++++++++++ .../commonMain/resources/MR/ml/strings.xml | 102 ++++++++++++++++++ 6 files changed, 194 insertions(+), 6 deletions(-) create mode 100644 i18n/src/commonMain/resources/MR/ml/plurals.xml create mode 100644 i18n/src/commonMain/resources/MR/ml/strings.xml diff --git a/i18n/src/commonMain/moko-resources/es/plurals.xml b/i18n/src/commonMain/moko-resources/es/plurals.xml index 63ddcf016c..fae5acc2dc 100644 --- a/i18n/src/commonMain/moko-resources/es/plurals.xml +++ b/i18n/src/commonMain/moko-resources/es/plurals.xml @@ -1,7 +1,7 @@ - Tras %1$s minuto + Tras %1$s minutos Tras %1$s minutos Tras %1$s minutos @@ -26,9 +26,9 @@ %d actualizaciones de extensiones disponibles - Queda %1$s - Quedan %1$s - Quedan %1$s + Restante %1$s + Restantes %1$s + Restantes %1$s %d categoría @@ -61,8 +61,8 @@ Hace %1$d días - El siguiente capítulo sin leer - Los siguientes %d capítulos sin leer + Siguiente capítulo sin leer + Siguientes %d capítulos sin leer Los siguientes %d capítulos sin leer diff --git a/i18n/src/commonMain/moko-resources/ne/plurals.xml b/i18n/src/commonMain/moko-resources/ne/plurals.xml index ae7dd83211..62d519d66a 100644 --- a/i18n/src/commonMain/moko-resources/ne/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ne/plurals.xml @@ -88,4 +88,8 @@ + + भोलि + %1$d दिनमा + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/ro/plurals.xml b/i18n/src/commonMain/moko-resources/ro/plurals.xml index 9364e11376..5e8e6b3d77 100644 --- a/i18n/src/commonMain/moko-resources/ro/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ro/plurals.xml @@ -110,4 +110,9 @@ + + Mâine + În %1$d zile + În %1$d de zile + \ No newline at end of file diff --git a/i18n/src/commonMain/moko-resources/vi/plurals.xml b/i18n/src/commonMain/moko-resources/vi/plurals.xml index 9280140a78..c3358998d7 100644 --- a/i18n/src/commonMain/moko-resources/vi/plurals.xml +++ b/i18n/src/commonMain/moko-resources/vi/plurals.xml @@ -45,4 +45,10 @@ %d ngày + + %d kho + + + Trong %1$d ngày + \ No newline at end of file diff --git a/i18n/src/commonMain/resources/MR/ml/plurals.xml b/i18n/src/commonMain/resources/MR/ml/plurals.xml new file mode 100644 index 0000000000..a3e67ba1c0 --- /dev/null +++ b/i18n/src/commonMain/resources/MR/ml/plurals.xml @@ -0,0 +1,71 @@ + + + + ഇന്നലെ + %1$d ദിവസം മുമ്പ് + + + നാളെ + %1$d ദിവസത്തിനുള്ളിൽ + + + %2$s പിശകോടെ %1$s-നുള്ളിൽ ചെയ്തു + %2$s പിശകുകളോടെ %1$s-നുള്ളിൽ ചെയ്തു + + + %d അധ്യായം ഒഴിവാക്കുന്നു, ഒന്നുകിൽ ഉറവിടം അത് കാണുന്നില്ല അല്ലെങ്കിൽ അത് ഫിൽട്ടർ ചെയ്തിരിക്കുന്നു + %d അധ്യായങ്ങൾ ഒഴിവാക്കുന്നു, ഒന്നുകിൽ ഉറവിടം കാണുന്നില്ല അല്ലെങ്കിൽ അവ ഫിൽട്ടർ ചെയ്തിരിക്കുന്നു + + + വിപുലീകരണ അപ്ഡേറ്റ് ലഭ്യമാണ് + %d വിപുലീകരണ അപ്‌ഡേറ്റുകൾ ലഭ്യമാണ് + + + അടുത്ത അധ്യായം + അടുത്ത %d അധ്യായങ്ങൾ + + + %1$s മിനിറ്റിന് ശേഷം + %1$s മിനിറ്റിന് ശേഷം + + + %d വിഭാഗം + %d വിഭാഗങ്ങൾ + + + അടുത്ത വായിക്കാത്ത അധ്യായം + അടുത്ത %d വായിക്കാത്ത അധ്യായങ്ങൾ + + + %1$s ശേഷിക്കുന്നു + %1$s ശേഷിക്കുന്നു + + + 1 ദിവസം + %d ദിവസം + + + %1$s അധ്യായം കാണുന്നില്ല + %1$s അധ്യായങ്ങൾ കാണുന്നില്ല + + + %1$s അധ്യായം + %1$s അധ്യായങ്ങൾ + + + %d ട്രാക്കർ + %d ട്രാക്കറുകൾ + + + %1$d പുതിയ അധ്യായം + %1$d പുതിയ അധ്യായങ്ങൾ + + + അധ്യായങ്ങൾ %1$s-ഉം 1-ഉം + %1$s-ഉം %2$d-ഉം അധ്യായങ്ങൾ + + + %d റിപ്പോ + %d റിപ്പോകൾ + + \ No newline at end of file diff --git a/i18n/src/commonMain/resources/MR/ml/strings.xml b/i18n/src/commonMain/resources/MR/ml/strings.xml new file mode 100644 index 0000000000..7a6be0bfb5 --- /dev/null +++ b/i18n/src/commonMain/resources/MR/ml/strings.xml @@ -0,0 +1,102 @@ + + + ഡാറ്റാ ആന്റ് സ്റ്റോറേജ് + ചരിത്രം + തിരയുക + ആഗോള തിരയൽ + ബുക്ക്മാർക്ക് അധ്യായം + എല്ലാം പ്രവർത്തനക്ഷമമാക്കുക + വായിച്ചിട്ടില്ലെന്ന് അടയാളപ്പെടുത്തുക + എല്ലാം പ്രവർത്തനരഹിതമാക്കുക + തിരുത്തുക + ചേർക്കുക + വിഭാഗം അപ്ഡേറ്റ് ചെയ്യുക + ക്രമരഹിതമായ എൻട്രി തുറക്കുക + വിഭാഗം ചേർക്കുക + വിഭാഗങ്ങൾ അക്ഷരമാലാക്രമത്തിൽ അടുക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ? + കവർ എഡിറ്റ് ചെയ്യുക + അധ്യായങ്ങൾ കാണുക + വിരാമം + വീണ്ടും ശ്രമിക്കുക + മുൻ അധ്യായം + അടുത്ത അധ്യായം + ഏറ്റവും പുതിയ അധ്യായം + ഓഫ് + തിരഞ്ഞെടുക്കപ്പെട്ട + മുകളിലേക്ക് നയിക്കുക + പേര് + വിഭാഗങ്ങൾ + അദ്ധ്യായങ്ങൾ + ട്രാക്കിംഗ് + ഡൗൺലോഡ് ചെയ്‌തത് ഇല്ലാതാക്കുക + ഇനിയും + ഡൗൺലോഡ് ക്യൂ + ലൈബ്രറി + അപ്ഡേറ്റുകൾ + അടുത്തതായി + ചരിത്രം + ഉൽഭവം + ബാക്കപ്പും റിസ്റ്റോറും + ലൈബ്രറി എൻട്രികൾ + സ്ഥിതിവിവരക്കണക്കുകൾ + മൈഗ്രേറ്റ് ചെയ്യുക + വിപുലീകരണങ്ങൾ + വിപുലീകരണ വിവരം + സഹായം + സ്ഥിരസ്ഥിതി + മുന്നറിയിപ്പ് + ആരംഭിച്ചു + പ്രാദേശികമായ + ഡൗൺലോഡ് ചെയ്തു + %s അൺലോക്ക് ചെയ്യുക + മാറ്റം സ്ഥിരീകരിക്കാൻ പ്രാമാണീകരിക്കുക + ക്രമീകരണങ്ങൾ + പട്ടിക + ഫിൽട്ടർ ചെയ്യുക + ഇടവേള സജ്ജമാക്കുക + ബുക്ക്‌മാർക്ക് ചെയ്തു + ട്രാക്ക് ചെയ്തു + വായിക്കാത്തത് + ഇഷ്‌ടാനുസൃതമാക്കിയ അപ്‌ഡേറ്റ് ആവൃത്തി + ഫിൽട്ടർ നീക്കം ചെയ്യുക + അക്ഷരമാലാക്രമത്തിൽ + ആകെ എൻട്രികൾ + ആകെ അധ്യായങ്ങൾ + അവസാനം വായിച്ചത് + അവസാന അപ്ഡേറ്റ് പരിശോധന + വായിക്കാത്ത എണ്ണം + അടുത്തതായി പ്രതീക്ഷിക്കുന്ന അപ്ഡേറ്റ് + അധ്യായം ലഭിച്ച തീയതി + തീയതി ചേർത്തു + ട്രാക്കർ സ്കോർ + തിരയുക… + തിരയൽ ക്രമീകരണങ്ങൾ + എല്ലാം തിരഞ്ഞെടുക്കുക + വിപരീതം തിരഞ്ഞെടുക്കുക + വായിച്ചതായി അടയാളപ്പെടുത്തുക + മുമ്പത്തേത് വായിച്ചതായി അടയാളപ്പെടുത്തുക + ഡൗൺലോഡ് + അൺബുക്ക്മാർക്ക് അധ്യായം + ഇല്ലാതാക്കുക + ലൈബ്രറി അപ്ഡേറ്റ് ചെയ്യുക + വിഭാഗങ്ങൾ എഡിറ്റ് ചെയ്യുക + വിഭാഗത്തിൻ്റെ പേരുമാറ്റുക + വിഭാഗങ്ങൾ സജ്ജമാക്കുക + \"%s\" എന്ന വിഭാഗം ഇല്ലാതാക്കാൻ നിങ്ങൾ ആഗ്രഹിക്കുന്നുണ്ടോ? + വിഭാഗം ഇല്ലാതാക്കുക + വിഭാഗങ്ങൾ അടുക്കുക + ഓൺ + കൂടുതൽ ഓപ്ഷനുകൾ + തിരഞ്ഞെടുത്തില്ല + സ്കാൻലേറ്റർ + ക്രമീകരണങ്ങൾ + നീക്കം ചെയ്യുക + എല്ലാം നീക്കം ചെയ്യുക + ആരംഭിക്കുക + പുനരാരംഭിക്കുക + ബ്രൗസറിൽ തുറക്കുക + ക്ലിപ്പ്ബോർഡിലേയ്ക്ക് പകർത്തുക + വെബ്‌വ്യൂവിൽ തുറക്കുക + മൈഗ്രേറ്റ് ചെയ്യുക + ഡിസ്പ്ലേ മോഡ് + \ No newline at end of file From 013b1ee762bc3aaaac180293bc4224e554124adc Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 20:07:29 +0200 Subject: [PATCH 37/74] Check category order before restoring from backup Closes #632 Co-authored-by: Cologler <10906962+Cologler@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../restore/restorers/CategoriesRestorer.kt | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt index 88f3122099..b889f63971 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/CategoriesRestorer.kt @@ -21,14 +21,20 @@ class CategoriesRestorer( if (backupCategories.isNotEmpty()) { val dbCategories = getAnimeCategories.await() val dbCategoriesByName = dbCategories.associateBy { it.name } + var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0 - val categories = backupCategories.map { - dbCategoriesByName[it.name] - ?: animeHandler.awaitOneExecutable { - categoriesQueries.insert(it.name, it.order, it.flags) + val categories = backupCategories + .sortedBy { it.order } + .map { + val dbCategory = dbCategoriesByName[it.name] + if (dbCategory != null) return@map dbCategory + val order = nextOrder++ + animeHandler.awaitOneExecutable { + categoriesQueries.insert(it.name, order, it.flags) categoriesQueries.selectLastInsertedRowId() - }.let { id -> it.toCategory(id) } - } + } + .let { id -> it.toCategory(id).copy(order = order) } + } libraryPreferences.categorizedDisplaySettings().set( (dbCategories + categories) @@ -42,14 +48,20 @@ class CategoriesRestorer( if (backupCategories.isNotEmpty()) { val dbCategories = getMangaCategories.await() val dbCategoriesByName = dbCategories.associateBy { it.name } + var nextOrder = dbCategories.maxOfOrNull { it.order }?.plus(1) ?: 0 - val categories = backupCategories.map { - dbCategoriesByName[it.name] - ?: mangaHandler.awaitOneExecutable { - categoriesQueries.insert(it.name, it.order, it.flags) + val categories = backupCategories + .sortedBy { it.order } + .map { + val dbCategory = dbCategoriesByName[it.name] + if (dbCategory != null) return@map dbCategory + val order = nextOrder++ + mangaHandler.awaitOneExecutable { + categoriesQueries.insert(it.name, order, it.flags) categoriesQueries.selectLastInsertedRowId() - }.let { id -> it.toCategory(id) } - } + } + .let { id -> it.toCategory(id).copy(order = order) } + } libraryPreferences.categorizedDisplaySettings().set( (dbCategories + categories) From f24b90e738b3f04f41c5d778688eb46fd2504b0a Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 20:49:25 +0200 Subject: [PATCH 38/74] Fix chapter number parsing when number is after unwanted tag Fixes #554 Co-authored-by: Naputt1 <94742489+Naputt1@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../chapter/service/ChapterRecognition.kt | 48 ++++++++++-------- .../episode/service/EpisodeRecognition.kt | 49 +++++++++++-------- .../chapter/service/ChapterRecognitionTest.kt | 11 +++++ 3 files changed, 67 insertions(+), 41 deletions(-) diff --git a/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterRecognition.kt b/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterRecognition.kt index 4ba2cdb81a..6d6a2c1523 100644 --- a/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterRecognition.kt +++ b/domain/src/main/java/tachiyomi/domain/items/chapter/service/ChapterRecognition.kt @@ -37,27 +37,35 @@ object ChapterRecognition { } // Get chapter title with lower case - var name = chapterName.lowercase() - - // Remove manga title from chapter title. - name = name.replace(mangaTitle.lowercase(), "").trim() - - // Remove comma's or hyphens. - name = name.replace(',', '.').replace('-', '.') - - // Remove unwanted white spaces. - name = unwantedWhiteSpace.replace(name, "") - - // Remove unwanted tags. - name = unwanted.replace(name, "") - - // Check base case ch.xx - basic.find(name)?.let { return getChapterNumberFromMatch(it) } - - // Take the first number encountered. - number.find(name)?.let { return getChapterNumberFromMatch(it) } + val cleanChapterName = chapterName.lowercase() + // Remove manga title from chapter title. + .replace(mangaTitle.lowercase(), "").trim() + // Remove comma's or hyphens. + .replace(',', '.') + .replace('-', '.') + // Remove unwanted white spaces. + .replace(unwantedWhiteSpace, "") + + val numberMatch = number.findAll(cleanChapterName) + + when { + numberMatch.none() -> { + return chapterNumber ?: -1.0 + } + numberMatch.count() > 1 -> { + // Remove unwanted tags. + unwanted.replace(cleanChapterName, "").let { name -> + // Check base case ch.xx + basic.find(name)?.let { return getChapterNumberFromMatch(it) } + + // need to find again first number might already removed + number.find(name)?.let { return getChapterNumberFromMatch(it) } + } + } + } - return chapterNumber ?: -1.0 + // return the first number encountered + return getChapterNumberFromMatch(numberMatch.first()) } /** diff --git a/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt b/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt index 16c94aafa5..d3cfd9b377 100644 --- a/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt +++ b/domain/src/main/java/tachiyomi/domain/items/episode/service/EpisodeRecognition.kt @@ -36,28 +36,35 @@ object EpisodeRecognition { return episodeNumber } - // Get chapter title with lower case - var name = episodeName.lowercase() - - // Remove anime title from episode title. - name = name.replace(animeTitle.lowercase(), "").trim() - - // Remove comma's or hyphens. - name = name.replace(',', '.').replace('-', '.') - - // Remove unwanted white spaces. - name = unwantedWhiteSpace.replace(name, "") - - // Remove unwanted tags. - name = unwanted.replace(name, "") - - // Check base case ch.xx - basic.find(name)?.let { return getEpisodeNumberFromMatch(it) } - - // Take the first number encountered. - number.find(name)?.let { return getEpisodeNumberFromMatch(it) } + // Get episode title with lower case + val cleanEpisodeName = episodeName.lowercase() + // Remove anime title from episode title. + .replace(animeTitle.lowercase(), "").trim() + // Remove comma's or hyphens. + .replace(',', '.') + .replace('-', '.') + // Remove unwanted white spaces. + .replace(unwantedWhiteSpace, "") + + val numberMatch = number.findAll(cleanEpisodeName) + + when { + numberMatch.none() -> { + return episodeNumber ?: -1.0 + } + numberMatch.count() > 1 -> { + // Remove unwanted tags. + unwanted.replace(cleanEpisodeName, "").let { name -> + // Check base case ep.xx + basic.find(name)?.let { return getEpisodeNumberFromMatch(it) } + + // need to find again first number might already removed + number.find(name)?.let { return getEpisodeNumberFromMatch(it) } + } + } + } - return episodeNumber ?: -1.0 + return getEpisodeNumberFromMatch(numberMatch.first()) } /** diff --git a/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt b/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt index 4c087fed46..08bd8bc30b 100644 --- a/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt +++ b/domain/src/test/java/tachiyomi/domain/items/chapter/service/ChapterRecognitionTest.kt @@ -171,6 +171,17 @@ class ChapterRecognitionTest { assertChapter(mangaTitle, "Tokyo ESP 027: Part 002: Chapter 001", 027.0) } + /** + * Case where the chapter title contains the unwanted tag + * But follow by chapter number. + */ + @Test + fun `Number after unwanted tag`() { + val mangaTitle = "One-punch Man" + + assertChapter(mangaTitle, "Mag Version 195.5", 195.5) + } + @Test fun `Unparseable chapter`() { val mangaTitle = "random" From f8da1092d598dd11712a008e8e155e4a048d5431 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 02:31:28 +0600 Subject: [PATCH 39/74] chore(deps): update actions/checkout action to v4.1.7 (#891) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 6d8cfd5f30753025434b203840a8a1b49b94ed95) --- .github/workflows/build_pull_request.yml | 2 +- .github/workflows/build_push.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index c4faa0ca88..8c75adae22 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index c79489c72b..518693699f 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Clone repo - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Validate Gradle Wrapper uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 From 5c3d5cdc182d69876cf00f2f86f1b670e8c7b8f8 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 20:53:33 +0200 Subject: [PATCH 40/74] Fix issue with creating and restoring backup Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt | 3 +-- .../eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt | 3 +-- .../java/eu/kanade/tachiyomi/data/backup/models/Backup.kt | 4 ---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt index e33572caf9..34f862548f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupDecoder.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup import android.content.Context import android.net.Uri import eu.kanade.tachiyomi.data.backup.models.Backup -import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import kotlinx.serialization.protobuf.ProtoBuf import okio.buffer import okio.gzip @@ -33,7 +32,7 @@ class BackupDecoder( source }.use { it.readByteArray() } - parser.decodeFromByteArray(BackupSerializer, backupString) + parser.decodeFromByteArray(Backup.serializer(), backupString) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt index a90ecde496..b4c20c06d1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/BackupCreator.kt @@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.data.backup.models.BackupCategory import eu.kanade.tachiyomi.data.backup.models.BackupExtension import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupPreference -import eu.kanade.tachiyomi.data.backup.models.BackupSerializer import eu.kanade.tachiyomi.data.backup.models.BackupSource import eu.kanade.tachiyomi.data.backup.models.BackupSourcePreferences import kotlinx.serialization.protobuf.ProtoBuf @@ -99,7 +98,7 @@ class BackupCreator( backupExtensions = backupExtensions(options), ) - val byteArray = parser.encodeToByteArray(BackupSerializer, backup) + val byteArray = parser.encodeToByteArray(Backup.serializer(), backup) if (byteArray.isEmpty()) { throw IllegalStateException(context.stringResource(MR.strings.empty_backup_error)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt index 9319854433..67ea342fa0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/Backup.kt @@ -1,12 +1,8 @@ package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable -import kotlinx.serialization.Serializer import kotlinx.serialization.protobuf.ProtoNumber -@Serializer(forClass = Backup::class) -object BackupSerializer - @Serializable data class Backup( @ProtoNumber(1) val backupManga: List = emptyList(), From 406cec32f7e551065411dbbf6cb1ac78899965f5 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 20:59:56 +0200 Subject: [PATCH 41/74] Remove unused file --- .../tachiyomi/data/backup/full/models/BackupSerializer.kt | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt deleted file mode 100644 index 55b1c6afc6..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupSerializer.kt +++ /dev/null @@ -1,6 +0,0 @@ -package eu.kanade.tachiyomi.data.backup.full.models - -import kotlinx.serialization.Serializer - -@Serializer(forClass = Backup::class) -object BackupSerializer From ddbf1878381f7ef325d16ff9830ea8d741041a60 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Wed, 12 Jun 2024 23:34:04 +0200 Subject: [PATCH 42/74] Translations update from Hosted Weblate (#878) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/zh_Hant/ * Translated using Weblate (Croatian) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/hr/ * Translated using Weblate (Malayalam) Currently translated at 15.5% (125 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/ * Translated using Weblate (Malayalam) Currently translated at 15.5% (125 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/ * Translated using Weblate (Malayalam) Currently translated at 94.4% (17 of 18 strings) Translation: Mihon/Mihon Plurals Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ml/ --------- Co-authored-by: ɴᴇᴋᴏ Co-authored-by: Milo Ivir Co-authored-by: Akhil Raj Co-authored-by: Animeboynz (cherry picked from commit aa1714b2acf0e5b16558ea703220f60d4ecd23e9) --- .../commonMain/resources/MR/ml/plurals.xml | 2 +- .../commonMain/resources/MR/ml/strings.xml | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/i18n/src/commonMain/resources/MR/ml/plurals.xml b/i18n/src/commonMain/resources/MR/ml/plurals.xml index a3e67ba1c0..589be4b616 100644 --- a/i18n/src/commonMain/resources/MR/ml/plurals.xml +++ b/i18n/src/commonMain/resources/MR/ml/plurals.xml @@ -61,7 +61,7 @@ %1$d പുതിയ അധ്യായങ്ങൾ - അധ്യായങ്ങൾ %1$s-ഉം 1-ഉം + അധ്യായങ്ങൾ %1$s-ഉം പിന്നെ 1-ഉം %1$s-ഉം %2$d-ഉം അധ്യായങ്ങൾ diff --git a/i18n/src/commonMain/resources/MR/ml/strings.xml b/i18n/src/commonMain/resources/MR/ml/strings.xml index 7a6be0bfb5..b9c9409dd2 100644 --- a/i18n/src/commonMain/resources/MR/ml/strings.xml +++ b/i18n/src/commonMain/resources/MR/ml/strings.xml @@ -99,4 +99,25 @@ വെബ്‌വ്യൂവിൽ തുറക്കുക മൈഗ്രേറ്റ് ചെയ്യുക ഡിസ്പ്ലേ മോഡ് + ഏറ്റവും പഴയത് + റദ്ദാക്കുക + വായന തുടരുക ബട്ടൺ + ഭാഷ + എല്ലാം റദ്ദാക്കുക + ശരി + ഈ സീരീസിനായി എല്ലാം റദ്ദാക്കുക + മുകളിലേക്ക് നീങ്ങുക + അധ്യായ നമ്പർ പ്രകാരം + ഏറ്റവും പുതിയത് + എൻട്രി കാണിക്കുക + കോംപാക്റ്റ് ഗ്രിഡ് + സുഖപ്രദമായ ഗ്രിഡ് + ഡൌൺലോഡ് ചെയ്ത അധ്യായങ്ങൾ + ലോക്കൽ സോഴ്സ് + ഡിസേബിൾ + പിൻ ചെയ്യുക + അപ്ലൈ + സോർട് + ലിസ്റ്റ് + ചിത്രം മാത്രമുള്ള ഗ്രിഡ് \ No newline at end of file From fa2f4e886abb7788df5da5da74cfd9a046ebb826 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 20 Jun 2024 21:41:26 +0200 Subject: [PATCH 43/74] chore(deps): update softprops/action-gh-release action to v2.0.6 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 518693699f..c33fb7891b 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -96,7 +96,7 @@ jobs: - name: Create Release if: startsWith(github.ref, 'refs/tags/') && github.repository == 'aniyomiorg/aniyomi' - uses: softprops/action-gh-release@69320dbe05506a9a39fc8ae11030b214ec2d1f87 # v2.0.5 + uses: softprops/action-gh-release@a74c6b72af54cfa997e81df42d94703d6313a2d0 # v2.0.6 with: tag_name: ${{ env.VERSION_TAG }} name: Aniyomi ${{ env.VERSION_TAG }} From 48abd7eb516b7bb787b24477e56e8129306890a8 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Thu, 20 Jun 2024 22:24:22 +0200 Subject: [PATCH 44/74] Translations update from Hosted Weblate (#904) * Translated using Weblate (Malayalam) Currently translated at 16.9% (136 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ml/ * Translated using Weblate (Swedish) Currently translated at 99.1% (797 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/ * Translated using Weblate (Arabic) Currently translated at 99.5% (800 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/ * Translated using Weblate (Swedish) Currently translated at 100.0% (804 of 804 strings) Translation: Mihon/Mihon Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/sv/ * Translated using Weblate (Swedish) Currently translated at 100.0% (18 of 18 strings) Translation: Mihon/Mihon Plurals Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/sv/ --------- Co-authored-by: Akhil Raj Co-authored-by: Norsze Co-authored-by: Duh051 Co-authored-by: bittin1ddc447d824349b2 (cherry picked from commit cf02119da55c431d0fb4c42ecfec3681d466ae43) --- i18n/src/commonMain/moko-resources/sv/plurals.xml | 4 ++++ i18n/src/commonMain/resources/MR/ml/strings.xml | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/i18n/src/commonMain/moko-resources/sv/plurals.xml b/i18n/src/commonMain/moko-resources/sv/plurals.xml index 6fc6229a55..062e457a63 100644 --- a/i18n/src/commonMain/moko-resources/sv/plurals.xml +++ b/i18n/src/commonMain/moko-resources/sv/plurals.xml @@ -88,4 +88,8 @@ + + Imorgon + Om %1$d dagar + \ No newline at end of file diff --git a/i18n/src/commonMain/resources/MR/ml/strings.xml b/i18n/src/commonMain/resources/MR/ml/strings.xml index b9c9409dd2..7e6ad53a51 100644 --- a/i18n/src/commonMain/resources/MR/ml/strings.xml +++ b/i18n/src/commonMain/resources/MR/ml/strings.xml @@ -120,4 +120,15 @@ സോർട് ലിസ്റ്റ് ചിത്രം മാത്രമുള്ള ഗ്രിഡ് + പുനഃസജ്ജമാക്കുക + പരമ്പര മുകളിലേക്ക് നീക്കുക + വിഭാഗം ടാബുകൾ കാണിക്കുക + അൺപിൻ ചെയ്യുക + അപ്‌ലോഡ് തീയതി പ്രകാരം + ആരോഹണം + അവരോഹണം + ഇൻസ്റ്റാൾ + താഴേക്ക് നീക്കുക + പങ്കിടുക + പ്രദർശിപ്പിക്കുക \ No newline at end of file From a3f7be5f6b11a9738aee0e73bdbdf6f74f1edb5e Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:07:44 +0200 Subject: [PATCH 45/74] fix(deps): update moko to v0.24.1 * fix(deps): update moko to v0.24.1 * Fix build --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- i18n/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/build.gradle.kts b/i18n/build.gradle.kts index 97e6c8725c..4b95ad331d 100644 --- a/i18n/build.gradle.kts +++ b/i18n/build.gradle.kts @@ -13,7 +13,7 @@ kotlin { applyDefaultHierarchyTemplate() sourceSets { - val commonMain by getting { + commonMain { dependencies { api(libs.moko.core) } From d670bd530f2e813f3c5731eb298e82aeecd652cf Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:08:53 +0200 Subject: [PATCH 46/74] Update `build_pull_request.yml` `paths-ignore` Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .github/workflows/build_pull_request.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 8c75adae22..ba0a69fe70 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -3,10 +3,10 @@ on: pull_request: paths-ignore: - '**.md' - - 'i18n/src/commonMain/resources/**/strings-aniyomi.xml' - - 'i18n/src/commonMain/resources/**/strings.xml' - - 'i18n/src/commonMain/resources/**/plurals-aniyomi.xml' - - 'i18n/src/commonMain/resources/**/plurals.xml' + - 'i18n/src/commonMain/moko-resources/**/strings-aniyomi.xml' + - 'i18n/src/commonMain/moko-resources/**/strings.xml' + - 'i18n/src/commonMain/moko-resources/**/plurals-aniyomi.xml' + - 'i18n/src/commonMain/moko-resources/**/plurals.xml' concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} From b88b179e476454f600a563378ec33170e45f9a40 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:17:04 +0200 Subject: [PATCH 47/74] Fix unexpected skips in strong skipping mode Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- gradle/compose.versions.toml | 1 + source-api/build.gradle.kts | 3 +++ .../eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt | 3 +++ .../kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt | 3 +++ 4 files changed, 10 insertions(+) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index b5e306abe1..c36eb21cbe 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -8,6 +8,7 @@ bom = { group = "dev.chrisbanes.compose", name = "compose-bom", version.ref = "c foundation = { module = "androidx.compose.foundation:foundation" } animation = { module = "androidx.compose.animation:animation" } animation-graphics = { module = "androidx.compose.animation:animation-graphics" } +runtime = { module = "androidx.compose.runtime:runtime" } ui-tooling = { module = "androidx.compose.ui:ui-tooling" } ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } ui-util = { module = "androidx.compose.ui:ui-util" } diff --git a/source-api/build.gradle.kts b/source-api/build.gradle.kts index af47e886bc..ad9fbf516d 100644 --- a/source-api/build.gradle.kts +++ b/source-api/build.gradle.kts @@ -13,6 +13,9 @@ kotlin { api(libs.injekt.core) api(libs.rxjava) api(libs.jsoup) + + implementation(project.dependencies.platform(compose.bom)) + implementation(compose.runtime) } } val androidMain by getting { diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt index 25c2124b9f..4304d187eb 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/animesource/model/AnimeFilterList.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.animesource.model +import androidx.compose.runtime.Stable + +@Stable data class AnimeFilterList(val list: List>) : List> by list { constructor(vararg fs: AnimeFilter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt index 77f339b9d9..6c99352665 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -1,5 +1,8 @@ package eu.kanade.tachiyomi.source.model +import androidx.compose.runtime.Stable + +@Stable data class FilterList(val list: List>) : List> by list { constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) From 1c0e44f7f953c6d204fb7a5b8d3cd27137f1bea6 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:21:58 +0200 Subject: [PATCH 48/74] Fix Migrator test and also add the test to build script * Fix MigratorTest after update to Kotlin 2.0.0 * add main module's test to build script Co-authored-by: Tran M. Cuong <16017808+cuong-tran@users.noreply.github.com> --- .github/workflows/build_pull_request.yml | 2 +- .github/workflows/build_push.yml | 2 +- app/src/test/java/mihon/core/migration/MigratorTest.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index ba0a69fe70..b10d1fcc17 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -39,4 +39,4 @@ jobs: - name: Build app and run unit tests uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 with: - arguments: detekt assembleStandardRelease testReleaseUnitTest \ No newline at end of file + arguments: detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest \ No newline at end of file diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index c33fb7891b..55b1db229b 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -46,7 +46,7 @@ jobs: - name: Build app and run unit tests uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 with: - arguments: detekt assembleStandardRelease testReleaseUnitTest + arguments: detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest # Sign APK and create release for tags diff --git a/app/src/test/java/mihon/core/migration/MigratorTest.kt b/app/src/test/java/mihon/core/migration/MigratorTest.kt index 47c4bc7c2e..a805b56302 100644 --- a/app/src/test/java/mihon/core/migration/MigratorTest.kt +++ b/app/src/test/java/mihon/core/migration/MigratorTest.kt @@ -29,7 +29,7 @@ class MigratorTest { fun initilize() { migrationContext = MigrationContext(false) migrationJobFactory = spyk(MigrationJobFactory(migrationContext, CoroutineScope(Dispatchers.Main + Job()))) - migrationCompletedListener = spyk<() -> Unit>({}) + migrationCompletedListener = spyk(block = {}) migrationStrategyFactory = spyk(MigrationStrategyFactory(migrationJobFactory, migrationCompletedListener)) } From b1fba0e55f090f5391b98c239e45a9c8520dac72 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:29:26 +0200 Subject: [PATCH 49/74] Cleanup in `CommonMangaItem.kt` Closes #19 Co-authored-by: Roshan Varughese <40583749+Animeboynz@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../library/components/CommonEntryItem.kt | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt b/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt index 9738a8bb47..175a595b5d 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt @@ -35,23 +35,31 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import eu.kanade.presentation.entries.components.ItemCover -import tachiyomi.domain.entries.EntryCover +import tachiyomi.domain.entries.EntryCover as EntryCoverModel import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.BadgeGroup import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.selectedBackground + object CommonEntryItemDefaults { val GridHorizontalSpacer = 4.dp val GridVerticalSpacer = 4.dp + @Suppress("ConstPropertyName") const val BrowseFavoriteCoverAlpha = 0.34f } -private val ContinueViewingButtonSize = 28.dp +private val ContinueViewingButtonSizeSmall = 28.dp +private val ContinueViewingButtonSizeLarge = 32.dp + +private val ContinueViewingButtonIconSizeSmall = 16.dp +private val ContinueViewingButtonIconSizeLarge = 20.dp + private val ContinueViewingButtonGridPadding = 6.dp private val ContinueViewingButtonListSpacing = 8.dp @@ -63,7 +71,7 @@ private const val GridSelectedCoverAlpha = 0.76f */ @Composable fun EntryCompactGridItem( - coverData: EntryCover, + coverData: EntryCoverModel, onClick: () -> Unit, onLongClick: () -> Unit, isSelected: Boolean = false, @@ -97,10 +105,12 @@ fun EntryCompactGridItem( ) } else if (onClickContinueViewing != null) { ContinueViewingButton( + size = ContinueViewingButtonSizeLarge, + iconSize = ContinueViewingButtonIconSizeLarge, + onClick = onClickContinueViewing, modifier = Modifier .padding(ContinueViewingButtonGridPadding) .align(Alignment.BottomEnd), - onClickContinueViewing = onClickContinueViewing, ) } }, @@ -149,11 +159,13 @@ private fun BoxScope.CoverTextOverlay( ) if (onClickContinueViewing != null) { ContinueViewingButton( + size = ContinueViewingButtonSizeSmall, + iconSize = ContinueViewingButtonIconSizeSmall, + onClick = onClickContinueViewing, modifier = Modifier.padding( end = ContinueViewingButtonGridPadding, bottom = ContinueViewingButtonGridPadding, ), - onClickContinueViewing = onClickContinueViewing, ) } } @@ -169,7 +181,7 @@ fun EntryComfortableGridItem( onClick: () -> Unit, onLongClick: () -> Unit, titleMaxLines: Int = 2, - coverData: EntryCover, + coverData: EntryCoverModel, coverAlpha: Float = 1f, coverBadgeStart: (@Composable RowScope.() -> Unit)? = null, coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null, @@ -195,10 +207,12 @@ fun EntryComfortableGridItem( content = { if (onClickContinueViewing != null) { ContinueViewingButton( + size = ContinueViewingButtonSizeLarge, + iconSize = ContinueViewingButtonIconSizeLarge, + onClick = onClickContinueViewing, modifier = Modifier .padding(ContinueViewingButtonGridPadding) .align(Alignment.BottomEnd), - onClickContinueViewing = onClickContinueViewing, ) } }, @@ -310,7 +324,7 @@ private fun GridItemSelectable( private fun Modifier.selectedOutline( isSelected: Boolean, color: Color, -) = this then drawBehind { if (isSelected) drawRect(color = color) } +) = drawBehind { if (isSelected) drawRect(color = color) } /** * Layout of list item. @@ -319,7 +333,7 @@ private fun Modifier.selectedOutline( fun EntryListItem( isSelected: Boolean = false, title: String, - coverData: EntryCover, + coverData: EntryCoverModel, coverAlpha: Float = 1f, onLongClick: () -> Unit, onClick: () -> Unit, @@ -355,8 +369,10 @@ fun EntryListItem( BadgeGroup(content = badge) if (onClickContinueViewing != null) { ContinueViewingButton( + size = ContinueViewingButtonSizeSmall, + iconSize = ContinueViewingButtonIconSizeSmall, + onClick = onClickContinueViewing, modifier = Modifier.padding(start = ContinueViewingButtonListSpacing), - onClickContinueViewing = onClickContinueViewing, ) } } @@ -364,23 +380,25 @@ fun EntryListItem( @Composable private fun ContinueViewingButton( + size: Dp, + iconSize: Dp, + onClick: () -> Unit, modifier: Modifier = Modifier, - onClickContinueViewing: () -> Unit, ) { Box(modifier = modifier) { FilledIconButton( - onClick = onClickContinueViewing, - modifier = Modifier.size(ContinueViewingButtonSize), + onClick = onClick, shape = MaterialTheme.shapes.small, colors = IconButtonDefaults.filledIconButtonColors( containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f), contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer), ), + modifier = Modifier.size(size) ) { Icon( imageVector = Icons.Filled.PlayArrow, contentDescription = stringResource(MR.strings.action_resume), - modifier = Modifier.size(16.dp), + modifier = Modifier.size(iconSize), ) } } From 08e58e7fef5712cee49450bda0b18fe294c33b33 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:31:11 +0200 Subject: [PATCH 50/74] Upload build artifacts To decode obfuscated stack traces and help debugging R8 issues. Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- .github/workflows/build_pull_request.yml | 14 +++++++++++++- .github/workflows/build_push.yml | 12 ++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index b10d1fcc17..7d8c2ec93f 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -39,4 +39,16 @@ jobs: - name: Build app and run unit tests uses: gradle/actions/setup-gradle@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 with: - arguments: detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest \ No newline at end of file + arguments: detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest + + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: arm64-v8a-${{ github.sha }} + path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk + + - name: Upload mapping + uses: actions/upload-artifact@v4 + with: + name: mapping-${{ github.sha }} + path: app/build/outputs/mapping/standardRelease \ No newline at end of file diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 55b1db229b..59c202f820 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -48,6 +48,18 @@ jobs: with: arguments: detekt assembleStandardRelease testReleaseUnitTest testStandardReleaseUnitTest + - name: Upload APK + uses: actions/upload-artifact@v4 + with: + name: arm64-v8a-${{ github.sha }} + path: app/build/outputs/apk/standard/release/app-standard-arm64-v8a-release-unsigned.apk + + - name: Upload mapping + uses: actions/upload-artifact@v4 + with: + name: mapping-${{ github.sha }} + path: app/build/outputs/mapping/standardRelease + # Sign APK and create release for tags - name: Get tag name From bde254aa88a9bf6082df5b0aa25e7b979673fb7f Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 21:31:57 +0200 Subject: [PATCH 51/74] [skip ci] remove unused github workflow Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .github/mergify.yml | 10 --------- .github/workflows/issue_moderator.yml | 29 --------------------------- 2 files changed, 39 deletions(-) delete mode 100644 .github/mergify.yml delete mode 100644 .github/workflows/issue_moderator.yml diff --git a/.github/mergify.yml b/.github/mergify.yml deleted file mode 100644 index c9ddf76029..0000000000 --- a/.github/mergify.yml +++ /dev/null @@ -1,10 +0,0 @@ -#pull_request_rules: -# - name: Automatically merge translations -# conditions: -# - "author = weblate" -# - "-conflict" -# - "current-day-of-week = Sat" -# - "created-at < 1 day ago" -# actions: -# merge: -# method: squash \ No newline at end of file diff --git a/.github/workflows/issue_moderator.yml b/.github/workflows/issue_moderator.yml deleted file mode 100644 index 9fe64c1bb6..0000000000 --- a/.github/workflows/issue_moderator.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Issue moderator - -on: - issues: - types: [opened, edited, reopened] - issue_comment: - types: [created] - -jobs: - moderate: - runs-on: ubuntu-latest - steps: - - name: Moderate issues - uses: keiyoushi/issue-moderator-action@a017be83547db6e107431ce7575f53c1dfa3296a - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - duplicate-label: Duplicate - - auto-close-rules: | - [ - { - "type": "both", - "regex": ".*(?:fail(?:ed|ure|s)?|can\\s*(?:no|')?t|(?:not|un).*able|(? Date: Fri, 12 Jul 2024 22:00:23 +0200 Subject: [PATCH 52/74] Refactor archive support with libarchive * Refactor archive support with libarchive * Revert string resource changs * Only mark archive formats as supported Comic book archives should not be compressed. * Fixup * Remove epub from archive format list * Move to mihon package * Format * Cleanup Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- app/build.gradle.kts | 1 - app/proguard-rules.pro | 5 +- .../data/download/manga/MangaDownloader.kt | 27 +----- ...{ZipPageLoader.kt => ArchivePageLoader.kt} | 20 ++--- .../ui/reader/loader/ChapterLoader.kt | 12 +-- .../ui/reader/loader/DownloadPageLoader.kt | 10 +-- .../ui/reader/loader/EpubPageLoader.kt | 8 +- .../ui/reader/loader/RarPageLoader.kt | 67 -------------- core/common/build.gradle.kts | 2 +- .../kanade/tachiyomi/util/storage/EpubFile.kt | 42 +++------ .../mihon/core/common/archive/ArchiveEntry.kt | 6 ++ .../core/common/archive/ArchiveInputStream.kt | 52 +++++++++++ .../core/common/archive/ArchiveReader.kt | 42 +++++++++ .../mihon/core/common/archive/ZipWriter.kt | 74 ++++++++++++++++ .../common/extensions/SeekableByteChannel.kt | 8 -- .../core/common/storage/UniFileExtensions.kt | 6 +- gradle/libs.versions.toml | 4 +- .../moko-resources/base/strings.xml | 1 - source-local/build.gradle.kts | 1 - .../local/entries/manga/LocalMangaSource.kt | 87 ++++--------------- .../tachiyomi/source/local/io/Archive.kt | 4 +- .../tachiyomi/source/local/io/Format.kt | 17 ++-- 22 files changed, 241 insertions(+), 255 deletions(-) rename app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/{ZipPageLoader.kt => ArchivePageLoader.kt} (57%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt create mode 100644 core/common/src/main/java/mihon/core/common/archive/ArchiveEntry.kt create mode 100644 core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt create mode 100644 core/common/src/main/java/mihon/core/common/archive/ArchiveReader.kt create mode 100644 core/common/src/main/java/mihon/core/common/archive/ZipWriter.kt delete mode 100644 core/common/src/main/java/mihon/core/common/extensions/SeekableByteChannel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ebb3f365a0..1d6e353267 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -214,7 +214,6 @@ dependencies { // Disk implementation(libs.disklrucache) implementation(libs.unifile) - implementation(libs.bundles.archive) // Preferences implementation(libs.preferencektx) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0906f6c415..4f47aa94ce 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -79,7 +79,4 @@ ##---------------End: proguard configuration for kotlinx.serialization ---------- # XmlUtil --keep public enum nl.adaptivity.xmlutil.EventType { *; } - -# Apache Commons Compress --keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { (); } \ No newline at end of file +-keep public enum nl.adaptivity.xmlutil.EventType { *; } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 0432c33da4..696f72e5cd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -41,6 +41,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import logcat.LogPriority +import mihon.core.common.archive.ZipWriter import nl.adaptivity.xmlutil.serialization.XML import okhttp3.Response import okio.Throttler @@ -63,12 +64,8 @@ import tachiyomi.domain.track.manga.interactor.GetMangaTracks import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.io.BufferedOutputStream import java.io.File import java.util.Locale -import java.util.zip.CRC32 -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream /** * This class is the one in charge of downloading chapters. @@ -643,25 +640,9 @@ class MangaDownloader( tmpDir: UniFile, ) { val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!! - ZipOutputStream(BufferedOutputStream(zip.openOutputStream())).use { zipOut -> - zipOut.setMethod(ZipEntry.STORED) - - tmpDir.listFiles()?.forEach { img -> - img.openInputStream().use { input -> - val data = input.readBytes() - val size = img.length() - val entry = ZipEntry(img.name).apply { - val crc = CRC32().apply { - update(data) - } - setCrc(crc.value) - - compressedSize = size - setSize(size) - } - zipOut.putNextEntry(entry) - zipOut.write(data) - } + ZipWriter(context, zip).use { writer -> + tmpDir.listFiles()?.forEach { file -> + writer.write(file) } } zip.renameTo("$dirname.cbz") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt similarity index 57% rename from app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt rename to app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt index 89856bf226..397ac51bc1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt @@ -3,26 +3,22 @@ package eu.kanade.tachiyomi.ui.reader.loader import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder -import mihon.core.common.extensions.toZipFile +import mihon.core.common.archive.ArchiveReader import tachiyomi.core.common.util.system.ImageUtil -import java.nio.channels.SeekableByteChannel /** - * Loader used to load a chapter from a .zip or .cbz file. + * Loader used to load a chapter from an archive file. */ -internal class ZipPageLoader(channel: SeekableByteChannel) : PageLoader() { - - private val zip = channel.toZipFile() - +internal class ArchivePageLoader(private val reader: ArchiveReader) : PageLoader() { override var isLocal: Boolean = true - override suspend fun getPages(): List { - return zip.entries.asSequence() - .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } + override suspend fun getPages(): List = reader.useEntries { entries -> + entries + .filter { it.isFile && ImageUtil.isImage(it.name) { reader.getInputStream(it.name)!! } } .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .mapIndexed { i, entry -> ReaderPage(i).apply { - stream = { zip.getInputStream(entry) } + stream = { reader.getInputStream(entry.name)!! } status = Page.State.READY } } @@ -35,6 +31,6 @@ internal class ZipPageLoader(channel: SeekableByteChannel) : PageLoader() { override fun recycle() { super.recycle() - zip.close() + reader.close() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 836eae1df5..3d57326414 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -1,15 +1,14 @@ package eu.kanade.tachiyomi.ui.reader.loader import android.content.Context -import com.github.junrar.exception.UnsupportedRarV5Exception import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.download.manga.MangaDownloadProvider import eu.kanade.tachiyomi.source.MangaSource import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences +import mihon.core.common.archive.archiveReader import tachiyomi.core.common.i18n.stringResource -import tachiyomi.core.common.storage.openReadOnlyChannel import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.entries.manga.model.Manga @@ -98,13 +97,8 @@ class ChapterLoader( source is LocalMangaSource -> source.getFormat(chapter.chapter).let { format -> when (format) { is Format.Directory -> DirectoryPageLoader(format.file) - is Format.Zip -> ZipPageLoader(format.file.openReadOnlyChannel(context)) - is Format.Rar -> try { - RarPageLoader(format.file.openInputStream()) - } catch (e: UnsupportedRarV5Exception) { - error(context.stringResource(MR.strings.loader_rar5_error)) - } - is Format.Epub -> EpubPageLoader(format.file.openReadOnlyChannel(context)) + is Format.Archive -> ArchivePageLoader(format.file.archiveReader(context)) + is Format.Epub -> EpubPageLoader(format.file.archiveReader(context)) } } source is HttpSource -> HttpPageLoader(chapter, source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index bb8afcb249..b5c499659b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.source.MangaSource import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import tachiyomi.core.common.storage.openReadOnlyChannel +import mihon.core.common.archive.archiveReader import tachiyomi.domain.entries.manga.model.Manga import uy.kohesive.injekt.injectLazy @@ -27,7 +27,7 @@ internal class DownloadPageLoader( private val context: Application by injectLazy() - private var zipPageLoader: ZipPageLoader? = null + private var archivePageLoader: ArchivePageLoader? = null override var isLocal: Boolean = true @@ -48,11 +48,11 @@ internal class DownloadPageLoader( override fun recycle() { super.recycle() - zipPageLoader?.recycle() + archivePageLoader?.recycle() } private suspend fun getPagesFromArchive(file: UniFile): List { - val loader = ZipPageLoader(file.openReadOnlyChannel(context)).also { zipPageLoader = it } + val loader = ArchivePageLoader(file.archiveReader(context)).also { archivePageLoader = it } return loader.getPages() } @@ -72,6 +72,6 @@ internal class DownloadPageLoader( } override suspend fun loadPage(page: ReaderPage) { - zipPageLoader?.loadPage(page) + archivePageLoader?.loadPage(page) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index baf65324ba..8ace2fdeee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -3,21 +3,21 @@ package eu.kanade.tachiyomi.ui.reader.loader import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.util.storage.EpubFile -import java.nio.channels.SeekableByteChannel +import mihon.core.common.archive.ArchiveReader /** * Loader used to load a chapter from a .epub file. */ -internal class EpubPageLoader(channel: SeekableByteChannel) : PageLoader() { +internal class EpubPageLoader(reader: ArchiveReader) : PageLoader() { - private val epub = EpubFile(channel) + private val epub = EpubFile(reader) override var isLocal: Boolean = true override suspend fun getPages(): List { return epub.getImagesFromPages() .mapIndexed { i, path -> - val streamFn = { epub.getInputStream(epub.getEntry(path)!!) } + val streamFn = { epub.getInputStream(path)!! } ReaderPage(i).apply { stream = streamFn status = Page.State.READY diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt deleted file mode 100644 index b1db3300d8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt +++ /dev/null @@ -1,67 +0,0 @@ -package eu.kanade.tachiyomi.ui.reader.loader - -import com.github.junrar.Archive -import com.github.junrar.rarfile.FileHeader -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder -import tachiyomi.core.common.util.system.ImageUtil -import java.io.InputStream -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.util.concurrent.Executors - -/** - * Loader used to load a chapter from a .rar or .cbr file. - */ -internal class RarPageLoader(inputStream: InputStream) : PageLoader() { - - private val rar = Archive(inputStream) - - override var isLocal: Boolean = true - - /** - * Pool for copying compressed files to an input stream. - */ - private val pool = Executors.newFixedThreadPool(1) - - override suspend fun getPages(): List { - return rar.fileHeaders.asSequence() - .filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } } - .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } - .mapIndexed { i, header -> - ReaderPage(i).apply { - stream = { getStream(header) } - status = Page.State.READY - } - } - .toList() - } - - override suspend fun loadPage(page: ReaderPage) { - check(!isRecycled) - } - - override fun recycle() { - super.recycle() - rar.close() - pool.shutdown() - } - - /** - * Returns an input stream for the given [header]. - */ - private fun getStream(header: FileHeader): InputStream { - val pipeIn = PipedInputStream() - val pipeOut = PipedOutputStream(pipeIn) - pool.execute { - try { - pipeOut.use { - rar.extractFile(header, it) - } - } catch (e: Exception) { - } - } - return pipeIn - } -} diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index 5a4c36a03c..30d20f78e3 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { implementation(libs.image.decoder) implementation(libs.unifile) - implementation(libs.bundles.archive) + implementation(libs.libarchive) api(kotlinx.coroutines.core) api(kotlinx.serialization.json) diff --git a/core/common/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/core/common/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt index 29cea58248..b194c5ee3a 100644 --- a/core/common/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/core/common/src/main/java/eu/kanade/tachiyomi/util/storage/EpubFile.kt @@ -1,48 +1,27 @@ package eu.kanade.tachiyomi.util.storage -import mihon.core.common.extensions.toZipFile -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry +import mihon.core.common.archive.ArchiveReader import org.jsoup.Jsoup import org.jsoup.nodes.Document import java.io.Closeable import java.io.File import java.io.InputStream -import java.nio.channels.SeekableByteChannel /** * Wrapper over ZipFile to load files in epub format. */ -class EpubFile(channel: SeekableByteChannel) : Closeable { - - /** - * Zip file of this epub. - */ - private val zip = channel.toZipFile() +class EpubFile(private val reader: ArchiveReader) : Closeable by reader { /** * Path separator used by this epub. */ private val pathSeparator = getPathSeparator() - /** - * Closes the underlying zip file. - */ - override fun close() { - zip.close() - } - /** * Returns an input stream for reading the contents of the specified zip file entry. */ - fun getInputStream(entry: ZipArchiveEntry): InputStream { - return zip.getInputStream(entry) - } - - /** - * Returns the zip file entry for the specified name, or null if not found. - */ - fun getEntry(name: String): ZipArchiveEntry? { - return zip.getEntry(name) + fun getInputStream(entryName: String): InputStream? { + return reader.getInputStream(entryName) } /** @@ -59,9 +38,9 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { * Returns the path to the package document. */ fun getPackageHref(): String { - val meta = zip.getEntry(resolveZipPath("META-INF", "container.xml")) + val meta = getInputStream(resolveZipPath("META-INF", "container.xml")) if (meta != null) { - val metaDoc = zip.getInputStream(meta).use { Jsoup.parse(it, null, "") } + val metaDoc = meta.use { Jsoup.parse(it, null, "") } val path = metaDoc.getElementsByTag("rootfile").first()?.attr("full-path") if (path != null) { return path @@ -74,8 +53,7 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { * Returns the package document where all the files are listed. */ fun getPackageDocument(ref: String): Document { - val entry = zip.getEntry(ref) - return zip.getInputStream(entry).use { Jsoup.parse(it, null, "") } + return getInputStream(ref)!!.use { Jsoup.parse(it, null, "") } } /** @@ -98,8 +76,7 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { val basePath = getParentDirectory(packageHref) pages.forEach { page -> val entryPath = resolveZipPath(basePath, page) - val entry = zip.getEntry(entryPath) - val document = zip.getInputStream(entry).use { Jsoup.parse(it, null, "") } + val document = getInputStream(entryPath)!!.use { Jsoup.parse(it, null, "") } val imageBasePath = getParentDirectory(entryPath) document.allElements.forEach { @@ -117,8 +94,9 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { * Returns the path separator used by the epub file. */ private fun getPathSeparator(): String { - val meta = zip.getEntry("META-INF\\container.xml") + val meta = getInputStream("META-INF\\container.xml") return if (meta != null) { + meta.close() "\\" } else { "/" diff --git a/core/common/src/main/java/mihon/core/common/archive/ArchiveEntry.kt b/core/common/src/main/java/mihon/core/common/archive/ArchiveEntry.kt new file mode 100644 index 0000000000..26240de003 --- /dev/null +++ b/core/common/src/main/java/mihon/core/common/archive/ArchiveEntry.kt @@ -0,0 +1,6 @@ +package mihon.core.common.archive + +class ArchiveEntry( + val name: String, + val isFile: Boolean, +) diff --git a/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt b/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt new file mode 100644 index 0000000000..a9bb878791 --- /dev/null +++ b/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt @@ -0,0 +1,52 @@ +package mihon.core.common.archive + +import me.zhanghai.android.libarchive.Archive +import me.zhanghai.android.libarchive.ArchiveEntry +import me.zhanghai.android.libarchive.ArchiveException +import java.io.InputStream +import java.nio.ByteBuffer + +class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { + private val archive = Archive.readNew() + + init { + try { + Archive.setCharset(archive, Charsets.UTF_8.name().toByteArray()) + Archive.readSupportFilterAll(archive) + Archive.readSupportFormatAll(archive) + Archive.readOpenMemoryUnsafe(archive, buffer, size) + } catch (e: ArchiveException) { + close() + throw e + } + } + + private val oneByteBuffer = ByteBuffer.allocateDirect(1) + + override fun read(): Int { + read(oneByteBuffer) + return if (oneByteBuffer.hasRemaining()) oneByteBuffer.get().toUByte().toInt() else -1 + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + val buffer = ByteBuffer.wrap(b, off, len) + read(buffer) + return if (buffer.hasRemaining()) buffer.remaining() else -1 + } + + private fun read(buffer: ByteBuffer) { + buffer.clear() + Archive.readData(archive, buffer) + buffer.flip() + } + + override fun close() { + Archive.readFree(archive) + } + + fun getNextEntry() = Archive.readNextHeader(archive).takeUnless { it == 0L }?.let { entry -> + val name = ArchiveEntry.pathnameUtf8(entry) ?: ArchiveEntry.pathname(entry)?.decodeToString() ?: return null + val isFile = ArchiveEntry.filetype(entry) == ArchiveEntry.AE_IFREG + ArchiveEntry(name, isFile) + } +} diff --git a/core/common/src/main/java/mihon/core/common/archive/ArchiveReader.kt b/core/common/src/main/java/mihon/core/common/archive/ArchiveReader.kt new file mode 100644 index 0000000000..28467d0fea --- /dev/null +++ b/core/common/src/main/java/mihon/core/common/archive/ArchiveReader.kt @@ -0,0 +1,42 @@ +package mihon.core.common.archive + +import android.content.Context +import android.os.ParcelFileDescriptor +import android.system.Os +import android.system.OsConstants +import com.hippo.unifile.UniFile +import me.zhanghai.android.libarchive.ArchiveException +import tachiyomi.core.common.storage.openFileDescriptor +import java.io.Closeable +import java.io.InputStream + +class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable { + val size = pfd.statSize + val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) + + inline fun useEntries(block: (Sequence) -> T): T = + ArchiveInputStream(address, size).use { block(generateSequence { it.getNextEntry() }) } + + fun getInputStream(entryName: String): InputStream? { + val archive = ArchiveInputStream(address, size) + try { + while (true) { + val entry = archive.getNextEntry() ?: break + if (entry.name == entryName) { + return archive + } + } + } catch (e: ArchiveException) { + archive.close() + throw e + } + archive.close() + return null + } + + override fun close() { + Os.munmap(address, size) + } +} + +fun UniFile.archiveReader(context: Context) = openFileDescriptor(context, "r").use { ArchiveReader(it) } diff --git a/core/common/src/main/java/mihon/core/common/archive/ZipWriter.kt b/core/common/src/main/java/mihon/core/common/archive/ZipWriter.kt new file mode 100644 index 0000000000..b5d2015168 --- /dev/null +++ b/core/common/src/main/java/mihon/core/common/archive/ZipWriter.kt @@ -0,0 +1,74 @@ +package mihon.core.common.archive + +import android.content.Context +import android.system.Os +import android.system.StructStat +import com.hippo.unifile.UniFile +import me.zhanghai.android.libarchive.Archive +import me.zhanghai.android.libarchive.ArchiveEntry +import me.zhanghai.android.libarchive.ArchiveException +import tachiyomi.core.common.storage.openFileDescriptor +import java.io.Closeable +import java.nio.ByteBuffer + +class ZipWriter(val context: Context, file: UniFile) : Closeable { + private val pfd = file.openFileDescriptor(context, "wt") + private val archive = Archive.writeNew() + private val entry = ArchiveEntry.new2(archive) + private val buffer = ByteBuffer.allocateDirect(8192) + + init { + try { + Archive.setCharset(archive, Charsets.UTF_8.name().toByteArray()) + Archive.writeSetFormatZip(archive) + Archive.writeZipSetCompressionStore(archive) + Archive.writeOpenFd(archive, pfd.fd) + } catch (e: ArchiveException) { + close() + throw e + } + } + + fun write(file: UniFile) { + file.openFileDescriptor(context, "r").use { + val fd = it.fileDescriptor + ArchiveEntry.clear(entry) + ArchiveEntry.setPathnameUtf8(entry, file.name) + val stat = Os.fstat(fd) + ArchiveEntry.setStat(entry, stat.toArchiveStat()) + Archive.writeHeader(archive, entry) + while (true) { + buffer.clear() + Os.read(fd, buffer) + if (buffer.position() == 0) break + buffer.flip() + Archive.writeData(archive, buffer) + } + Archive.writeFinishEntry(archive) + } + } + + override fun close() { + ArchiveEntry.free(entry) + Archive.writeFree(archive) + pfd.close() + } +} + +private fun StructStat.toArchiveStat() = ArchiveEntry.StructStat().apply { + stDev = st_dev + stMode = st_mode + stNlink = st_nlink.toInt() + stUid = st_uid + stGid = st_gid + stRdev = st_rdev + stSize = st_size + stBlksize = st_blksize + stBlocks = st_blocks + stAtim = timespec(st_atime) + stMtim = timespec(st_mtime) + stCtim = timespec(st_ctime) + stIno = st_ino +} + +private fun timespec(tvSec: Long) = ArchiveEntry.StructTimespec().also { it.tvSec = tvSec } diff --git a/core/common/src/main/java/mihon/core/common/extensions/SeekableByteChannel.kt b/core/common/src/main/java/mihon/core/common/extensions/SeekableByteChannel.kt deleted file mode 100644 index 69e2d7201f..0000000000 --- a/core/common/src/main/java/mihon/core/common/extensions/SeekableByteChannel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package mihon.core.common.extensions - -import org.apache.commons.compress.archivers.zip.ZipFile -import java.nio.channels.SeekableByteChannel - -fun SeekableByteChannel.toZipFile(): ZipFile { - return ZipFile.Builder().setSeekableByteChannel(this).get() -} diff --git a/core/common/src/main/java/tachiyomi/core/common/storage/UniFileExtensions.kt b/core/common/src/main/java/tachiyomi/core/common/storage/UniFileExtensions.kt index 257fe210d9..4b04ff4056 100644 --- a/core/common/src/main/java/tachiyomi/core/common/storage/UniFileExtensions.kt +++ b/core/common/src/main/java/tachiyomi/core/common/storage/UniFileExtensions.kt @@ -3,7 +3,6 @@ package tachiyomi.core.common.storage import android.content.Context import android.os.ParcelFileDescriptor import com.hippo.unifile.UniFile -import java.nio.channels.FileChannel val UniFile.extension: String? get() = name?.substringAfterLast('.') @@ -14,6 +13,5 @@ val UniFile.nameWithoutExtension: String? val UniFile.displayablePath: String get() = filePath ?: uri.toString() -fun UniFile.openReadOnlyChannel(context: Context): FileChannel { - return ParcelFileDescriptor.AutoCloseInputStream(context.contentResolver.openFileDescriptor(uri, "r")).channel -} +fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor = + context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: $displayablePath") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3e0bf6da11..1459648f41 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,8 +32,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" disklrucache = "com.jakewharton:disklrucache:2.0.2" unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc" -common-compress = "org.apache.commons:commons-compress:1.26.2" -junrar = "com.github.junrar:junrar:7.5.5" +libarchive = "me.zhanghai.android.libarchive:library:1.1.0" sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" } sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" } @@ -114,7 +113,6 @@ seeker = "io.github.2307vivek:seeker:1.2.2" truetypeparser = "io.github.yubyf:truetypeparser-light:2.1.4" [bundles] -archive = ["common-compress", "junrar"] acra = ["acra-http", "acra-scheduler"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"] js-engine = ["quickjs-android"] diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 4f1f053633..384cf0bb0e 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -685,7 +685,6 @@ Failed to load pages: %1$s No pages found Source not found - RARv5 format is not supported Updating library An update is already running Unable to open last read chapter diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index f7bccd9210..9922d3c755 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -12,7 +12,6 @@ kotlin { api(projects.i18n) implementation(libs.unifile) - implementation(libs.bundles.archive) } } val androidMain by getting { diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt index 9dfd46db59..04f03652d8 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/entries/manga/LocalMangaSource.kt @@ -16,14 +16,13 @@ import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream -import mihon.core.common.extensions.toZipFile +import mihon.core.common.archive.archiveReader import logcat.LogPriority import nl.adaptivity.xmlutil.core.AndroidXmlReader import nl.adaptivity.xmlutil.serialization.XML import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.storage.nameWithoutExtension -import tachiyomi.core.common.storage.openReadOnlyChannel import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat @@ -49,7 +48,6 @@ import java.text.SimpleDateFormat import java.util.Locale import java.util.concurrent.TimeUnit import kotlin.math.abs -import com.github.junrar.Archive as JunrarArchive actual class LocalMangaSource( private val context: Context, @@ -190,9 +188,7 @@ actual class LocalMangaSource( // Copy ComicInfo.xml from chapter archive to top level if found noXmlFile == null -> { - val chapterArchives = mangaDirFiles - .filter(ArchiveManga::isSupported) - .toList() + val chapterArchives = mangaDirFiles.filter(ArchiveManga::isSupported) val copiedFile = copyComicInfoFileFromArchive(chapterArchives, mangaDir) if (copiedFile != null) { @@ -215,26 +211,10 @@ actual class LocalMangaSource( private fun copyComicInfoFileFromArchive(chapterArchives: List, folder: UniFile): UniFile? { for (chapter in chapterArchives) { - when (Format.valueOf(chapter)) { - is Format.Zip -> { - chapter.openReadOnlyChannel(context).toZipFile().use { zip -> - zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile -> - zip.getInputStream(comicInfoFile).buffered().use { stream -> - return copyComicInfoFile(stream, folder) - } - } - } - } - is Format.Rar -> { - JunrarArchive(chapter.openInputStream()).use { rar -> - rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile -> - rar.getInputStream(comicInfoFile).buffered().use { stream -> - return copyComicInfoFile(stream, folder) - } - } - } + chapter.archiveReader(context).use { reader -> + reader.getInputStream(COMIC_INFO_FILE)?.use { stream -> + return copyComicInfoFile(stream, folder) } - else -> {} } } return null @@ -269,7 +249,7 @@ actual class LocalMangaSource( val chapters = fileSystem.getFilesInMangaDirectory(manga.url) // Only keep supported formats - .filter { it.isDirectory || ArchiveManga.isSupported(it) } + .filter { it.isDirectory || ArchiveManga.isSupported(it) || it.extension.equals("epub", true) } .map { chapterFile -> SChapter.create().apply { url = "${manga.url}/${chapterFile.name}" @@ -287,7 +267,7 @@ actual class LocalMangaSource( val format = Format.valueOf(chapterFile) if (format is Format.Epub) { - EpubFile(format.file.openReadOnlyChannel(context)).use { epub -> + EpubFile(format.file.archiveReader(context)).use { epub -> epub.fillMetadata(manga, this) } } @@ -364,51 +344,22 @@ actual class LocalMangaSource( entry?.let { coverManager.update(manga, it.openInputStream()) } } - is Format.Zip -> { - format.file.openReadOnlyChannel(context).toZipFile().use { zip -> - val entry = zip.entries.toList() - .sortedWith { f1, f2 -> - f1.name.compareToCaseInsensitiveNaturalOrder( - f2.name, - ) - } - .find { - !it.isDirectory && ImageUtil.isImage(it.name) { - zip.getInputStream( - it, - ) - } - } - - entry?.let { coverManager.update(manga, zip.getInputStream(it)) } - } - } - is Format.Rar -> { - JunrarArchive(format.file.openInputStream()).use { archive -> - val entry = archive.fileHeaders - .sortedWith { f1, f2 -> - f1.fileName.compareToCaseInsensitiveNaturalOrder( - f2.fileName, - ) - } - .find { - !it.isDirectory && ImageUtil.isImage(it.fileName) { - archive.getInputStream( - it, - ) - } - } - - entry?.let { coverManager.update(manga, archive.getInputStream(it)) } + is Format.Archive -> { + format.file.archiveReader(context).use { reader -> + val entry = reader.useEntries { entries -> + entries + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .find { it.isFile && ImageUtil.isImage(it.name) { reader.getInputStream(it.name)!! } } + } + + entry?.let { coverManager.update(manga, reader.getInputStream(it.name)!!) } } } is Format.Epub -> { - EpubFile(format.file.openReadOnlyChannel(context)).use { epub -> - val entry = epub.getImagesFromPages() - .firstOrNull() - ?.let { epub.getEntry(it) } + EpubFile(format.file.archiveReader(context)).use { epub -> + val entry = epub.getImagesFromPages().firstOrNull() - entry?.let { coverManager.update(manga, epub.getInputStream(it)) } + entry?.let { coverManager.update(manga, epub.getInputStream(it)!!) } } } } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt index 3a19de5a3a..c3559a856e 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt @@ -14,9 +14,9 @@ object ArchiveAnime { object ArchiveManga { - private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub") + private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "7z", "cb7", "tar", "cbt") fun isSupported(file: UniFile): Boolean { - return file.extension in SUPPORTED_ARCHIVE_TYPES + return file.extension?.lowercase() in SUPPORTED_ARCHIVE_TYPES } } diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt index 5b22e41e2b..58ca838f3f 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt @@ -2,25 +2,22 @@ package tachiyomi.source.local.io import com.hippo.unifile.UniFile import tachiyomi.core.common.storage.extension +import tachiyomi.source.local.io.ArchiveManga.isSupported as isArchiveSupported sealed interface Format { data class Directory(val file: UniFile) : Format - data class Zip(val file: UniFile) : Format - data class Rar(val file: UniFile) : Format + data class Archive(val file: UniFile) : Format data class Epub(val file: UniFile) : Format class UnknownFormatException : Exception() companion object { - fun valueOf(file: UniFile) = with(file) { - when { - isDirectory -> Directory(this) - extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this) - extension.equals("rar", true) || extension.equals("cbr", true) -> Rar(this) - extension.equals("epub", true) -> Epub(this) - else -> throw UnknownFormatException() - } + fun valueOf(file: UniFile) = when { + file.isDirectory -> Directory(file) + file.extension.equals("epub", true) -> Epub(file) + isArchiveSupported(file) -> Archive(file) + else -> throw UnknownFormatException() } } } From 637f159079d5f34679b33a061b177c7896ede631 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 22:11:53 +0200 Subject: [PATCH 53/74] Added configuration options to e-ink page flashes * Recommit for e-ink pref changes * Fixed state holder for flash interval * Detekt * Refactor suggested by Antsy * inverted currentDisplayRefresh check for early exit Co-authored-by: Maddie Witman <1882979+sirlag@users.noreply.github.com> --- .../settings/screen/SettingsReaderScreen.kt | 66 +++++++++++++++++-- .../presentation/reader/DisplayRefreshHost.kt | 60 ++++++++++++++--- .../reader/settings/GeneralSettingsPage.kt | 50 ++++++++++++++ .../ui/reader/setting/ReaderPreferences.kt | 14 ++++ .../moko-resources/base/plurals.xml | 4 ++ .../moko-resources/base/strings.xml | 7 ++ 6 files changed, 188 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index 56dc2dbfc0..e88d66757f 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -14,6 +14,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableMap import tachiyomi.i18n.MR +import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt @@ -61,12 +62,8 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPref.pageTransitions(), title = stringResource(MR.strings.pref_page_transitions), ), - Preference.PreferenceItem.SwitchPreference( - pref = readerPref.flashOnPageChange(), - title = stringResource(MR.strings.pref_flash_page), - subtitle = stringResource(MR.strings.pref_flash_page_summ), - ), getDisplayGroup(readerPreferences = readerPref), + getEInkGroup(readerPreferences = readerPref), getReadingGroup(readerPreferences = readerPref), getPagedGroup(readerPreferences = readerPref), getWebtoonGroup(readerPreferences = readerPref), @@ -122,6 +119,65 @@ object SettingsReaderScreen : SearchableSettings { ) } + @Composable + private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { + val flashPageState by readerPreferences.flashOnPageChange().collectAsState() + + val flashMillisPref = readerPreferences.flashDurationMillis() + val flashMillis by flashMillisPref.collectAsState() + + val flashIntervalPref = readerPreferences.flashPageInterval() + val flashInterval by flashIntervalPref.collectAsState() + + val flashColorPref = readerPreferences.flashColor() + + return Preference.PreferenceGroup( + title = "E-Ink", + preferenceItems = persistentListOf( + Preference.PreferenceItem.SwitchPreference( + pref = readerPreferences.flashOnPageChange(), + title = stringResource(MR.strings.pref_flash_page), + subtitle = stringResource(MR.strings.pref_flash_page_summ), + ), + Preference.PreferenceItem.SliderPreference( + value = flashMillis / ReaderPreferences.MILLI_CONVERSION, + min = 1, + max = 15, + title = stringResource(MR.strings.pref_flash_duration), + subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), + onValueChanged = { + flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) + true + }, + enabled = flashPageState, + ), + Preference.PreferenceItem.SliderPreference( + value = flashInterval, + min = 1, + max = 10, + title = stringResource(MR.strings.pref_flash_page_interval), + subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), + onValueChanged = { + flashIntervalPref.set(it) + true + }, + enabled = flashPageState, + ), + Preference.PreferenceItem.ListPreference( + pref = flashColorPref, + title = stringResource(MR.strings.pref_flash_with), + entries = persistentMapOf( + ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black), + ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white), + ReaderPreferences.FlashColor.WHITE_BLACK + to stringResource(MR.strings.pref_flash_style_white_black), + ), + enabled = flashPageState, + ), + ), + ) + } + @Composable private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { return Preference.PreferenceGroup( diff --git a/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt b/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt index 82d3cad0fa..ecf26119d0 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt @@ -7,19 +7,42 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import kotlinx.coroutines.delay -import kotlin.time.Duration.Companion.seconds +import tachiyomi.presentation.core.util.collectAsState +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import kotlin.time.Duration.Companion.milliseconds @Stable class DisplayRefreshHost { internal var currentDisplayRefresh by mutableStateOf(false) + private val readerPreferences = Injekt.get() + + internal val flashMillis = readerPreferences.flashDurationMillis() + internal val flashMode = readerPreferences.flashColor() + + internal val flashIntervalPref = readerPreferences.flashPageInterval() + + // Internal State for Flash + private var flashInterval = flashIntervalPref.get() + private var timesCalled = 0 fun flash() { - currentDisplayRefresh = true + if (timesCalled % flashInterval == 0) { + currentDisplayRefresh = true + } + timesCalled += 1 + } + + fun setInterval(interval: Int) { + flashInterval = interval + timesCalled = 0 } } @@ -29,18 +52,39 @@ fun DisplayRefreshHost( modifier: Modifier = Modifier, ) { val currentDisplayRefresh = hostState.currentDisplayRefresh + val refreshDuration by hostState.flashMillis.collectAsState() + val flashMode by hostState.flashMode.collectAsState() + val flashInterval by hostState.flashIntervalPref.collectAsState() + + var currentColor by remember { mutableStateOf(null) } + LaunchedEffect(currentDisplayRefresh) { - if (currentDisplayRefresh) { - delay(1.5.seconds) - hostState.currentDisplayRefresh = false + if (!currentDisplayRefresh) { + currentColor = null + return@LaunchedEffect + } + + val refreshDurationHalf = refreshDuration.milliseconds / 2 + currentColor = if (flashMode == ReaderPreferences.FlashColor.BLACK) { + Color.Black + } else { + Color.White + } + delay(refreshDurationHalf) + if (flashMode == ReaderPreferences.FlashColor.WHITE_BLACK) { + currentColor = Color.Black } + delay(refreshDurationHalf) + hostState.currentDisplayRefresh = false + } + + LaunchedEffect(flashInterval) { + hostState.setInterval(flashInterval) } Canvas( modifier = modifier.fillMaxSize(), ) { - if (currentDisplayRefresh) { - drawRect(Color.Black) - } + currentColor?.let { drawRect(it) } } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt index 2f2832feb5..0bb2da6cc0 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt @@ -5,10 +5,13 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.SettingsChipRow +import tachiyomi.presentation.core.components.SliderItem +import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState @@ -19,9 +22,27 @@ private val themes = listOf( MR.strings.automatic_background to 3, ) +private val flashColors = listOf( + MR.strings.pref_flash_style_black to ReaderPreferences.FlashColor.BLACK, + MR.strings.pref_flash_style_white to ReaderPreferences.FlashColor.WHITE, + MR.strings.pref_flash_style_white_black to ReaderPreferences.FlashColor.WHITE_BLACK, +) + @Composable internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { val readerTheme by screenModel.preferences.readerTheme().collectAsState() + + val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState() + + val flashMillisPref = screenModel.preferences.flashDurationMillis() + val flashMillis by flashMillisPref.collectAsState() + + val flashIntervalPref = screenModel.preferences.flashPageInterval() + val flashInterval by flashIntervalPref.collectAsState() + + val flashColorPref = screenModel.preferences.flashColor() + val flashColor by flashColorPref.collectAsState() + SettingsChipRow(MR.strings.pref_reader_theme) { themes.map { (labelRes, value) -> FilterChip( @@ -73,4 +94,33 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { label = stringResource(MR.strings.pref_flash_page), pref = screenModel.preferences.flashOnPageChange(), ) + if (flashPageState) { + SliderItem( + value = flashMillis / ReaderPreferences.MILLI_CONVERSION, + label = stringResource(MR.strings.pref_flash_duration), + valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis), + onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) }, + min = 1, + max = 15, + ) + SliderItem( + value = flashInterval, + label = stringResource(MR.strings.pref_flash_page_interval), + valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval), + onChange = { + flashIntervalPref.set(it) + }, + min = 1, + max = 10, + ) + SettingsChipRow(MR.strings.pref_flash_with) { + flashColors.map { (labelRes, value) -> + FilterChip( + selected = flashColor == value, + onClick = { flashColorPref.set(value) }, + label = { Text(stringResource(labelRes)) }, + ) + } + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index 75292153bc..58e193410d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -17,6 +17,12 @@ class ReaderPreferences( fun flashOnPageChange() = preferenceStore.getBoolean("pref_reader_flash", false) + fun flashDurationMillis() = preferenceStore.getInt("pref_reader_flash_duration", MILLI_CONVERSION) + + fun flashPageInterval() = preferenceStore.getInt("pref_reader_flash_interval", 1) + + fun flashColor() = preferenceStore.getEnum("pref_reader_flash_mode", FlashColor.BLACK) + fun doubleTapAnimSpeed() = preferenceStore.getInt("pref_double_tap_anim_speed", 500) fun showPageNumber() = preferenceStore.getBoolean("pref_show_page_number_key", true) @@ -168,6 +174,12 @@ class ReaderPreferences( // endregion + enum class FlashColor { + BLACK, + WHITE, + WHITE_BLACK + } + enum class TappingInvertMode( val titleRes: StringResource, val shouldInvertHorizontal: Boolean = false, @@ -194,6 +206,8 @@ class ReaderPreferences( const val WEBTOON_PADDING_MIN = 0 const val WEBTOON_PADDING_MAX = 25 + const val MILLI_CONVERSION = 100 + val TapZones = listOf( MR.strings.label_default, MR.strings.l_nav, diff --git a/i18n/src/commonMain/moko-resources/base/plurals.xml b/i18n/src/commonMain/moko-resources/base/plurals.xml index 501e4140dd..ff06f36b30 100644 --- a/i18n/src/commonMain/moko-resources/base/plurals.xml +++ b/i18n/src/commonMain/moko-resources/base/plurals.xml @@ -32,6 +32,10 @@ %d day %d days + + 1 page + %1$s pages + Missing %1$s item Missing %1$s items diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 384cf0bb0e..dc271d2361 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -328,6 +328,13 @@ Animate page transitions Flash on page change Reduces ghosting on e-ink displays + Flash duration + %1$s ms + Flash every + Flash with + Black + White + White and Black Double tap animation speed Show page number Show reading mode From 16a239f98c5bd7ce63ca67ac0ec48766c33ea307 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 03:42:32 +0600 Subject: [PATCH 54/74] fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2024.06.00-alpha01 (#957) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 2d41bf5589a3f26e549ecc7ddd5adafc17d503f4) --- gradle/compose.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index c36eb21cbe..74ae656e98 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compose-bom = "2024.05.00-alpha03" +compose-bom = "2024.06.00-alpha01" accompanist = "0.35.1-alpha" [libraries] From bcf72c919fb36dc75f09863aef6dd41314245575 Mon Sep 17 00:00:00 2001 From: CrepeTF <70870719+CrepeTF@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:59:32 +0100 Subject: [PATCH 55/74] Theme fixes (#963) * Fix theme issue with download progress indicator * Fix theme issue with download progress indicator + better contrast (cherry picked from commit e132cc405f23e18dd8d73626730821eae9051149) --- .../presentation/theme/colorscheme/NordColorScheme.kt | 6 +++--- .../src/main/res/values-night/color_lavender.xml | 2 +- .../src/main/res/values-night/colors_midnightdusk.xml | 2 +- presentation-core/src/main/res/values-night/colors_nord.xml | 2 +- presentation-core/src/main/res/values-night/colors_tako.xml | 2 +- presentation-core/src/main/res/values/color_lavender.xml | 2 +- presentation-core/src/main/res/values/colors_nord.xml | 2 +- presentation-core/src/main/res/values/colors_tako.xml | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt index 2e89b9ea4b..84fe7b49fa 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt @@ -19,8 +19,8 @@ internal object NordColorScheme : BaseColorScheme() { inversePrimary = Color(0xFF397E91), secondary = Color(0xFF81A1C1), // Unread badge onSecondary = Color(0xFF2E3440), // Unread badge text - secondaryContainer = Color(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining) - onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon + secondaryContainer = Color(0xFF506275), // Navigation bar selector pill & progress indicator (remaining) + onSecondaryContainer = Color(0xFF88C0D0), // Navigation bar selector icon tertiary = Color(0xFF5E81AC), // Downloaded badge onTertiary = Color(0xFF000000), // Downloaded badge text tertiaryContainer = Color(0xFF5E81AC), @@ -54,7 +54,7 @@ internal object NordColorScheme : BaseColorScheme() { inversePrimary = Color(0xFF8CA8CD), secondary = Color(0xFF81A1C1), // Unread badge onSecondary = Color(0xFF2E3440), // Unread badge text - secondaryContainer = Color(0xFF81A1C1), // Navigation bar selector pill & progress indicator (remaining) + secondaryContainer = Color(0xFF91B4D7), // Navigation bar selector pill & progress indicator (remaining) onSecondaryContainer = Color(0xFF2E3440), // Navigation bar selector icon tertiary = Color(0xFF88C0D0), // Downloaded badge onTertiary = Color(0xFF2E3440), // Downloaded badge text diff --git a/presentation-core/src/main/res/values-night/color_lavender.xml b/presentation-core/src/main/res/values-night/color_lavender.xml index d76e55486e..f9d558025a 100644 --- a/presentation-core/src/main/res/values-night/color_lavender.xml +++ b/presentation-core/src/main/res/values-night/color_lavender.xml @@ -17,7 +17,7 @@ #111129 #A177FF #111129 - #A177FF + #423271 #111129 #5E25E1 #E8E8E8 diff --git a/presentation-core/src/main/res/values-night/colors_midnightdusk.xml b/presentation-core/src/main/res/values-night/colors_midnightdusk.xml index d107312aa3..f10e846622 100644 --- a/presentation-core/src/main/res/values-night/colors_midnightdusk.xml +++ b/presentation-core/src/main/res/values-night/colors_midnightdusk.xml @@ -17,7 +17,7 @@ #FFFFFF #F02475 #FFFFFF - #F02475 + #66183C #FFFFFF #55971C #FFFFFF diff --git a/presentation-core/src/main/res/values-night/colors_nord.xml b/presentation-core/src/main/res/values-night/colors_nord.xml index f9ab8ce50c..058d52a4a6 100644 --- a/presentation-core/src/main/res/values-night/colors_nord.xml +++ b/presentation-core/src/main/res/values-night/colors_nord.xml @@ -7,7 +7,7 @@ #2E3440 #81A1C1 #2E3440 - #81A1C1 + #506275 #2E3440 #5E81AC #000000 diff --git a/presentation-core/src/main/res/values-night/colors_tako.xml b/presentation-core/src/main/res/values-night/colors_tako.xml index 907486c60b..85f898c32b 100644 --- a/presentation-core/src/main/res/values-night/colors_tako.xml +++ b/presentation-core/src/main/res/values-night/colors_tako.xml @@ -15,7 +15,7 @@ #38294E #F3B375 #38294E - #F3B375 + #5C4D4B #38294E #F3B375 #38294E diff --git a/presentation-core/src/main/res/values/color_lavender.xml b/presentation-core/src/main/res/values/color_lavender.xml index f6cfda65c2..02d6274e35 100644 --- a/presentation-core/src/main/res/values/color_lavender.xml +++ b/presentation-core/src/main/res/values/color_lavender.xml @@ -16,7 +16,7 @@ #EDE2FF #7B46AF #EDE2FF - #7B46AF + #C9B0E6 #EDE2FF #EDE2FF #7B46AF diff --git a/presentation-core/src/main/res/values/colors_nord.xml b/presentation-core/src/main/res/values/colors_nord.xml index 7aab3eb43c..b04ae3eecf 100644 --- a/presentation-core/src/main/res/values/colors_nord.xml +++ b/presentation-core/src/main/res/values/colors_nord.xml @@ -8,7 +8,7 @@ #000000 #81A1C1 #2E3440 - #81A1C1 + #91B4D7 #2E3440 #88C0D0 #2E3440 diff --git a/presentation-core/src/main/res/values/colors_tako.xml b/presentation-core/src/main/res/values/colors_tako.xml index 0976d1b14b..6a002cd2cc 100644 --- a/presentation-core/src/main/res/values/colors_tako.xml +++ b/presentation-core/src/main/res/values/colors_tako.xml @@ -17,7 +17,7 @@ #F3B375 #66577E #F3B375 - #66577E + #C8BED0 #F3B375 #F3B375 #574360 From c31283281297143a3ae801c4ec567cd2fae8be0f Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 22:27:00 +0200 Subject: [PATCH 56/74] =?UTF-8?q?fix(deps):=20update=20dependency=20io.coi?= =?UTF-8?q?l-kt.coil3:coil-bom=20to=20v3.0.0-alph=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …a07 * fix(deps): update dependency io.coil-kt.coil3:coil-bom to v3.0.0-alpha07 * Fix build --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 2 ++ .../presentation/entries/anime/components/AnimeCoverDialog.kt | 1 + .../presentation/entries/manga/components/MangaCoverDialog.kt | 1 + .../eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt | 4 ++-- .../data/library/anime/AnimeLibraryUpdateNotifier.kt | 1 + .../data/library/manga/MangaLibraryUpdateNotifier.kt | 1 + .../tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt | 1 + .../tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt | 1 + .../java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt | 1 + .../kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt | 1 + .../eu/kanade/tachiyomi/util/system/DrawableExtensions.kt | 2 +- gradle/androidx.versions.toml | 3 +++ gradle/libs.versions.toml | 2 +- .../widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt | 1 + .../widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt | 1 + 15 files changed, 19 insertions(+), 4 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1d6e353267..b2e4166479 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -169,6 +169,8 @@ dependencies { implementation(compose.ui.util) implementation(compose.accompanist.systemuicontroller) + implementation(androidx.interpolator) + implementation(androidx.paging.runtime) implementation(androidx.paging.compose) diff --git a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeCoverDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeCoverDialog.kt index 06ac19b41b..901235c98f 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeCoverDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/anime/components/AnimeCoverDialog.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.view.updatePadding +import coil3.asDrawable import coil3.imageLoader import coil3.request.CachePolicy import coil3.request.ImageRequest diff --git a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaCoverDialog.kt b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaCoverDialog.kt index a7ff5a563a..eabe209ce2 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaCoverDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/manga/components/MangaCoverDialog.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import androidx.core.view.updatePadding +import coil3.asDrawable import coil3.imageLoader import coil3.request.CachePolicy import coil3.request.ImageRequest diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt index 2929f4da3f..6403f760bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.coil import android.graphics.Bitmap import coil3.ImageLoader -import coil3.asCoilImage +import coil3.asImage import coil3.decode.DecodeResult import coil3.decode.DecodeUtils import coil3.decode.Decoder @@ -58,7 +58,7 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti } return DecodeResult( - image = bitmap.asCoilImage(), + image = bitmap.asImage(), isSampled = sampleSize > 1, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt index 2f9a9f6491..63212ede39 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/anime/AnimeLibraryUpdateNotifier.kt @@ -9,6 +9,7 @@ import android.graphics.BitmapFactory import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import coil3.asDrawable import coil3.imageLoader import coil3.request.ImageRequest import coil3.request.transformations diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt index 3dad875db6..7990d13e58 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/manga/MangaLibraryUpdateNotifier.kt @@ -9,6 +9,7 @@ import android.graphics.BitmapFactory import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import coil3.asDrawable import coil3.imageLoader import coil3.request.ImageRequest import coil3.request.transformations diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt index 6d18f2a58b..bb4177cc55 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeCoverScreenModel.kt @@ -5,6 +5,7 @@ import android.net.Uri import androidx.compose.material3.SnackbarHostState import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope +import coil3.asDrawable import coil3.imageLoader import coil3.request.ImageRequest import coil3.size.Size diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt index 24cdd70504..812693f35e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaCoverScreenModel.kt @@ -5,6 +5,7 @@ import android.net.Uri import androidx.compose.material3.SnackbarHostState import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope +import coil3.asDrawable import coil3.imageLoader import coil3.request.ImageRequest import coil3.size.Size diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 373ed97a83..6b08dfeb30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -4,6 +4,7 @@ import android.content.Context import android.graphics.Bitmap import android.net.Uri import androidx.core.app.NotificationCompat +import coil3.asDrawable import coil3.imageLoader import coil3.request.CachePolicy import coil3.request.ImageRequest diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index 17674cf3ad..a209f3c3b1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -19,6 +19,7 @@ import androidx.appcompat.widget.AppCompatImageView import androidx.core.os.postDelayed import androidx.core.view.isVisible import coil3.BitmapImage +import coil3.asDrawable import coil3.dispose import coil3.imageLoader import coil3.request.CachePolicy diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt index 2877768af1..872552ef9a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/DrawableExtensions.kt @@ -4,7 +4,7 @@ import android.graphics.Bitmap import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import androidx.core.graphics.drawable.toBitmap -import coil3.gif.ScaleDrawable +import coil3.size.ScaleDrawable fun Drawable.getBitmapOrNull(): Bitmap? = when (this) { is BitmapDrawable -> bitmap diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index f61ed42941..bd356fc119 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -2,6 +2,7 @@ agp_version = "8.5.0" lifecycle_version = "2.8.2" paging_version = "3.3.0" +interpolator_version = "1.0.0" [libraries] gradle = { module = "com.android.tools.build:gradle", version.ref = "agp_version" } @@ -26,6 +27,8 @@ workmanager = "androidx.work:work-runtime:2.9.0" paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } +interpolator = { group = "androidx.interpolator", name = "interpolator", version.ref = "interpolator_version" } + benchmark-macro = "androidx.benchmark:benchmark-macro-junit4:1.2.4" test-ext = "androidx.test.ext:junit-ktx:1.2.1" test-espresso-core = "androidx.test.espresso:espresso-core:3.6.1" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1459648f41..3a64c2e781 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,7 @@ preferencektx = "androidx.preference:preference-ktx:1.2.1" injekt-core = "com.github.inorichi.injekt:injekt-core:65b0440" -coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.0-alpha06" } +coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.0-alpha07" } coil-core = { module = "io.coil-kt.coil3:coil" } coil-gif = { module = "io.coil-kt.coil3:coil-gif" } coil-compose = { module = "io.coil-kt.coil3:coil-compose" } diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt index a2cccb91f7..63130ed7ed 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/anime/BaseAnimeUpdatesGridGlanceWidget.kt @@ -22,6 +22,7 @@ import androidx.glance.layout.fillMaxSize import androidx.glance.layout.padding import androidx.glance.unit.ColorProvider import coil3.annotation.ExperimentalCoilApi +import coil3.asDrawable import coil3.executeBlocking import coil3.imageLoader import coil3.request.CachePolicy diff --git a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt index 6fa0b6c073..fadc144be5 100644 --- a/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt +++ b/presentation-widget/src/main/java/tachiyomi/presentation/widget/entries/manga/BaseMangaUpdatesGridGlanceWidget.kt @@ -22,6 +22,7 @@ import androidx.glance.layout.fillMaxSize import androidx.glance.layout.padding import androidx.glance.unit.ColorProvider import coil3.annotation.ExperimentalCoilApi +import coil3.asDrawable import coil3.executeBlocking import coil3.imageLoader import coil3.request.CachePolicy From 22c46273f975635e2699e0df03639bc462050014 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 22:29:41 +0200 Subject: [PATCH 57/74] Add safeguard to prevent ArchiveInputStream from being closed twice * fix: Add safeguard to prevent ArchiveInputStream from being closed twice * detekt * lint: Make detekt happy --------- Co-authored-by: Ahmad Ansori Palembani <46041660+null2264@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../mihon/core/common/archive/ArchiveInputStream.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt b/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt index a9bb878791..1499867c83 100644 --- a/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt +++ b/core/common/src/main/java/mihon/core/common/archive/ArchiveInputStream.kt @@ -5,8 +5,14 @@ import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveException import java.io.InputStream import java.nio.ByteBuffer +import kotlin.concurrent.Volatile class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { + private val lock = Any() + + @Volatile + private var isClosed = false + private val archive = Archive.readNew() init { @@ -41,6 +47,11 @@ class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { } override fun close() { + synchronized(lock) { + if (isClosed) return + isClosed = true + } + Archive.readFree(archive) } From 35732fea4d58131d5a1f7699f83d444c553549b9 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 22:30:24 +0200 Subject: [PATCH 58/74] Update image-decoder revision Co-authored-by: WerctFourth <88225220+WerctFourth@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3a64c2e781..749314293a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -49,7 +49,7 @@ coil-compose = { module = "io.coil-kt.coil3:coil-compose" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp" } subsamplingscaleimageview = "com.github.tachiyomiorg:subsampling-scale-image-view:b8e1b0ed2b" -image-decoder = "com.github.tachiyomiorg:image-decoder:e08e9be535" +image-decoder = "com.github.tachiyomiorg:image-decoder:41c059e540" natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1" From 0adc11dd71c813e55d4e8dd81db8fa90115ca507 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 01:58:48 +0600 Subject: [PATCH 59/74] fix(deps): update lifecycle.version to v2.8.3 (#972) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 77db8873f6753cc9db8f67b39d53685563380cc6) --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index bd356fc119..0380c9597f 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,6 +1,6 @@ [versions] agp_version = "8.5.0" -lifecycle_version = "2.8.2" +lifecycle_version = "2.8.3" paging_version = "3.3.0" interpolator_version = "1.0.0" From d59fa3fcf3010917269f60f6b603e5c5442d06da Mon Sep 17 00:00:00 2001 From: CrepeTF <70870719+CrepeTF@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:52:55 +0100 Subject: [PATCH 60/74] Correct tako variable colours (#976) (cherry picked from commit 75b5d966018aa917f57adf37370088a51e4914b2) --- presentation-core/src/main/res/values-night/colors_tako.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/presentation-core/src/main/res/values-night/colors_tako.xml b/presentation-core/src/main/res/values-night/colors_tako.xml index 85f898c32b..63c2db1ce0 100644 --- a/presentation-core/src/main/res/values-night/colors_tako.xml +++ b/presentation-core/src/main/res/values-night/colors_tako.xml @@ -15,8 +15,8 @@ #38294E #F3B375 #38294E - #5C4D4B - #38294E + #F3B375 + #5C4D4B #F3B375 #38294E #66577E From 2ccd3ffc8f4f4036c258c8aa05c3fedf47175daa Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 22:35:31 +0200 Subject: [PATCH 61/74] Smart Update Dialog Tweak * Smart Update Dialog Fix * Build Fail Change 1 * Commit Suggested Change Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * Build Fail Change 2 --------- Co-authored-by: Roshan Varughese <40583749+Animeboynz@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../presentation/entries/components/ItemsDialogs.kt | 13 +++++++++++-- i18n/src/commonMain/moko-resources/base/strings.xml | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/entries/components/ItemsDialogs.kt b/app/src/main/java/eu/kanade/presentation/entries/components/ItemsDialogs.kt index 4ea417e903..20906d6b8d 100644 --- a/app/src/main/java/eu/kanade/presentation/entries/components/ItemsDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/entries/components/ItemsDialogs.kt @@ -110,9 +110,18 @@ fun SetIntervalDialog( ), ), ) - - Spacer(Modifier.height(MaterialTheme.padding.small)) + } else { + Text( + stringResource( + if (isManga) { + MR.strings.manga_interval_expected_update_null + } else { + MR.strings.anime_interval_expected_update_null + }, + ), + ) } + Spacer(Modifier.height(MaterialTheme.padding.small)) if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) { Text(stringResource(MR.strings.manga_interval_custom_amount)) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index dc271d2361..dc708c95a8 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -612,6 +612,8 @@ New chapters predicted to be released in around %1$s, checking around every %2$s. New episodes predicted to be released in around %1$s, checking around every %2$s. + This manga is either completed, or there is no predicted release date. + This anime is either completed, or there is no predicted release date. Soon Custom update frequency: Downloading (%1$d/%2$d) From 2ff66d7f1d49ee85166cedbfb278f6daa8642297 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 22:39:54 +0200 Subject: [PATCH 62/74] Make global search "Has result" sticky Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../domain/source/service/SourcePreferences.kt | 5 +++++ .../source/globalsearch/AnimeSearchScreenModel.kt | 13 ++++++++++++- .../source/globalsearch/MangaSearchScreenModel.kt | 13 ++++++++++++- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt index 5385716786..f910b53db7 100644 --- a/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt +++ b/app/src/main/java/eu/kanade/domain/source/service/SourcePreferences.kt @@ -46,6 +46,11 @@ class SourcePreferences( emptySet(), ) + fun globalSearchFilterState() = preferenceStore.getBoolean( + Preference.appStateKey("has_filters_toggle_state"), + false, + ) + // Mixture Sources fun disabledAnimeSources() = preferenceStore.getStringSet("hidden_anime_catalogues", emptySet()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt index 99c724c75d..d881945d1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/anime/source/globalsearch/AnimeSearchScreenModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.produceState import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.entries.anime.model.toDomainAnime import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.util.ioCoroutineScope @@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import tachiyomi.core.common.preference.toggle import tachiyomi.domain.entries.anime.interactor.GetAnime import tachiyomi.domain.entries.anime.interactor.NetworkToLocalAnime import tachiyomi.domain.entries.anime.model.Anime @@ -38,6 +40,7 @@ abstract class AnimeSearchScreenModel( private val extensionManager: AnimeExtensionManager = Injekt.get(), private val networkToLocalAnime: NetworkToLocalAnime = Injekt.get(), private val getAnime: GetAnime = Injekt.get(), + private val preferences: SourcePreferences = Injekt.get(), ) : StateScreenModel(initialState) { private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher() @@ -60,6 +63,14 @@ abstract class AnimeSearchScreenModel( ) } + init { + screenModelScope.launch { + preferences.globalSearchFilterState().changes().collectLatest { state -> + mutableState.update { it.copy(onlyShowHasResults = state) } + } + } + } + @Composable fun getAnime(initialAnime: Anime): androidx.compose.runtime.State { return produceState(initialValue = initialAnime) { @@ -107,7 +118,7 @@ abstract class AnimeSearchScreenModel( } fun toggleFilterResults() { - mutableState.update { it.copy(onlyShowHasResults = !it.onlyShowHasResults) } + preferences.globalSearchFilterState().toggle() } fun search() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt index d3ad08469e..ce2e7322db 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/manga/source/globalsearch/MangaSearchScreenModel.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.produceState import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.entries.manga.model.toDomainManga import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.presentation.util.ioCoroutineScope @@ -23,6 +24,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import tachiyomi.core.common.preference.toggle import tachiyomi.domain.entries.manga.interactor.GetManga import tachiyomi.domain.entries.manga.interactor.NetworkToLocalManga import tachiyomi.domain.entries.manga.model.Manga @@ -38,6 +40,7 @@ abstract class MangaSearchScreenModel( private val extensionManager: MangaExtensionManager = Injekt.get(), private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val getManga: GetManga = Injekt.get(), + private val preferences: SourcePreferences = Injekt.get(), ) : StateScreenModel(initialState) { private val coroutineDispatcher = Executors.newFixedThreadPool(5).asCoroutineDispatcher() @@ -60,6 +63,14 @@ abstract class MangaSearchScreenModel( ) } + init { + screenModelScope.launch { + preferences.globalSearchFilterState().changes().collectLatest { state -> + mutableState.update { it.copy(onlyShowHasResults = state) } + } + } + } + @Composable fun getManga(initialManga: Manga): androidx.compose.runtime.State { return produceState(initialValue = initialManga) { @@ -107,7 +118,7 @@ abstract class MangaSearchScreenModel( } fun toggleFilterResults() { - mutableState.update { it.copy(onlyShowHasResults = !it.onlyShowHasResults) } + preferences.globalSearchFilterState().toggle() } fun search() { From 38ba45b6e753e68c463711bd30185fa32752746b Mon Sep 17 00:00:00 2001 From: Secozzi Date: Fri, 12 Jul 2024 23:15:47 +0200 Subject: [PATCH 63/74] Observe tracker login state instead of fetching once * Observe tracker login state instead of fetching once * Review changes Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../anime/AnimeLibrarySettingsDialog.kt | 15 +++---- .../manga/MangaLibrarySettingsDialog.kt | 15 +++---- .../more/settings/PreferenceItem.kt | 20 ++++------ .../tachiyomi/data/track/BaseTracker.kt | 11 +++++ .../eu/kanade/tachiyomi/data/track/Tracker.kt | 3 ++ .../tachiyomi/data/track/TrackerManager.kt | 9 +++++ .../tachiyomi/ui/entries/anime/AnimeScreen.kt | 2 +- .../ui/entries/anime/AnimeScreenModel.kt | 40 +++++++++++-------- .../anime/track/AnimeTrackInfoDialog.kt | 4 +- .../tachiyomi/ui/entries/manga/MangaScreen.kt | 2 +- .../ui/entries/manga/MangaScreenModel.kt | 34 ++++++++++------ .../manga/track/MangaTrackInfoDialog.kt | 4 +- .../library/anime/AnimeLibraryScreenModel.kt | 35 ++++++++-------- .../anime/AnimeLibrarySettingsScreenModel.kt | 13 ++++-- .../library/manga/MangaLibraryScreenModel.kt | 35 ++++++++-------- .../manga/MangaLibrarySettingsScreenModel.kt | 13 ++++-- .../ui/stats/anime/AnimeStatsScreenModel.kt | 2 +- .../ui/stats/manga/MangaStatsScreenModel.kt | 2 +- .../main/java/eu/kanade/test/DummyTracker.kt | 3 ++ 19 files changed, 161 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt index 5059ee4bfc..3172cf6f93 100644 --- a/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/anime/AnimeLibrarySettingsDialog.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.FilterChip import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -125,7 +126,7 @@ private fun ColumnScope.FilterPage( ) } - val trackers = remember { screenModel.trackers } + val trackers by screenModel.trackersFlow.collectAsState() when (trackers.size) { 0 -> { // No trackers @@ -162,15 +163,15 @@ private fun ColumnScope.SortPage( category: Category?, screenModel: AnimeLibrarySettingsScreenModel, ) { + val trackers by screenModel.trackersFlow.collectAsState() val sortingMode = category.sort.type val sortDescending = !category.sort.isAscending - val trackerSortOption = - if (screenModel.trackers.isEmpty()) { - emptyList() - } else { - listOf(MR.strings.action_sort_tracker_score to AnimeLibrarySort.Type.TrackerMean) - } + val trackerSortOption = if (trackers.isEmpty()) { + emptyList() + } else { + listOf(MR.strings.action_sort_tracker_score to AnimeLibrarySort.Type.TrackerMean) + } listOf( MR.strings.action_sort_alpha to AnimeLibrarySort.Type.Alphabetical, diff --git a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt index 9e0a59f431..d3ecf57858 100644 --- a/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/manga/MangaLibrarySettingsDialog.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.FilterChip import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -125,7 +126,7 @@ private fun ColumnScope.FilterPage( ) } - val trackers = remember { screenModel.trackers } + val trackers by screenModel.trackersFlow.collectAsState() when (trackers.size) { 0 -> { // No trackers @@ -162,15 +163,15 @@ private fun ColumnScope.SortPage( category: Category?, screenModel: MangaLibrarySettingsScreenModel, ) { + val trackers by screenModel.trackersFlow.collectAsState() val sortingMode = category.sort.type val sortDescending = !category.sort.isAscending - val trackerSortOption = - if (screenModel.trackers.isEmpty()) { - emptyList() - } else { - listOf(MR.strings.action_sort_tracker_score to MangaLibrarySort.Type.TrackerMean) - } + val trackerSortOption = if (trackers.isEmpty()) { + emptyList() + } else { + listOf(MR.strings.action_sort_tracker_score to MangaLibrarySort.Type.TrackerMean) + } listOf( MR.strings.action_sort_alpha to MangaLibrarySort.Type.Alphabetical, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt index e428b2288c..9af40d8f1d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt @@ -7,12 +7,12 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkVertically import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.structuralEqualityPolicy import androidx.compose.ui.unit.dp -import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.presentation.more.settings.widget.EditTextPreferenceWidget import eu.kanade.presentation.more.settings.widget.InfoWidget import eu.kanade.presentation.more.settings.widget.ListPreferenceWidget @@ -23,8 +23,6 @@ import eu.kanade.presentation.more.settings.widget.TrackingPreferenceWidget import kotlinx.coroutines.launch import tachiyomi.presentation.core.components.SliderItem import tachiyomi.presentation.core.util.collectAsState -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get val LocalPreferenceHighlighted = compositionLocalOf(structuralEqualityPolicy()) { false } val LocalPreferenceMinHeight = compositionLocalOf(structuralEqualityPolicy()) { 56.dp } @@ -172,16 +170,14 @@ internal fun PreferenceItem( ) } is Preference.PreferenceItem.TrackerPreference -> { - val uName by Injekt.get() - .trackUsername(item.tracker) - .collectAsState() - item.tracker.run { - TrackingPreferenceWidget( - tracker = this, - checked = uName.isNotEmpty(), - onClick = { if (isLoggedIn) item.logout() else item.login() }, - ) + val isLoggedIn by item.tracker.let { tracker -> + tracker.isLoggedInFlow.collectAsState(tracker.isLoggedIn) } + TrackingPreferenceWidget( + tracker = item.tracker, + checked = isLoggedIn, + onClick = { if (isLoggedIn) item.logout() else item.login() }, + ) } is Preference.PreferenceItem.InfoPreference -> { InfoWidget(text = item.title) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt index 7c126e9691..3eb9978d02 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/BaseTracker.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.data.track import androidx.annotation.CallSuper import eu.kanade.domain.track.service.TrackPreferences import eu.kanade.tachiyomi.network.NetworkHelper +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import okhttp3.OkHttpClient import uy.kohesive.injekt.injectLazy @@ -29,6 +31,15 @@ abstract class BaseTracker( get() = getUsername().isNotEmpty() && getPassword().isNotEmpty() + override val isLoggedInFlow: Flow by lazy { + combine( + trackPreferences.trackUsername(this).changes(), + trackPreferences.trackPassword(this).changes(), + ) { username, password -> + username.isNotEmpty() && password.isNotEmpty() + } + } + override fun getUsername() = trackPreferences.trackUsername(this).get() override fun getPassword() = trackPreferences.trackPassword(this).get() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt index 0f08150436..67ec1886a9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt @@ -5,6 +5,7 @@ import androidx.annotation.ColorInt import androidx.annotation.DrawableRes import dev.icerock.moko.resources.StringResource import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.Flow import okhttp3.OkHttpClient interface Tracker { @@ -37,6 +38,8 @@ interface Tracker { val isLoggedIn: Boolean + val isLoggedInFlow: Flow + fun getUsername(): String fun getPassword(): String diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt index b4fa422e36..08a4c1df46 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/TrackerManager.kt @@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeList import eu.kanade.tachiyomi.data.track.shikimori.Shikimori import eu.kanade.tachiyomi.data.track.simkl.Simkl import eu.kanade.tachiyomi.data.track.suwayomi.Suwayomi +import kotlinx.coroutines.flow.combine class TrackerManager(context: Context) { @@ -42,5 +43,13 @@ class TrackerManager(context: Context) { fun loggedInTrackers() = trackers.filter { it.isLoggedIn } + fun loggedInTrackersFlow() = combine(trackers.map { it.isLoggedInFlow }) { + it.mapIndexedNotNull { index, isLoggedIn -> + if (isLoggedIn) trackers[index] else null + } + } + fun get(id: Long) = trackers.find { it.id == id } + + fun getAll(ids: Set) = trackers.filter { it.id in ids } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt index 9688d92512..26249841af 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt @@ -148,7 +148,7 @@ class AnimeScreen( ) }.takeIf { isAnimeHttpSource }, onTrackingClicked = { - if (screenModel.loggedInTrackers.isEmpty()) { + if (successState.loggedInTracker.isEmpty()) { navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking)) } else { screenModel.showTrackDialog() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index 24659b2ce2..cadc08d8a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -27,6 +27,7 @@ import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload import eu.kanade.tachiyomi.data.track.AnimeTracker import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker +import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem @@ -44,7 +45,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -116,8 +116,6 @@ class AnimeScreenModel( private val successState: State.Success? get() = state.value as? State.Success - val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn && it is AnimeTracker } } - val anime: Anime? get() = successState?.anime @@ -177,6 +175,16 @@ class AnimeScreenModel( } } + screenModelScope.launchIO { + trackerManager.loggedInTrackersFlow() + .distinctUntilChanged() + .collectLatest { trackers -> + updateSuccessState { + it.copy(loggedInTracker = trackers) + } + } + } + observeDownloads() screenModelScope.launchIO { @@ -976,21 +984,18 @@ class AnimeScreenModel( private fun observeTrackers() { val anime = successState?.anime ?: return + screenModelScope.launchIO { - getTracks.subscribe(anime.id) - .catch { logcat(LogPriority.ERROR, it) } - .map { tracks -> - loggedInTrackers - // Map to TrackItem - .map { service -> - AnimeTrackItem( - tracks.find { it.trackerId == service.id }, - service, - ) - } - // Show only if the service supports this anime's source - .filter { (it.tracker as? EnhancedAnimeTracker)?.accept(source!!) ?: true } - } + combine( + getTracks.subscribe(anime.id).catch { logcat(LogPriority.ERROR, it) }, + trackerManager.loggedInTrackersFlow(), + ) { animeTracks, loggedInTrackers -> + loggedInTrackers + // Map to TrackItem + .map { service -> AnimeTrackItem(animeTracks.find { it.trackerId == service.id }, service) } + // Show only if the service supports this anime's source + .filter { (it.tracker as? EnhancedAnimeTracker)?.accept(source!!) ?: true } + } .distinctUntilChanged() .collectLatest { trackItems -> updateSuccessState { it.copy(trackItems = trackItems) } @@ -1074,6 +1079,7 @@ class AnimeScreenModel( val isRefreshingData: Boolean = false, val dialog: Dialog? = null, val hasPromptedToAddBefore: Boolean = false, + val loggedInTracker: List = emptyList(), val nextAiringEpisode: Pair = Pair( anime.nextEpisodeToAir, anime.nextEpisodeAiringAt, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt index b11f2cd365..dcebaf4811 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/track/AnimeTrackInfoDialog.kt @@ -250,8 +250,8 @@ data class AnimeTrackInfoDialogHomeScreen( } private fun List.mapToTrackItem(): List { - val loggedInTrackers = Injekt.get().trackers.filter { - it.isLoggedIn && it is AnimeTracker + val loggedInTrackers = Injekt.get().loggedInTrackers().filter { + it is AnimeTracker } val source = Injekt.get().getOrStub(sourceId) return loggedInTrackers diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt index e1b132e5a3..9689d8581d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt @@ -139,7 +139,7 @@ class MangaScreen( ) }.takeIf { isHttpSource }, onTrackingClicked = { - if (screenModel.loggedInTrackers.isEmpty()) { + if (successState.loggedInTracker.isEmpty()) { navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking)) } else { screenModel.showTrackDialog() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index 685327b191..1d3a7d4a54 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -30,6 +30,7 @@ import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload import eu.kanade.tachiyomi.data.track.EnhancedMangaTracker import eu.kanade.tachiyomi.data.track.MangaTracker +import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.source.MangaSource @@ -47,7 +48,6 @@ import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -120,8 +120,6 @@ class MangaScreenModel( private val successState: State.Success? get() = state.value as? State.Success - val loggedInTrackers by lazy { trackerManager.trackers.filter { it.isLoggedIn && it is MangaTracker } } - val manga: Manga? get() = successState?.manga @@ -202,6 +200,16 @@ class MangaScreenModel( } } + screenModelScope.launchIO { + trackerManager.loggedInTrackersFlow() + .distinctUntilChanged() + .collectLatest { trackers -> + updateSuccessState { + it.copy(loggedInTracker = trackers) + } + } + } + observeDownloads() screenModelScope.launchIO { @@ -1004,15 +1012,16 @@ class MangaScreenModel( val manga = successState?.manga ?: return screenModelScope.launchIO { - getTracks.subscribe(manga.id) - .catch { logcat(LogPriority.ERROR, it) } - .map { tracks -> - loggedInTrackers - // Map to TrackItem - .map { service -> MangaTrackItem(tracks.find { it.trackerId == service.id }, service) } - // Show only if the service supports this manga's source - .filter { (it.tracker as? EnhancedMangaTracker)?.accept(source!!) ?: true } - } + combine( + getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) }, + trackerManager.loggedInTrackersFlow(), + ) { mangaTracks, loggedInTrackers -> + loggedInTrackers + // Map to TrackItem + .map { service -> MangaTrackItem(mangaTracks.find { it.trackerId == service.id }, service) } + // Show only if the service supports this manga's source + .filter { (it.tracker as? EnhancedMangaTracker)?.accept(source!!) ?: true } + } .distinctUntilChanged() .collectLatest { trackItems -> updateSuccessState { it.copy(trackItems = trackItems) } @@ -1083,6 +1092,7 @@ class MangaScreenModel( val isRefreshingData: Boolean = false, val dialog: Dialog? = null, val hasPromptedToAddBefore: Boolean = false, + val loggedInTracker: List = emptyList(), ) : State { val processedChapters by lazy { chapters.applyFilters(manga).toList() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt index 79f95e8f30..8955430bec 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/track/MangaTrackInfoDialog.kt @@ -250,8 +250,8 @@ data class MangaTrackInfoDialogHomeScreen( } private fun List.mapToTrackItem(): List { - val loggedInTrackers = Injekt.get().trackers.filter { - it.isLoggedIn && it is MangaTracker + val loggedInTrackers = Injekt.get().loggedInTrackers().filter { + it is MangaTracker } val source = Injekt.get().getOrStub(sourceId) return loggedInTrackers diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt index 7f7bf79484..88fbbccf30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibraryScreenModel.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -108,10 +109,10 @@ class AnimeLibraryScreenModel( getTracksPerAnime.subscribe(), getTrackingFilterFlow(), downloadCache.changes, - ) { searchQuery, library, tracks, loggedInTrackers, _ -> + ) { searchQuery, library, tracks, trackingFilter, _ -> library - .applyFilters(tracks, loggedInTrackers) - .applySort(tracks) + .applyFilters(tracks, trackingFilter) + .applySort(tracks, trackingFilter.keys) .mapValues { (_, value) -> if (searchQuery != null) { // Filter query @@ -175,9 +176,10 @@ class AnimeLibraryScreenModel( /** * Applies library filters to the given map of anime. */ + @Suppress("LongMethod", "CyclomaticComplexMethod") private suspend fun AnimeLibraryMap.applyFilters( trackMap: Map>, - loggedInTrackers: Map, + trackingFilter: Map, ): AnimeLibraryMap { val prefs = getAnimelibItemPreferencesFlow().first() val downloadedOnly = prefs.globalFilterDownloaded @@ -189,10 +191,10 @@ class AnimeLibraryScreenModel( val filterCompleted = prefs.filterCompleted val filterIntervalCustom = prefs.filterIntervalCustom - val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty() + val isNotLoggedInAnyTrack = trackingFilter.isEmpty() - val excludedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null } - val includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null } + val excludedTracks = trackingFilter.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null } + val includedTracks = trackingFilter.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null } val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() val filterFnDownloaded: (AnimeLibraryItem) -> Boolean = { @@ -256,9 +258,11 @@ class AnimeLibraryScreenModel( /** * Applies library sorting to the given map of anime. */ + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun AnimeLibraryMap.applySort( // Map> trackMap: Map>, + loggedInTrackerIds: Set, ): AnimeLibraryMap { val sortAlphabetically: (AnimeLibraryItem, AnimeLibraryItem) -> Int = { i1, i2 -> i1.libraryAnime.anime.title.lowercase().compareToWithCollator(i2.libraryAnime.anime.title.lowercase()) @@ -266,7 +270,7 @@ class AnimeLibraryScreenModel( val defaultTrackerScoreSortValue = -1.0 val trackerScores by lazy { - val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id } + val trackerMap = trackerManager.getAll(loggedInTrackerIds).associateBy { e -> e.id } trackMap.mapValues { entry -> when { entry.value.isEmpty() -> null @@ -417,18 +421,17 @@ class AnimeLibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedInTrackers = trackerManager.loggedInTrackers() - return if (loggedInTrackers.isNotEmpty()) { - val prefFlows = loggedInTrackers - .map { libraryPreferences.filterTrackedAnime(it.id.toInt()).changes() } - .toTypedArray() - combine(*prefFlows) { + return trackerManager.loggedInTrackersFlow().flatMapLatest { loggedInTrackers -> + if (loggedInTrackers.isEmpty()) return@flatMapLatest flowOf(emptyMap()) + + val prefFlows = loggedInTrackers.map { tracker -> + libraryPreferences.filterTrackedAnime(tracker.id.toInt()).changes() + } + combine(prefFlows) { loggedInTrackers .mapIndexed { index, tracker -> tracker.id to it[index] } .toMap() } - } else { - flowOf(emptyMap()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibrarySettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibrarySettingsScreenModel.kt index f7214745eb..13e90cdedb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibrarySettingsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/anime/AnimeLibrarySettingsScreenModel.kt @@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.data.track.TrackerManager +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.TriState import tachiyomi.core.common.preference.getAndSet @@ -16,17 +18,22 @@ import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import kotlin.time.Duration.Companion.seconds class AnimeLibrarySettingsScreenModel( val preferences: BasePreferences = Injekt.get(), val libraryPreferences: LibraryPreferences = Injekt.get(), private val setAnimeDisplayMode: SetAnimeDisplayMode = Injekt.get(), private val setSortModeForCategory: SetSortModeForAnimeCategory = Injekt.get(), - private val trackerManager: TrackerManager = Injekt.get(), + trackerManager: TrackerManager = Injekt.get(), ) : ScreenModel { - val trackers - get() = trackerManager.trackers.filter { it.isLoggedIn } + val trackersFlow = trackerManager.loggedInTrackersFlow() + .stateIn( + scope = screenModelScope, + started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds), + initialValue = trackerManager.loggedInTrackers() + ) fun toggleFilter(preference: (LibraryPreferences) -> Preference) { preference(libraryPreferences).getAndSet { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt index a34cfc3819..1b530e6202 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibraryScreenModel.kt @@ -39,6 +39,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map @@ -108,10 +109,10 @@ class MangaLibraryScreenModel( getTracksPerManga.subscribe(), getTrackingFilterFlow(), downloadCache.changes, - ) { searchQuery, library, tracks, loggedInTrackers, _ -> + ) { searchQuery, library, tracks, trackingFilter, _ -> library - .applyFilters(tracks, loggedInTrackers) - .applySort(tracks) + .applyFilters(tracks, trackingFilter) + .applySort(tracks, trackingFilter.keys) .mapValues { (_, value) -> if (searchQuery != null) { // Filter query @@ -175,9 +176,10 @@ class MangaLibraryScreenModel( /** * Applies library filters to the given map of manga. */ + @Suppress("LongMethod", "CyclomaticComplexMethod") private suspend fun MangaLibraryMap.applyFilters( trackMap: Map>, - loggedInTrackers: Map, + trackingFilter: Map, ): MangaLibraryMap { val prefs = getLibraryItemPreferencesFlow().first() val downloadedOnly = prefs.globalFilterDownloaded @@ -189,10 +191,10 @@ class MangaLibraryScreenModel( val filterCompleted = prefs.filterCompleted val filterIntervalCustom = prefs.filterIntervalCustom - val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty() + val isNotLoggedInAnyTrack = trackingFilter.isEmpty() - val excludedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null } - val includedTracks = loggedInTrackers.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null } + val excludedTracks = trackingFilter.mapNotNull { if (it.value == TriState.ENABLED_NOT) it.key else null } + val includedTracks = trackingFilter.mapNotNull { if (it.value == TriState.ENABLED_IS) it.key else null } val trackFiltersIsIgnored = includedTracks.isEmpty() && excludedTracks.isEmpty() val filterFnDownloaded: (MangaLibraryItem) -> Boolean = { @@ -256,9 +258,11 @@ class MangaLibraryScreenModel( /** * Applies library sorting to the given map of manga. */ + @Suppress("LongMethod", "CyclomaticComplexMethod") private fun MangaLibraryMap.applySort( // Map> trackMap: Map>, + loggedInTrackerIds: Set, ): MangaLibraryMap { val sortAlphabetically: (MangaLibraryItem, MangaLibraryItem) -> Int = { i1, i2 -> i1.libraryManga.manga.title.lowercase().compareToWithCollator(i2.libraryManga.manga.title.lowercase()) @@ -266,7 +270,7 @@ class MangaLibraryScreenModel( val defaultTrackerScoreSortValue = -1.0 val trackerScores by lazy { - val trackerMap = trackerManager.loggedInTrackers().associateBy { e -> e.id } + val trackerMap = trackerManager.getAll(loggedInTrackerIds).associateBy { e -> e.id } trackMap.mapValues { entry -> when { entry.value.isEmpty() -> null @@ -407,18 +411,17 @@ class MangaLibraryScreenModel( * @return map of track id with the filter value */ private fun getTrackingFilterFlow(): Flow> { - val loggedInTrackers = trackerManager.loggedInTrackers() - return if (loggedInTrackers.isNotEmpty()) { - val prefFlows = loggedInTrackers - .map { libraryPreferences.filterTrackedManga(it.id.toInt()).changes() } - .toTypedArray() - combine(*prefFlows) { + return trackerManager.loggedInTrackersFlow().flatMapLatest { loggedInTrackers -> + if (loggedInTrackers.isEmpty()) return@flatMapLatest flowOf(emptyMap()) + + val prefFlows = loggedInTrackers.map { tracker -> + libraryPreferences.filterTrackedManga(tracker.id.toInt()).changes() + } + combine(prefFlows) { loggedInTrackers .mapIndexed { index, tracker -> tracker.id to it[index] } .toMap() } - } else { - flowOf(emptyMap()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibrarySettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibrarySettingsScreenModel.kt index 3f59c7c33f..d439e47d59 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibrarySettingsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/manga/MangaLibrarySettingsScreenModel.kt @@ -4,6 +4,8 @@ import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.domain.base.BasePreferences import eu.kanade.tachiyomi.data.track.TrackerManager +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn import tachiyomi.core.common.preference.Preference import tachiyomi.core.common.preference.TriState import tachiyomi.core.common.preference.getAndSet @@ -16,17 +18,22 @@ import tachiyomi.domain.library.model.LibraryDisplayMode import tachiyomi.domain.library.service.LibraryPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import kotlin.time.Duration.Companion.seconds class MangaLibrarySettingsScreenModel( val preferences: BasePreferences = Injekt.get(), val libraryPreferences: LibraryPreferences = Injekt.get(), private val setMangaDisplayMode: SetMangaDisplayMode = Injekt.get(), private val setSortModeForCategory: SetSortModeForMangaCategory = Injekt.get(), - private val trackerManager: TrackerManager = Injekt.get(), + trackerManager: TrackerManager = Injekt.get(), ) : ScreenModel { - val trackers - get() = trackerManager.trackers.filter { it.isLoggedIn } + val trackersFlow = trackerManager.loggedInTrackersFlow() + .stateIn( + scope = screenModelScope, + started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds), + initialValue = trackerManager.loggedInTrackers() + ) fun toggleFilter(preference: (LibraryPreferences) -> Preference) { preference(libraryPreferences).getAndSet { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt index d82f3af6a0..18d410f42e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/anime/AnimeStatsScreenModel.kt @@ -37,7 +37,7 @@ class AnimeStatsScreenModel( private val trackerManager: TrackerManager = Injekt.get(), ) : StateScreenModel(StatsScreenState.Loading) { - private val loggedInTrackers by lazy { trackerManager.trackers.fastFilter { it.isLoggedIn && it is AnimeTracker } } + private val loggedInTrackers by lazy { trackerManager.loggedInTrackers().filter { it is AnimeTracker } } init { screenModelScope.launchIO { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt index 836126a884..ac84d887b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/stats/manga/MangaStatsScreenModel.kt @@ -37,7 +37,7 @@ class MangaStatsScreenModel( private val trackerManager: TrackerManager = Injekt.get(), ) : StateScreenModel(StatsScreenState.Loading) { - private val loggedInTrackers by lazy { trackerManager.trackers.fastFilter { it.isLoggedIn && it is MangaTracker } } + private val loggedInTrackers by lazy { trackerManager.loggedInTrackers().filter { it is MangaTracker } } init { screenModelScope.launchIO { diff --git a/app/src/main/java/eu/kanade/test/DummyTracker.kt b/app/src/main/java/eu/kanade/test/DummyTracker.kt index 3d32edc612..0c6ac38478 100644 --- a/app/src/main/java/eu/kanade/test/DummyTracker.kt +++ b/app/src/main/java/eu/kanade/test/DummyTracker.kt @@ -8,6 +8,8 @@ import eu.kanade.tachiyomi.data.track.model.AnimeTrackSearch import eu.kanade.tachiyomi.data.track.model.MangaTrackSearch import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import okhttp3.OkHttpClient import tachiyomi.i18n.MR @@ -16,6 +18,7 @@ data class DummyTracker( override val name: String, override val supportsReadingDates: Boolean = false, override val isLoggedIn: Boolean = false, + override val isLoggedInFlow: Flow = flowOf(false), val valLogoColor: Int = Color.rgb(18, 25, 35), val valLogo: Int = R.drawable.ic_tracker_anilist, val valStatuses: List = (1L..6L).toList(), From 5e1a6c9d9c650e08f638802e3f5a06655de3fdad Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sat, 13 Jul 2024 09:26:00 +0200 Subject: [PATCH 64/74] Fix login prompts despite being logged in to trackers in Manga screen Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../tachiyomi/ui/entries/anime/AnimeScreen.kt | 2 +- .../ui/entries/anime/AnimeScreenModel.kt | 42 ++++++++++--------- .../tachiyomi/ui/entries/manga/MangaScreen.kt | 2 +- .../ui/entries/manga/MangaScreenModel.kt | 38 +++++++---------- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt index 26249841af..d000680c2f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreen.kt @@ -148,7 +148,7 @@ class AnimeScreen( ) }.takeIf { isAnimeHttpSource }, onTrackingClicked = { - if (successState.loggedInTracker.isEmpty()) { + if (!successState.hasLoggedInTrackers) { navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking)) } else { screenModel.showTrackDialog() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index cadc08d8a8..492afc2d5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -175,16 +175,6 @@ class AnimeScreenModel( } } - screenModelScope.launchIO { - trackerManager.loggedInTrackersFlow() - .distinctUntilChanged() - .collectLatest { trackers -> - updateSuccessState { - it.copy(loggedInTracker = trackers) - } - } - } - observeDownloads() screenModelScope.launchIO { @@ -986,19 +976,35 @@ class AnimeScreenModel( val anime = successState?.anime ?: return screenModelScope.launchIO { + combine( + getTracks.subscribe(anime.id).catch { logcat(LogPriority.ERROR, it) }, + trackerManager.loggedInTrackersFlow(), + ) { animeTracks, loggedInTrackers -> + // Show only if the service supports this manga's source + val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedAnimeTracker)?.accept(source!!) ?: true } + val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet() + val supportedTrackerTracks = animeTracks.filter { it.trackerId in supportedTrackerIds } + supportedTrackerTracks.size to supportedTrackers.isNotEmpty() + } + .distinctUntilChanged() + .collectLatest { (trackingCount, hasLoggedInTrackers) -> + updateSuccessState { + it.copy( + trackingCount = trackingCount, + hasLoggedInTrackers = hasLoggedInTrackers, + ) + } + } + combine( getTracks.subscribe(anime.id).catch { logcat(LogPriority.ERROR, it) }, trackerManager.loggedInTrackersFlow(), ) { animeTracks, loggedInTrackers -> loggedInTrackers - // Map to TrackItem .map { service -> AnimeTrackItem(animeTracks.find { it.trackerId == service.id }, service) } - // Show only if the service supports this anime's source - .filter { (it.tracker as? EnhancedAnimeTracker)?.accept(source!!) ?: true } } .distinctUntilChanged() .collectLatest { trackItems -> - updateSuccessState { it.copy(trackItems = trackItems) } updateAiringTime(anime, trackItems, manualFetch = false) } } @@ -1075,11 +1081,12 @@ class AnimeScreenModel( val source: AnimeSource, val isFromSource: Boolean, val episodes: List, - val trackItems: List = emptyList(), + val trackingCount: Int = 0, + val hasLoggedInTrackers: Boolean = false, val isRefreshingData: Boolean = false, val dialog: Dialog? = null, val hasPromptedToAddBefore: Boolean = false, - val loggedInTracker: List = emptyList(), + val trackItems: List = emptyList(), val nextAiringEpisode: Pair = Pair( anime.nextEpisodeToAir, anime.nextEpisodeAiringAt, @@ -1120,9 +1127,6 @@ class AnimeScreenModel( val trackingAvailable: Boolean get() = trackItems.isNotEmpty() - val trackingCount: Int - get() = trackItems.count { it.track != null } - val airingEpisodeNumber: Double get() = nextAiringEpisode.first.toDouble() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt index 9689d8581d..fa0ac29b5c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreen.kt @@ -139,7 +139,7 @@ class MangaScreen( ) }.takeIf { isHttpSource }, onTrackingClicked = { - if (successState.loggedInTracker.isEmpty()) { + if (!successState.hasLoggedInTrackers) { navigator.push(SettingsScreen(SettingsScreen.Destination.Tracking)) } else { screenModel.showTrackDialog() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index 1d3a7d4a54..7e8809cb2c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -200,16 +200,6 @@ class MangaScreenModel( } } - screenModelScope.launchIO { - trackerManager.loggedInTrackersFlow() - .distinctUntilChanged() - .collectLatest { trackers -> - updateSuccessState { - it.copy(loggedInTracker = trackers) - } - } - } - observeDownloads() screenModelScope.launchIO { @@ -1016,15 +1006,20 @@ class MangaScreenModel( getTracks.subscribe(manga.id).catch { logcat(LogPriority.ERROR, it) }, trackerManager.loggedInTrackersFlow(), ) { mangaTracks, loggedInTrackers -> - loggedInTrackers - // Map to TrackItem - .map { service -> MangaTrackItem(mangaTracks.find { it.trackerId == service.id }, service) } - // Show only if the service supports this manga's source - .filter { (it.tracker as? EnhancedMangaTracker)?.accept(source!!) ?: true } + // Show only if the service supports this manga's source + val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedMangaTracker)?.accept(source!!) ?: true } + val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet() + val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds } + supportedTrackerTracks.size to supportedTrackers.isNotEmpty() } .distinctUntilChanged() - .collectLatest { trackItems -> - updateSuccessState { it.copy(trackItems = trackItems) } + .collectLatest { (trackingCount, hasLoggedInTrackers) -> + updateSuccessState { + it.copy( + trackingCount = trackingCount, + hasLoggedInTrackers = hasLoggedInTrackers, + ) + } } } } @@ -1088,11 +1083,11 @@ class MangaScreenModel( val chapters: List, val availableScanlators: Set, val excludedScanlators: Set, - val trackItems: List = emptyList(), + val trackingCount: Int = 0, + val hasLoggedInTrackers: Boolean = false, val isRefreshingData: Boolean = false, val dialog: Dialog? = null, val hasPromptedToAddBefore: Boolean = false, - val loggedInTracker: List = emptyList(), ) : State { val processedChapters by lazy { chapters.applyFilters(manga).toList() @@ -1136,10 +1131,7 @@ class MangaScreenModel( get() = scanlatorFilterActive || manga.chaptersFiltered() val trackingAvailable: Boolean - get() = trackItems.isNotEmpty() - - val trackingCount: Int - get() = trackItems.count { it.track != null } + get() = trackingCount > 0 /** * Applies the view filters to the list of chapters obtained from the database. From 20d8b06104071df6cad5a08009baf1adffdf932c Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sat, 13 Jul 2024 09:31:27 +0200 Subject: [PATCH 65/74] Fix some issues when reading/saving images * Fix unsupported mime type error when saving images Avoid using platform mime type map to get extensions as it may not have all mime types we support. * Fix jxl images downloading/reading Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> --- .../download/manga/MangaDownloadManager.kt | 3 ++- .../data/download/manga/MangaDownloader.kt | 8 +------ .../kanade/tachiyomi/data/saver/ImageSaver.kt | 18 ++++++++++----- .../core/common/util/system/ImageUtil.kt | 23 ++++--------------- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt index 1922e78f07..381ca11983 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloadManager.kt @@ -18,6 +18,7 @@ import logcat.LogPriority import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.category.manga.interactor.GetMangaCategories import tachiyomi.domain.download.service.DownloadPreferences @@ -171,7 +172,7 @@ class MangaDownloadManager( source, ) val files = chapterDir?.listFiles().orEmpty() - .filter { "image" in it.type.orEmpty() } + .filter { it.isFile && ImageUtil.isImage(it.name) { it.openInputStream() } } if (files.isEmpty()) { throw Exception(context.stringResource(MR.strings.page_list_empty_error)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt index 696f72e5cd..f13b9a7049 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/manga/MangaDownloader.kt @@ -574,14 +574,8 @@ class MangaDownloader( * @param file the file where the image is already downloaded. */ private fun getImageExtension(response: Response, file: UniFile): String { - // Read content type if available. val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null } - // Else guess from the uri. - ?: context.contentResolver.getType(file.uri) - // Else read magic numbers. - ?: ImageUtil.findImageType { file.openInputStream() }?.mime - - return ImageUtil.getExtensionFromMimeType(mime) + return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() } } private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt index b6c3c307ba..cf906b40ba 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore +import android.webkit.MimeTypeMap import androidx.annotation.RequiresApi import androidx.core.content.contentValuesOf import androidx.core.net.toUri @@ -65,21 +66,26 @@ class ImageSaver( filename: String, data: () -> InputStream, ): Uri { - val pictureDir = + val isMimeTypeSupported = MimeTypeMap.getSingleton().hasMimeType(type.mime) + + val pictureDir = if (isMimeTypeSupported) { MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } else { + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } val imageLocation = (image.location as Location.Pictures).relativePath val relativePath = listOf( - Environment.DIRECTORY_PICTURES, + if (isMimeTypeSupported) Environment.DIRECTORY_PICTURES else Environment.DIRECTORY_DOCUMENTS, context.stringResource(MR.strings.app_name), imageLocation, ).joinToString(File.separator) val contentValues = contentValuesOf( - MediaStore.Images.Media.RELATIVE_PATH to relativePath, - MediaStore.Images.Media.DISPLAY_NAME to image.name, - MediaStore.Images.Media.MIME_TYPE to type.mime, - MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond, + MediaStore.MediaColumns.RELATIVE_PATH to relativePath, + MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename, + MediaStore.MediaColumns.MIME_TYPE to type.mime, + MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond, ) val picture = findUriOrDefault(relativePath, filename) { diff --git a/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt b/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt index e33275de65..1933d49e37 100644 --- a/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt +++ b/core/common/src/main/java/tachiyomi/core/common/util/system/ImageUtil.kt @@ -13,7 +13,6 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build -import android.webkit.MimeTypeMap import androidx.annotation.ColorInt import androidx.core.graphics.alpha import androidx.core.graphics.applyCanvas @@ -29,7 +28,6 @@ import okio.BufferedSource import tachiyomi.decoder.Format import tachiyomi.decoder.ImageDecoder import java.io.InputStream -import java.net.URLConnection import java.util.Locale import kotlin.math.abs import kotlin.math.max @@ -40,12 +38,8 @@ object ImageUtil { fun isImage(name: String?, openStream: (() -> InputStream)? = null): Boolean { if (name == null) return false - val contentType = try { - URLConnection.guessContentTypeFromName(name) - } catch (e: Exception) { - null - } ?: openStream?.let { findImageType(it)?.mime } - return contentType?.startsWith("image/") ?: false + val extension = name.substringAfterLast('.') + return ImageType.entries.any { it.extension == extension } || openStream?.let { findImageType(it) } != null } fun findImageType(openStream: () -> InputStream): ImageType? { @@ -69,10 +63,9 @@ object ImageUtil { } } - fun getExtensionFromMimeType(mime: String?): String { - return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) - ?: SUPPLEMENTARY_MIMETYPE_MAPPING[mime] - ?: "jpg" + fun getExtensionFromMimeType(mime: String?, openStream: () -> InputStream): String { + val type = mime?.let { ImageType.entries.find { it.mime == mime } } ?: findImageType(openStream) + return type?.extension ?: "jpg" } fun isAnimatedAndSupported(source: BufferedSource): Boolean { @@ -596,12 +589,6 @@ object ImageUtil { } private val optimalImageHeight = getDisplayMaxHeightInPx * 2 - - // Android doesn't include some mappings - private val SUPPLEMENTARY_MIMETYPE_MAPPING = mapOf( - // https://issuetracker.google.com/issues/182703810 - "image/jxl" to "jxl", - ) } val getDisplayMaxHeightInPx: Int From 633e9880a21c224bd52f423bd464755d98aa2f5b Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sat, 13 Jul 2024 09:34:39 +0200 Subject: [PATCH 66/74] Bump coil version and some cleanup Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/src/main/java/eu/kanade/tachiyomi/App.kt | 17 ++++++++++++----- gradle/libs.versions.toml | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 672c401d98..25aec7c193 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -174,30 +174,37 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor ) } + @Suppress("MagicNumber") override fun newImageLoader(context: Context): ImageLoader { return ImageLoader.Builder(this).apply { val callFactoryLazy = lazy { Injekt.get().client } components { + // NetworkFetcher.Factory add(OkHttpNetworkFetcherFactory(callFactoryLazy::value)) + // Decoder.Factory add(TachiyomiImageDecoder.Factory()) + // Fetcher.Factory + add(BufferedSourceFetcher.Factory()) add(MangaCoverFetcher.MangaFactory(callFactoryLazy)) + add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy)) add(AnimeCoverFetcher.AnimeFactory(callFactoryLazy)) add(AnimeCoverFetcher.AnimeCoverFactory(callFactoryLazy)) + // Keyer add(AnimeKeyer()) - add(MangaCoverFetcher.MangaCoverFactory(callFactoryLazy)) add(MangaKeyer()) add(AnimeCoverKeyer()) add(MangaCoverKeyer()) - add(BufferedSourceFetcher.Factory()) } + crossfade((300 * this@App.animatorDurationScale).toInt()) allowRgb565(DeviceUtil.isLowRamDevice(this@App)) if (networkPreferences.verboseLogging().get()) logger(DebugLogger()) // Coil spawns a new thread for every image load by default - fetcherDispatcher(Dispatchers.IO.limitedParallelism(8)) - decoderDispatcher(Dispatchers.IO.limitedParallelism(2)) - }.build() + fetcherCoroutineContext(Dispatchers.IO.limitedParallelism(8)) + decoderCoroutineContext(Dispatchers.IO.limitedParallelism(3)) + } + .build() } override fun onStart(owner: LifecycleOwner) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 749314293a..a40eb7c85c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -42,7 +42,7 @@ preferencektx = "androidx.preference:preference-ktx:1.2.1" injekt-core = "com.github.inorichi.injekt:injekt-core:65b0440" -coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.0-alpha07" } +coil-bom = { module = "io.coil-kt.coil3:coil-bom", version = "3.0.0-alpha08" } coil-core = { module = "io.coil-kt.coil3:coil" } coil-gif = { module = "io.coil-kt.coil3:coil-gif" } coil-compose = { module = "io.coil-kt.coil3:coil-compose" } From faa5c070d4df5e68b20756511d376f87900d5fd1 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sat, 13 Jul 2024 09:49:25 +0200 Subject: [PATCH 67/74] Merge readme.md from mihon --- .../app-icon.png => assets/logo.png} | Bin README.md | 113 +++++++++--------- 2 files changed, 58 insertions(+), 55 deletions(-) rename .github/{readme-images/app-icon.png => assets/logo.png} (100%) diff --git a/.github/readme-images/app-icon.png b/.github/assets/logo.png similarity index 100% rename from .github/readme-images/app-icon.png rename to .github/assets/logo.png diff --git a/README.md b/README.md index cfa6afc017..56ac87da43 100644 --- a/README.md +++ b/README.md @@ -1,83 +1,86 @@ -| Build | Preview Release | Codefactor | Stable | Translate Aniyomi | Discord Server | -|-------|-----------|-------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------|---------| -| [![CI](https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml/badge.svg)](https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml) | [![latest preview build](https://img.shields.io/github/v/release/aniyomiorg/aniyomi-preview.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi-preview/releases) | [![CodeFactor](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi/badge)](https://www.codefactor.io/repository/github/aniyomiorg/aniyomi) | [![stable release](https://img.shields.io/github/release/aniyomiorg/aniyomi.svg?maxAge=3600&label=download)](https://github.com/aniyomiorg/aniyomi/releases) | [![Translation status](https://hosted.weblate.org/widgets/aniyomi/-/svg-badge.svg)](https://hosted.weblate.org/engage/aniyomi/?utm_source=widget) | [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) | +
-# ![app icon](.github/readme-images/app-icon.png)Aniyomi -Aniyomi is a video player and image viewer for Android 8.0 and above. + + Aniyomi logo + -## Features +# Aniyomi [App](#) -Features include: -* Watching videos -* View images -* Local reading/watching of downloaded content -* A configurable reader with multiple viewers, reading directions and other settings. -* A configurable player built on mpv-android with multiple options and settings -* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), and [Bangumi](https://bgm.tv/) -* Categories to organize your library -* Light and dark themes -* Create backups locally to read/watch offline or to your desired cloud service +### Full-featured player and reader, based on ~~Tachiyomi~~ Mihon. +Discover and watch anime, cartoons, series, and more – easier than ever on your Android device. + +[![Discord server](https://img.shields.io/discord/841701076242530374.svg?label=&labelColor=6A7EC2&color=7389D8&logo=discord&logoColor=FFFFFF)](https://discord.gg/F32UjdJZrR) +[![GitHub downloads](https://img.shields.io/github/downloads/aniyomiorg/aniyomi/total?label=downloads&labelColor=27303D&color=0D1117&logo=github&logoColor=FFFFFF&style=flat)](https://github.com/aniyomiorg/aniyomi/releases) + +[![CI](https://img.shields.io/github/actions/workflow/status/aniyomiorg/aniyomi/build_push.yml?labelColor=27303D)](https://github.com/aniyomiorg/aniyomi/actions/workflows/build_push.yml) +[![License: Apache-2.0](https://img.shields.io/github/license/aniyomiorg/aniyomi?labelColor=27303D&color=818cf8)](/LICENSE) +[![Translation status](https://img.shields.io/weblate/progress/aniyomi?labelColor=27303D&color=946300)](https://hosted.weblate.org/engage/aniyomi/) ## Download -Get the app from the [releases page](https://github.com/aniyomiorg/aniyomi/releases). -If you want to try new features before they get to the stable release, you can download the preview version [here](https://github.com/aniyomiorg/aniyomi-preview/releases). +[![Aniyomi Stable](https://img.shields.io/github/release/aniyomiorg/aniyomi.svg?maxAge=3600&label=Stable&labelColor=06599d&color=043b69)](https://github.com/aniyomiorg/aniyomi/releases) +[![Aniyomi Preview](https://img.shields.io/github/v/release/aniyomiorg/aniyomi-preview.svg?maxAge=3600&label=Beta&labelColor=2c2c47&color=1c1c39)](https://github.com/aniyomiorg/aniyomi-preview/releases) -## Issues, Feature Requests and Contributing +*Requires Android 8.0 or higher.* -Please make sure to read the full guidelines. Your issue may be closed without warning if you do not. +## Features -
Issues +
-1. **Before reporting a new issue, take a look at the already opened and closed [issues](https://github.com/aniyomiorg/aniyomi/issues), and [recent release version changes](https://aniyomi.org/changelogs/).** -2. If you are unsure, ask on Discord: [![Discord](https://img.shields.io/discord/841701076242530374?label=discord&labelColor=7289da&color=2c2f33&style=flat)](https://discord.gg/F32UjdJZrR) +* Local reading and watching of content. +* A configurable reader with multiple viewers, reading directions and other settings. +* A configurable player built on mpv-android with multiple options and settings. +* Tracker support: [MyAnimeList](https://myanimelist.net/), [AniList](https://anilist.co/), [Kitsu](https://kitsu.io/), [MangaUpdates](https://mangaupdates.com), [Shikimori](https://shikimori.one), [Simkl](https://simkl.com/), and [Bangumi](https://bgm.tv/) support. +* Categories to organize your library. +* Light and dark themes. +* Schedule updating your library for new chapters/episodes. +* Create backups locally to read/watch offline or to your desired cloud service. +* Plus much more... -
+
-
Bugs +## Contributing -* Include version (More → About → Version) - * If not latest, try updating, it may have already been solved - * Preview version is equal to the number of commits as seen on the main page -* Include steps to reproduce (if not obvious from description) -* Include screenshot (if needed) -* If it could be device-dependent, try reproducing on another device (if possible) -* Don't group unrelated requests into one issue +[Code of conduct](./CODE_OF_CONDUCT.md) · [Contributing guide](./CONTRIBUTING.md) -
+Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. -
Feature Requests +Before reporting a new issue, take a look at the [FAQ](https://aniyomi.org/docs/faq/general), the [changelog](https://aniyomi.org/changelogs/) and the already opened [issues](https://github.com/aniyomiorg/aniyomi/issues); if you got any questions, join our [Discord server](https://discord.gg/F32UjdJZrR). -* Write a detailed issue, explaining what it should do or how. Avoid writing just "like X app does" -* Include screenshot (if needed) +### Repositories -
Contributing +[![aniyomiorg/aniyomi-website - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=aniyomiorg&repo=aniyomi-website&bg_color=161B22&text_color=c9d1d9&title_color=818cf8&icon_color=818cf8&border_radius=8&hide_border=true&description_lines_count=2)](https://github.com/aniyomiorg/aniyomi-website/) +[![aniyomiorg/aniyomi-mpv-lib - GitHub](https://github-readme-stats.vercel.app/api/pin/?username=aniyomiorg&repo=aniyomi-mpv-lib&bg_color=161B22&text_color=c9d1d9&title_color=818cf8&icon_color=818cf8&border_radius=8&hide_border=true&description_lines_count=2)](https://github.com/aniyomiorg/aniyomi-mpv-lib/) -See [CONTRIBUTING.md](./CONTRIBUTING.md). -
+### Credits -
Code of Conduct +Thank you to all the people who have contributed! -See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). -
+ + Aniyomi app contributors + +### Disclaimer -## License +The developer(s) of this application does not have any affiliation with the content providers available, and this application hosts zero content. - Copyright 2015 Javier Tomás +### License - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +
+Copyright © 2015 Javier Tomás
+Copyright © 2024 The Aniyomi Open Source Project
 
-    http://www.apache.org/licenses/LICENSE-2.0
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
 
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
+http://www.apache.org/licenses/LICENSE-2.0
 
-## Disclaimer
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
-The developer of this application does not have any affiliation with the content providers available. + \ No newline at end of file From abfb31dfc83aa52a38347f310f4472792479befc Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sat, 13 Jul 2024 09:50:45 +0200 Subject: [PATCH 68/74] fix(deps): update dependency com.android.tools.build:gradle to v8.5.1 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/androidx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 0380c9597f..0e6a06d151 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,5 +1,5 @@ [versions] -agp_version = "8.5.0" +agp_version = "8.5.1" lifecycle_version = "2.8.3" paging_version = "3.3.0" interpolator_version = "1.0.0" From 9243169dbad5d7c1a52003f8268545bf9bf8adcc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 04:51:55 +0600 Subject: [PATCH 69/74] chore(deps): update actions/dependency-review-action action to v4.3.4 (#1009) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit e94c8dac9425a1d3fbe6640a0c285e9f3a5f16a8) --- .github/workflows/build_pull_request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 7d8c2ec93f..530db287cf 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -28,7 +28,7 @@ jobs: uses: gradle/actions/wrapper-validation@dbbdc275be76ac10734476cc723d82dfe7ec6eda # v3.4.2 - name: Dependency Review - uses: actions/dependency-review-action@72eb03d02c7872a771aacd928f3123ac62ad6d3a # v4.3.3 + uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4 - name: Set up JDK uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1 From e8d1beb167ec4499de72c3b49c2362db40f9213e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 04:52:08 +0600 Subject: [PATCH 70/74] fix(deps): update dependency dev.chrisbanes.compose:compose-bom to v2024.07.00-alpha01 (#1002) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit 14d687c5cdff1071098b3580e8b010d7aa01f485) --- gradle/compose.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/compose.versions.toml b/gradle/compose.versions.toml index 74ae656e98..ea9e52efde 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compose-bom = "2024.06.00-alpha01" +compose-bom = "2024.07.00-alpha01" accompanist = "0.35.1-alpha" [libraries] From cb87612f8a903402e819c580fbd6083f0d6eb744 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 04:52:17 +0600 Subject: [PATCH 71/74] fix(deps): update dependency org.jsoup:jsoup to v1.18.1 (#999) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit f3f2bd41c3974878bcf0e3a62d99ee89bf92fb41) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a40eb7c85c..7c1bcdbb84 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ conscrypt-android = "org.conscrypt:conscrypt-android:2.5.2" quickjs-android = "app.cash.quickjs:quickjs-android:0.9.2" -jsoup = "org.jsoup:jsoup:1.17.2" +jsoup = "org.jsoup:jsoup:1.18.1" disklrucache = "com.jakewharton:disklrucache:2.0.2" unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc" From 78cb48b5162196cccbc4a21d77a3217c3f7a2cca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 06:04:57 +0600 Subject: [PATCH 72/74] chore(deps): update dependency gradle to v8.9 (#1007) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> (cherry picked from commit f63e95091013320b27bfc3c7c975c4bdd4a983c5) --- gradle/wrapper/gradle-wrapper.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4413138c9..09523c0e54 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 150ba47b7670c46e461628823cb467c5e3014185 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Sat, 13 Jul 2024 10:00:14 +0200 Subject: [PATCH 73/74] detekt(detekt): detekt --- .../extension/anime/interactor/TrustAnimeExtension.kt | 3 --- .../extension/manga/interactor/TrustMangaExtension.kt | 3 --- .../presentation/library/components/CommonEntryItem.kt | 3 +-- .../kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt | 7 +++---- .../kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt | 7 +++---- 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt index ad31d132a8..5e8643a62b 100644 --- a/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt +++ b/app/src/main/java/eu/kanade/domain/extension/anime/interactor/TrustAnimeExtension.kt @@ -30,6 +30,3 @@ class TrustAnimeExtension( preferences.trustedExtensions().delete() } } - -// jmir1's key -private const val officialSignature = "50ab1d1e3a20d204d0ad6d334c7691c632e41b98dfa132bf385695fdfa63839c" diff --git a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt index a6e3afb087..006f650bad 100644 --- a/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt +++ b/app/src/main/java/eu/kanade/domain/extension/manga/interactor/TrustMangaExtension.kt @@ -30,6 +30,3 @@ class TrustMangaExtension( preferences.trustedExtensions().delete() } } - -// inorichi's key -private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" diff --git a/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt b/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt index 175a595b5d..43c9ecd4c6 100644 --- a/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt +++ b/app/src/main/java/eu/kanade/presentation/library/components/CommonEntryItem.kt @@ -39,12 +39,11 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import eu.kanade.presentation.entries.components.ItemCover -import tachiyomi.domain.entries.EntryCover as EntryCoverModel import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.BadgeGroup import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.selectedBackground - +import tachiyomi.domain.entries.EntryCover as EntryCoverModel object CommonEntryItemDefaults { val GridHorizontalSpacer = 4.dp diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt index 492afc2d5d..41f502b8de 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/anime/AnimeScreenModel.kt @@ -4,7 +4,6 @@ import android.content.Context import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult import androidx.compose.runtime.Immutable -import androidx.compose.runtime.getValue import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.core.util.addOrRemove @@ -25,9 +24,7 @@ import eu.kanade.tachiyomi.animesource.model.Video import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadCache import eu.kanade.tachiyomi.data.download.anime.AnimeDownloadManager import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload -import eu.kanade.tachiyomi.data.track.AnimeTracker import eu.kanade.tachiyomi.data.track.EnhancedAnimeTracker -import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.ui.entries.anime.track.AnimeTrackItem @@ -981,7 +978,9 @@ class AnimeScreenModel( trackerManager.loggedInTrackersFlow(), ) { animeTracks, loggedInTrackers -> // Show only if the service supports this manga's source - val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedAnimeTracker)?.accept(source!!) ?: true } + val supportedTrackers = loggedInTrackers.filter { + (it as? EnhancedAnimeTracker)?.accept(source!!) ?: true + } val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet() val supportedTrackerTracks = animeTracks.filter { it.trackerId in supportedTrackerIds } supportedTrackerTracks.size to supportedTrackers.isNotEmpty() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt index 7e8809cb2c..30a2cd3252 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/entries/manga/MangaScreenModel.kt @@ -29,12 +29,9 @@ import eu.kanade.tachiyomi.data.download.manga.MangaDownloadCache import eu.kanade.tachiyomi.data.download.manga.MangaDownloadManager import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload import eu.kanade.tachiyomi.data.track.EnhancedMangaTracker -import eu.kanade.tachiyomi.data.track.MangaTracker -import eu.kanade.tachiyomi.data.track.Tracker import eu.kanade.tachiyomi.data.track.TrackerManager import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.source.MangaSource -import eu.kanade.tachiyomi.ui.entries.manga.track.MangaTrackItem import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.util.chapter.getNextUnread import eu.kanade.tachiyomi.util.removeCovers @@ -1007,7 +1004,9 @@ class MangaScreenModel( trackerManager.loggedInTrackersFlow(), ) { mangaTracks, loggedInTrackers -> // Show only if the service supports this manga's source - val supportedTrackers = loggedInTrackers.filter { (it as? EnhancedMangaTracker)?.accept(source!!) ?: true } + val supportedTrackers = loggedInTrackers.filter { + (it as? EnhancedMangaTracker)?.accept(source!!) ?: true + } val supportedTrackerIds = supportedTrackers.map { it.id }.toHashSet() val supportedTrackerTracks = mangaTracks.filter { it.trackerId in supportedTrackerIds } supportedTrackerTracks.size to supportedTrackers.isNotEmpty() From b40f2a864d8e326d442a978109c1ac877a7b1518 Mon Sep 17 00:00:00 2001 From: jmir1 Date: Tue, 16 Jul 2024 15:13:00 +0200 Subject: [PATCH 74/74] fix: Include Mihon copyright --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 56ac87da43..ab36a21f99 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ The developer(s) of this application does not have any affiliation with the cont
 Copyright © 2015 Javier Tomás
+Copyright © 2024 The Mihon Open Source Project
 Copyright © 2024 The Aniyomi Open Source Project
 
 Licensed under the Apache License, Version 2.0 (the "License");