From 96c5813981d47fb2f2eb6b45dc3af777305817a7 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 08:34:51 +0100 Subject: [PATCH 01/40] Use materilalKolor for monet compat color scheme Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- app/build.gradle.kts | 1 + .../presentation/theme/TachiyomiTheme.kt | 24 +++-- .../theme/colorscheme/BaseColorScheme.kt | 13 ++- .../theme/colorscheme/MonetColorScheme.kt | 88 ++----------------- gradle/libs.versions.toml | 3 + 5 files changed, 42 insertions(+), 87 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9689bca070..a3557a7a71 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -286,6 +286,7 @@ dependencies { implementation(libs.compose.grid) implementation(libs.reorderable) implementation(libs.bundles.markdown) + implementation(libs.materialKolor) // Logging implementation(libs.logcat) diff --git a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt index b84f9b1a03..f07f4d4732 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.theme +import android.content.Context import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.ripple.RippleAlpha import androidx.compose.material3.ColorScheme @@ -7,6 +8,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RippleConfiguration import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable +import androidx.compose.runtime.remember import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import eu.kanade.domain.ui.UiPreferences @@ -62,26 +64,36 @@ private fun BaseTachiyomiTheme( isAmoled: Boolean, content: @Composable () -> Unit, ) { + val context = LocalContext.current + val isDark = isSystemInDarkTheme() MaterialTheme( - colorScheme = getThemeColorScheme(appTheme, isAmoled), + colorScheme = remember(appTheme, isDark, isAmoled) { + getThemeColorScheme( + context = context, + appTheme = appTheme, + isDark = isDark, + isAmoled = isAmoled, + ) + }, content = content, ) } -@Composable -@ReadOnlyComposable private fun getThemeColorScheme( + context: Context, appTheme: AppTheme, + isDark: Boolean, isAmoled: Boolean, ): ColorScheme { val colorScheme = if (appTheme == AppTheme.MONET) { - MonetColorScheme(LocalContext.current) + MonetColorScheme(context) } else { colorSchemes.getOrDefault(appTheme, TachiyomiColorScheme) } return colorScheme.getColorScheme( - isSystemInDarkTheme(), - isAmoled, + isDark = isDark, + isAmoled = isAmoled, + overrideDarkSurfaceContainers = appTheme != AppTheme.MONET, ) } 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 22dd9a0a79..4ad2bfb807 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 @@ -14,16 +14,25 @@ internal abstract class BaseColorScheme { private val surfaceContainerHigh = Color(0xFF131313) private val surfaceContainerHighest = Color(0xFF1B1B1B) - fun getColorScheme(isDark: Boolean, isAmoled: Boolean): ColorScheme { + fun getColorScheme( + isDark: Boolean, + isAmoled: Boolean, + overrideDarkSurfaceContainers: Boolean, + ): ColorScheme { if (!isDark) return lightScheme if (!isAmoled) return darkScheme - return darkScheme.copy( + val amoledScheme = darkScheme.copy( background = Color.Black, onBackground = Color.White, surface = Color.Black, onSurface = Color.White, + ) + + if (!overrideDarkSurfaceContainers) return amoledScheme + + return amoledScheme.copy( surfaceVariant = surfaceContainer, // Navigation bar background (ThemePrefWidget) surfaceContainerLowest = surfaceContainer, surfaceContainerLow = surfaceContainer, diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt index adcbaf62fa..fb670babe9 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt @@ -1,22 +1,15 @@ package eu.kanade.presentation.theme.colorscheme -import android.annotation.SuppressLint -import android.app.UiModeManager import android.app.WallpaperManager import android.content.Context -import android.graphics.Bitmap import android.os.Build import androidx.annotation.RequiresApi import androidx.compose.material3.ColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.ui.graphics.Color -import androidx.core.content.getSystemService -import com.google.android.material.color.utilities.Hct -import com.google.android.material.color.utilities.MaterialDynamicColors -import com.google.android.material.color.utilities.QuantizerCelebi -import com.google.android.material.color.utilities.SchemeContent -import com.google.android.material.color.utilities.Score +import com.materialkolor.ktx.DynamicScheme +import com.materialkolor.toColorScheme internal class MonetColorScheme(context: Context) : BaseColorScheme() { @@ -28,7 +21,7 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() { ?.primaryColor ?.toArgb() if (seed != null) { - MonetCompatColorScheme(context, seed) + MonetCompatColorScheme(Color(seed)) } else { TachiyomiColorScheme } @@ -41,19 +34,6 @@ internal class MonetColorScheme(context: Context) : BaseColorScheme() { override val lightScheme get() = monet.lightScheme - - companion object { - @Suppress("Unused") - @SuppressLint("RestrictedApi") - fun extractSeedColorFromImage(bitmap: Bitmap): Int? { - val width = bitmap.width - val height = bitmap.height - val bitmapPixels = IntArray(width * height) - bitmap.getPixels(bitmapPixels, 0, width, 0, 0, width, height) - return Score.score(QuantizerCelebi.quantize(bitmapPixels, 128), 1, 0)[0] - .takeIf { it != 0 } // Don't take fallback color - } - } } @RequiresApi(Build.VERSION_CODES.S) @@ -62,64 +42,14 @@ private class MonetSystemColorScheme(context: Context) : BaseColorScheme() { override val darkScheme = dynamicDarkColorScheme(context) } -private class MonetCompatColorScheme(context: Context, seed: Int) : BaseColorScheme() { - - override val lightScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = false) - override val darkScheme = generateColorSchemeFromSeed(context = context, seed = seed, dark = true) +internal class MonetCompatColorScheme(seed: Color) : BaseColorScheme() { + override val lightScheme = generateColorSchemeFromSeed(seed = seed, dark = false) + override val darkScheme = generateColorSchemeFromSeed(seed = seed, dark = true) companion object { - private fun Int.toComposeColor(): Color = Color(this) - - @SuppressLint("PrivateResource", "RestrictedApi") - private fun generateColorSchemeFromSeed(context: Context, seed: Int, dark: Boolean): ColorScheme { - val scheme = SchemeContent( - Hct.fromInt(seed), - dark, - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - context.getSystemService()?.contrast?.toDouble() ?: 0.0 - } else { - 0.0 - }, - ) - val dynamicColors = MaterialDynamicColors() - return ColorScheme( - primary = dynamicColors.primary().getArgb(scheme).toComposeColor(), - onPrimary = dynamicColors.onPrimary().getArgb(scheme).toComposeColor(), - primaryContainer = dynamicColors.primaryContainer().getArgb(scheme).toComposeColor(), - onPrimaryContainer = dynamicColors.onPrimaryContainer().getArgb(scheme).toComposeColor(), - inversePrimary = dynamicColors.inversePrimary().getArgb(scheme).toComposeColor(), - secondary = dynamicColors.secondary().getArgb(scheme).toComposeColor(), - onSecondary = dynamicColors.onSecondary().getArgb(scheme).toComposeColor(), - secondaryContainer = dynamicColors.secondaryContainer().getArgb(scheme).toComposeColor(), - onSecondaryContainer = dynamicColors.onSecondaryContainer().getArgb(scheme).toComposeColor(), - tertiary = dynamicColors.tertiary().getArgb(scheme).toComposeColor(), - onTertiary = dynamicColors.onTertiary().getArgb(scheme).toComposeColor(), - tertiaryContainer = dynamicColors.tertiary().getArgb(scheme).toComposeColor(), - onTertiaryContainer = dynamicColors.onTertiaryContainer().getArgb(scheme).toComposeColor(), - background = dynamicColors.background().getArgb(scheme).toComposeColor(), - onBackground = dynamicColors.onBackground().getArgb(scheme).toComposeColor(), - surface = dynamicColors.surface().getArgb(scheme).toComposeColor(), - onSurface = dynamicColors.onSurface().getArgb(scheme).toComposeColor(), - surfaceVariant = dynamicColors.surfaceVariant().getArgb(scheme).toComposeColor(), - onSurfaceVariant = dynamicColors.onSurfaceVariant().getArgb(scheme).toComposeColor(), - surfaceTint = dynamicColors.surfaceTint().getArgb(scheme).toComposeColor(), - inverseSurface = dynamicColors.inverseSurface().getArgb(scheme).toComposeColor(), - inverseOnSurface = dynamicColors.inverseOnSurface().getArgb(scheme).toComposeColor(), - error = dynamicColors.error().getArgb(scheme).toComposeColor(), - onError = dynamicColors.onError().getArgb(scheme).toComposeColor(), - errorContainer = dynamicColors.errorContainer().getArgb(scheme).toComposeColor(), - onErrorContainer = dynamicColors.onErrorContainer().getArgb(scheme).toComposeColor(), - outline = dynamicColors.outline().getArgb(scheme).toComposeColor(), - outlineVariant = dynamicColors.outlineVariant().getArgb(scheme).toComposeColor(), - scrim = Color.Black, - surfaceBright = dynamicColors.surfaceBright().getArgb(scheme).toComposeColor(), - surfaceDim = dynamicColors.surfaceDim().getArgb(scheme).toComposeColor(), - surfaceContainer = dynamicColors.surfaceContainer().getArgb(scheme).toComposeColor(), - surfaceContainerHigh = dynamicColors.surfaceContainerHigh().getArgb(scheme).toComposeColor(), - surfaceContainerHighest = dynamicColors.surfaceContainerHighest().getArgb(scheme).toComposeColor(), - surfaceContainerLow = dynamicColors.surfaceContainerLow().getArgb(scheme).toComposeColor(), - surfaceContainerLowest = dynamicColors.surfaceContainerLowest().getArgb(scheme).toComposeColor(), - ) + fun generateColorSchemeFromSeed(seed: Color, dark: Boolean): ColorScheme { + return DynamicScheme(seedColor = seed, isDark = dark) + .toColorScheme(isAmoled = false) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 26f4ca4c1b..8ba641af4d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,6 +12,7 @@ ktlint-core = "1.8.0" firebase-bom = "34.7.0" markdown = "0.39.0" junit = "6.0.1" +materialKolor = "5.0.0-alpha04" [libraries] desugar = "com.android.tools:desugar_jdk_libs:2.1.5" @@ -106,6 +107,8 @@ markdown-coil = { module = "com.mikepenz:multiplatform-markdown-renderer-coil3", stringSimilarity = { module = "com.aallam.similarity:string-similarity-kotlin", version = "0.1.0" } +materialKolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" } + [plugins] google-services = { id = "com.google.gms.google-services", version = "4.4.4" } aboutLibraries = { id = "com.mikepenz.aboutlibraries.plugin.android", version.ref = "aboutlib_version" } From 6fa041ec860733180f11bce2a25c18b9c68ea761 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+antsylich@users.noreply.github.com> Date: Fri, 26 Dec 2025 19:50:03 +0100 Subject: [PATCH 02/40] Switch to MaterialExpressiveTheme (cherry picked from commit 3e6afee13b9fd5716f7f2d547b3edcd3e17915db) --- app/build.gradle.kts | 1 + .../java/eu/kanade/presentation/theme/TachiyomiTheme.kt | 4 ++-- .../presentation/theme/colorscheme/MonetColorScheme.kt | 9 ++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a3557a7a71..2eb8530826 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -158,6 +158,7 @@ kotlin { "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", "-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi", "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + "-opt-in=androidx.compose.material3.ExperimentalMaterial3ExpressiveApi", "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", "-opt-in=coil3.annotation.ExperimentalCoilApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", diff --git a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt index f07f4d4732..5f1eb6b2dd 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.material.ripple.RippleAlpha import androidx.compose.material3.ColorScheme -import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.MaterialExpressiveTheme import androidx.compose.material3.RippleConfiguration import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable @@ -66,7 +66,7 @@ private fun BaseTachiyomiTheme( ) { val context = LocalContext.current val isDark = isSystemInDarkTheme() - MaterialTheme( + MaterialExpressiveTheme( colorScheme = remember(appTheme, isDark, isAmoled) { getThemeColorScheme( context = context, diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt index fb670babe9..039d4bd169 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/MonetColorScheme.kt @@ -8,6 +8,8 @@ import androidx.compose.material3.ColorScheme import androidx.compose.material3.dynamicDarkColorScheme import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.ui.graphics.Color +import com.materialkolor.PaletteStyle +import com.materialkolor.dynamiccolor.ColorSpec import com.materialkolor.ktx.DynamicScheme import com.materialkolor.toColorScheme @@ -48,7 +50,12 @@ internal class MonetCompatColorScheme(seed: Color) : BaseColorScheme() { companion object { fun generateColorSchemeFromSeed(seed: Color, dark: Boolean): ColorScheme { - return DynamicScheme(seedColor = seed, isDark = dark) + return DynamicScheme( + seedColor = seed, + isDark = dark, + specVersion = ColorSpec.SpecVersion.SPEC_2025, + style = PaletteStyle.Expressive, + ) .toColorScheme(isAmoled = false) } } From fa27fb8b0f4a91be8920dadee7acb446cd7116c6 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 08:58:48 +0100 Subject: [PATCH 03/40] Switch to M3E ExtendedFloatingActionButton Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../kanade/presentation/anime/AnimeScreen.kt | 86 ++++++------ .../presentation/browse/SourcesScreen.kt | 4 +- .../CategoryFloatingActionButton.kt | 4 +- .../components/FloatingActionAddButton.kt | 4 +- .../migration/anime/MigrateAnimeScreen.kt | 34 +++-- .../search/MigrateSourceSearchScreen.kt | 22 +-- .../ui/download/DownloadQueueScreen.kt | 4 +- .../migration/config/MigrationConfigScreen.kt | 4 +- .../material/FloatingActionButton.kt | 131 ------------------ .../presentation/core/util/LazyListState.kt | 27 +--- 10 files changed, 84 insertions(+), 236 deletions(-) delete mode 100644 presentation-core/src/main/java/tachiyomi/presentation/core/components/material/FloatingActionButton.kt diff --git a/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt b/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt index 974c6b87cc..d9f56c80b3 100644 --- a/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/anime/AnimeScreen.kt @@ -1,10 +1,7 @@ package eu.kanade.presentation.anime import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column @@ -29,9 +26,11 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material3.Icon +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf @@ -92,7 +91,6 @@ import tachiyomi.i18n.aniyomi.AYMR import tachiyomi.presentation.core.components.FastScrollIrregularLazyVerticalGrid import tachiyomi.presentation.core.components.Scroller.EXACT_HEIGHT_KEY_PREFIX import tachiyomi.presentation.core.components.TwoPanelBox -import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource @@ -479,27 +477,25 @@ private fun AnimeScreenSmallImpl( val isFABVisible = remember(episodes) { episodes.fastAny { !it.episode.seen } && !isAnySelected } - AnimatedVisibility( - visible = isFABVisible, - enter = fadeIn(), - exit = fadeOut(), - ) { - ExtendedFloatingActionButton( - text = { - val isWatching = remember(state.episodes) { - state.episodes.fastAny { it.episode.seen } - } - Text( - text = stringResource( - if (isWatching) MR.strings.action_resume else MR.strings.action_start, - ), - ) - }, - icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, - onClick = onContinueWatching, - expanded = itemListState.shouldExpandFAB(), - ) - } + SmallExtendedFloatingActionButton( + text = { + val isWatching = remember(state.episodes) { + state.episodes.fastAny { it.episode.seen } + } + Text( + text = stringResource( + if (isWatching) MR.strings.action_resume else MR.strings.action_start, + ), + ) + }, + icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, + onClick = onContinueWatching, + expanded = itemListState.shouldExpandFAB(), + modifier = Modifier.animateFloatingActionButton( + visible = isFABVisible, + alignment = Alignment.BottomEnd, + ), + ) }, ) { contentPadding -> val topPadding = contentPadding.calculateTopPadding() @@ -864,27 +860,25 @@ fun AnimeScreenLargeImpl( val isFABVisible = remember(episodes) { episodes.fastAny { !it.episode.seen } && !isAnySelected } - AnimatedVisibility( - visible = isFABVisible, - enter = fadeIn(), - exit = fadeOut(), - ) { - ExtendedFloatingActionButton( - text = { - val isWatching = remember(state.episodes) { - state.episodes.fastAny { it.episode.seen } - } - Text( - text = stringResource( - if (isWatching) MR.strings.action_resume else MR.strings.action_start, - ), - ) - }, - icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, - onClick = onContinueWatching, - expanded = itemListState.shouldExpandFAB(), - ) - } + SmallExtendedFloatingActionButton( + text = { + val isWatching = remember(state.episodes) { + state.episodes.fastAny { it.episode.seen } + } + Text( + text = stringResource( + if (isWatching) MR.strings.action_resume else MR.strings.action_start, + ), + ) + }, + icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, + onClick = onContinueWatching, + expanded = itemListState.shouldExpandFAB(), + modifier = Modifier.animateFloatingActionButton( + visible = isFABVisible, + alignment = Alignment.BottomEnd, + ), + ) }, ) { contentPadding -> PullRefresh( diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index 6fae10b5df..21574ff88b 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -17,11 +17,11 @@ import androidx.compose.material.icons.outlined.Settings import androidx.compose.material.icons.outlined.SwapCalls import androidx.compose.material.icons.outlined.TravelExplore import androidx.compose.material3.AlertDialog -import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -115,7 +115,7 @@ fun SourcesScreen( floatingActionButton = { val buttonText = if (updateCount != 0) MR.strings.ext_update else MR.strings.ext_install val buttonIcon = if (updateCount != 0) Icons.Filled.Upload else Icons.Filled.Download - ExtendedFloatingActionButton( + SmallExtendedFloatingActionButton( text = { Text(text = stringResource(buttonText)) }, icon = { Icon(imageVector = buttonIcon, contentDescription = null) }, onClick = { toExtensionsScreen() }, 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 a151e9b2fb..bb246b066c 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 @@ -4,11 +4,11 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.Icon +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable 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.shouldExpandFAB @@ -18,7 +18,7 @@ fun CategoryFloatingActionButton( onCreate: () -> Unit, modifier: Modifier = Modifier, ) { - ExtendedFloatingActionButton( + SmallExtendedFloatingActionButton( text = { Text(text = stringResource(MR.strings.action_add)) }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) }, onClick = onCreate, diff --git a/app/src/main/java/eu/kanade/presentation/components/FloatingActionAddButton.kt b/app/src/main/java/eu/kanade/presentation/components/FloatingActionAddButton.kt index 0bf59fbe18..3777fc7649 100644 --- a/app/src/main/java/eu/kanade/presentation/components/FloatingActionAddButton.kt +++ b/app/src/main/java/eu/kanade/presentation/components/FloatingActionAddButton.kt @@ -5,11 +5,11 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.Icon +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable 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.shouldExpandFAB @@ -19,7 +19,7 @@ fun FloatingActionAddButton( onClick: () -> Unit, modifier: Modifier = Modifier, ) { - ExtendedFloatingActionButton( + SmallExtendedFloatingActionButton( text = { Text(text = stringResource(MR.strings.action_add)) }, icon = { Icon(imageVector = Icons.Outlined.Add, contentDescription = null) }, onClick = onClick, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrateAnimeScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrateAnimeScreen.kt index b447c889a6..0e3e6b6279 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrateAnimeScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/anime/MigrateAnimeScreen.kt @@ -9,11 +9,14 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowForward import androidx.compose.material3.Icon +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text +import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import cafe.adriel.voyager.core.model.rememberScreenModel @@ -29,7 +32,6 @@ import mihon.feature.migration.config.MigrationConfigScreen import tachiyomi.domain.anime.model.Anime import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.FastScrollLazyColumn -import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen @@ -75,20 +77,22 @@ data class MigrateAnimeScreen( ) }, floatingActionButton = { - if (state.selectionMode) { - ExtendedFloatingActionButton( - text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) }, - icon = { - Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) - }, - onClick = { - val selection = state.selection - screenModel.clearSelection() - navigator.push(MigrationConfigScreen(selection)) - }, - expanded = lazyListState.shouldExpandFAB(), - ) - } + SmallExtendedFloatingActionButton( + text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) }, + icon = { + Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) + }, + onClick = { + val selection = state.selection + screenModel.clearSelection() + navigator.push(MigrationConfigScreen(selection)) + }, + expanded = lazyListState.shouldExpandFAB(), + modifier = Modifier.animateFloatingActionButton( + visible = state.selectionMode, + alignment = Alignment.BottomEnd, + ), + ) }, ) { contentPadding -> if (state.isEmpty) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt index 8cc5d959b8..f32ab31ace 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSourceSearchScreen.kt @@ -1,17 +1,20 @@ package eu.kanade.tachiyomi.ui.browse.migration.search -import androidx.compose.animation.AnimatedVisibility import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material3.Icon +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text +import androidx.compose.material3.animateFloatingActionButton import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalUriHandler import cafe.adriel.voyager.core.model.rememberScreenModel @@ -35,7 +38,6 @@ import mihon.presentation.core.util.collectAsLazyPagingItems import tachiyomi.core.common.Constants import tachiyomi.domain.anime.model.Anime import tachiyomi.i18n.MR -import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.LoadingScreen @@ -74,13 +76,15 @@ data class MigrateSourceSearchScreen( ) }, floatingActionButton = { - AnimatedVisibility(visible = state.filters.isNotEmpty()) { - ExtendedFloatingActionButton( - text = { Text(text = stringResource(MR.strings.action_filter)) }, - icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) }, - onClick = screenModel::openFilterSheet, - ) - } + SmallExtendedFloatingActionButton( + text = { Text(text = stringResource(MR.strings.action_filter)) }, + icon = { Icon(Icons.Outlined.FilterList, contentDescription = null) }, + onClick = screenModel::openFilterSheet, + modifier = Modifier.animateFloatingActionButton( + visible = state.filters.isNotEmpty(), + alignment = Alignment.BottomEnd, + ), + ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, ) { paddingValues -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt index ce075b79d5..2db02cf39d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/download/DownloadQueueScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material.icons.outlined.Pause import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -57,7 +58,6 @@ import tachiyomi.core.common.util.lang.launchUI import tachiyomi.i18n.MR import tachiyomi.i18n.aniyomi.AYMR import tachiyomi.presentation.core.components.Pill -import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen @@ -208,7 +208,7 @@ object DownloadQueueScreen : Screen() { exit = fadeOut(), ) { val isRunning by screenModel.isDownloaderRunning.collectAsState() - ExtendedFloatingActionButton( + SmallExtendedFloatingActionButton( text = { // AY --> val id = if (isRunning) { diff --git a/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt b/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt index 2cd031df16..6ab254864f 100644 --- a/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt +++ b/app/src/main/java/mihon/feature/migration/config/MigrationConfigScreen.kt @@ -20,6 +20,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.ListItemDefaults import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallExtendedFloatingActionButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState @@ -61,7 +62,6 @@ import tachiyomi.domain.source.service.SourceManager import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.Pill -import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource @@ -143,7 +143,7 @@ class MigrationConfigScreen(private val animeIds: Collection) : Screen() { ) }, floatingActionButton = { - ExtendedFloatingActionButton( + SmallExtendedFloatingActionButton( text = { Text(text = stringResource(MR.strings.migrationConfigScreen_continueButtonText)) }, icon = { Icon(imageVector = Icons.AutoMirrored.Outlined.ArrowForward, contentDescription = null) }, onClick = { diff --git a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/FloatingActionButton.kt b/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/FloatingActionButton.kt deleted file mode 100644 index c43a6849da..0000000000 --- a/presentation-core/src/main/java/tachiyomi/presentation/core/components/material/FloatingActionButton.kt +++ /dev/null @@ -1,131 +0,0 @@ -package tachiyomi.presentation.core.components.material - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.CubicBezierEasing -import androidx.compose.animation.core.animateDpAsState -import androidx.compose.animation.core.tween -import androidx.compose.animation.expandHorizontally -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.shrinkHorizontally -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.sizeIn -import androidx.compose.material3.FloatingActionButton -import androidx.compose.material3.FloatingActionButtonDefaults -import androidx.compose.material3.FloatingActionButtonElevation -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.contentColorFor -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.unit.dp - -/** - * ExtendedFloatingActionButton with custom transition between collapsed/expanded state. - * - * @see androidx.compose.material3.ExtendedFloatingActionButton - */ -@Composable -fun ExtendedFloatingActionButton( - text: @Composable () -> Unit, - icon: @Composable () -> Unit, - onClick: () -> Unit, - modifier: Modifier = Modifier, - expanded: Boolean = true, - interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - shape: Shape = MaterialTheme.shapes.large, - containerColor: Color = MaterialTheme.colorScheme.primaryContainer, - contentColor: Color = contentColorFor(containerColor), - elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), -) { - FloatingActionButton( - modifier = modifier, - onClick = onClick, - interactionSource = interactionSource, - shape = shape, - containerColor = containerColor, - contentColor = contentColor, - elevation = elevation, - ) { - val minWidth by animateDpAsState( - targetValue = if (expanded) ExtendedFabMinimumWidth else FabContainerWidth, - animationSpec = tween( - durationMillis = 500, - easing = EasingEmphasizedCubicBezier, - ), - label = "minWidth", - ) - val startPadding by animateDpAsState( - targetValue = if (expanded) ExtendedFabIconSize / 2 else 0.dp, - animationSpec = tween( - durationMillis = if (expanded) 300 else 900, - easing = EasingEmphasizedCubicBezier, - ), - label = "startPadding", - ) - - Row( - modifier = Modifier - .sizeIn(minWidth = minWidth) - .padding(start = startPadding), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - ) { - icon() - AnimatedVisibility( - visible = expanded, - enter = ExtendedFabExpandAnimation, - exit = ExtendedFabCollapseAnimation, - ) { - Box(modifier = Modifier.padding(start = ExtendedFabIconPadding, end = ExtendedFabTextPadding)) { - text() - } - } - } - } -} - -private val EasingLinearCubicBezier = CubicBezierEasing(0.0f, 0.0f, 1.0f, 1.0f) -private val EasingEmphasizedCubicBezier = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f) - -private val ExtendedFabMinimumWidth = 80.dp -private val ExtendedFabIconSize = 24.0.dp -private val ExtendedFabIconPadding = 12.dp -private val ExtendedFabTextPadding = 20.dp - -private val ExtendedFabCollapseAnimation = fadeOut( - animationSpec = tween( - durationMillis = 100, - easing = EasingLinearCubicBezier, - ), -) + shrinkHorizontally( - animationSpec = tween( - durationMillis = 500, - easing = EasingEmphasizedCubicBezier, - ), - shrinkTowards = Alignment.Start, -) - -private val ExtendedFabExpandAnimation = fadeIn( - animationSpec = tween( - durationMillis = 200, - delayMillis = 100, - easing = EasingLinearCubicBezier, - ), -) + expandHorizontally( - animationSpec = tween( - durationMillis = 500, - easing = EasingEmphasizedCubicBezier, - ), - expandFrom = Alignment.Start, -) - -private val FabContainerWidth = 56.0.dp 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 0222d54928..c2dc3f3ac4 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 @@ -2,32 +2,9 @@ package tachiyomi.presentation.core.util import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.remember -@Composable -fun LazyListState.shouldExpandFAB(): Boolean { - return remember { - derivedStateOf { - (firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0) || - lastScrolledBackward || - !canScrollForward - } - } - .value -} +fun LazyListState.shouldExpandFAB(): Boolean = lastScrolledBackward || !canScrollForward || !canScrollBackward // AY --> -@Composable -fun LazyGridState.shouldExpandFAB(): Boolean { - return remember { - derivedStateOf { - (firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0) || - lastScrolledBackward || - !canScrollForward - } - } - .value -} +fun LazyGridState.shouldExpandFAB(): Boolean = lastScrolledBackward || !canScrollForward || !canScrollBackward // <-- AY From 40f3656074e5bff80752ea4e228a6aa180bb05b2 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 09:05:14 +0100 Subject: [PATCH 04/40] Cleanup extension screen search query predicate Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> --- .../browse/extension/ExtensionsScreenModel.kt | 124 +++++++++--------- 1 file changed, 61 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt index b259c8f327..4954cb80a4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt @@ -54,80 +54,47 @@ class ExtensionsScreenModel( ExtensionUiModel.Item(it, map[it.pkgName] ?: InstallStep.Idle) } } - val queryFilter: (String) -> ((Extension) -> Boolean) = { query -> - filter@{ extension -> - if (query.isEmpty()) return@filter true - query.split(",").any { _input -> - val input = _input.trim() - if (input.isEmpty()) return@any false - when (extension) { - is Extension.Available -> { - extension.sources.any { - it.name.contains(input, ignoreCase = true) || - it.baseUrl.contains(input, ignoreCase = true) || - it.id == input.toLongOrNull() - } || - extension.name.contains(input, ignoreCase = true) - } - is Extension.Installed -> { - extension.sources.any { - it.name.contains(input, ignoreCase = true) || - it.id == input.toLongOrNull() || - if (it is AnimeHttpSource) { - it.baseUrl.contains(input, ignoreCase = true) - } else { - false - } - } || - extension.name.contains(input, ignoreCase = true) - } - is Extension.Untrusted -> extension.name.contains(input, ignoreCase = true) - } - } - } - } screenModelScope.launchIO { combine( - state.map { it.searchQuery }.distinctUntilChanged().debounce(SEARCH_DEBOUNCE_MILLIS), + state.map { it.searchQuery } + .distinctUntilChanged() + .debounce(SEARCH_DEBOUNCE_MILLIS) + .map { searchQueryPredicate(it ?: "") }, currentDownloads, getExtensions.subscribe(), - ) { query, downloads, (_updates, _, _available, _untrusted) -> - val searchQuery = query ?: "" - - val itemsGroups: ItemGroups = mutableMapOf() - - val updates = _updates.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) - if (updates.isNotEmpty()) { - itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending)] = updates - } + ) { predicate, downloads, (_updates, _installed, _available, _untrusted) -> + buildMap { + val updates = _updates.filter(predicate).map(extensionMapper(downloads)) + if (updates.isNotEmpty()) { + put(ExtensionUiModel.Header.Resource(MR.strings.ext_updates_pending), updates) + } - val untrusted = _untrusted.filter(queryFilter(searchQuery)).map(extensionMapper(downloads)) - // AM (BROWSE) --> - if (untrusted.isNotEmpty()) { - itemsGroups[ExtensionUiModel.Header.Resource(MR.strings.ext_untrusted)] = untrusted - } - // <-- AM (BROWSE) - - val languagesWithExtensions = _available - .filter(queryFilter(searchQuery)) - .groupBy { it.lang } - .toSortedMap(LocaleHelper.comparator) - .map { (lang, exts) -> - ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to - exts.map(extensionMapper(downloads)) + val untrusted = _untrusted.filter(predicate).map(extensionMapper(downloads)) + // AM (BROWSE) --> + if (untrusted.isNotEmpty()) { + put(ExtensionUiModel.Header.Resource(MR.strings.ext_untrusted), untrusted) + } + // <-- AM (BROWSE) + + val languagesWithExtensions = _available + .filter(predicate) + .groupBy { it.lang } + .toSortedMap(LocaleHelper.comparator) + .map { (lang, exts) -> + ExtensionUiModel.Header.Text(LocaleHelper.getSourceDisplayName(lang, context)) to + exts.map(extensionMapper(downloads)) + } + if (languagesWithExtensions.isNotEmpty()) { + putAll(languagesWithExtensions) } - if (languagesWithExtensions.isNotEmpty()) { - itemsGroups.putAll(languagesWithExtensions) } - - itemsGroups } - .collectLatest { + .collectLatest { items -> mutableState.update { state -> state.copy( isLoading = false, - items = it, + items = items, ) } } @@ -144,6 +111,37 @@ class ExtensionsScreenModel( .launchIn(screenModelScope) } + + fun searchQueryPredicate(query: String): (Extension) -> Boolean { + val subqueries = query.split(",") + .map { it.trim() } + .filterNot { it.isBlank() } + + if (subqueries.isEmpty()) return { true } + + return { extension -> + subqueries.any { subquery -> + if (extension.name.contains(subquery, ignoreCase = true)) return@any true + + when (extension) { + is Extension.Installed -> extension.sources.any { source -> + source.name.contains(subquery, ignoreCase = true) || + (source as? AnimeHttpSource)?.baseUrl?.contains(subquery, ignoreCase = true) == true || + source.id == subquery.toLongOrNull() + } + + is Extension.Available -> extension.sources.any { + it.name.contains(subquery, ignoreCase = true) || + it.baseUrl.contains(subquery, ignoreCase = true) || + it.id == subquery.toLongOrNull() + } + + else -> false + } + } + } + } + fun search(query: String?) { mutableState.update { it.copy(searchQuery = query) @@ -227,7 +225,7 @@ class ExtensionsScreenModel( } } -typealias ItemGroups = MutableMap> +typealias ItemGroups = Map> object ExtensionUiModel { sealed interface Header { From 1d4e1c67f86a9f1698f398e1e82840afd5bba138 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sat, 27 Dec 2025 01:43:39 +0600 Subject: [PATCH 05/40] Remember descriptionAnnotator across composition Closes #2510 Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> (cherry picked from commit 906d6f3cdbd6f7168d9cdea0c3eb8f9c663a09c2) --- .../anime/components/AnimeInfoHeader.kt | 65 ++++++++++--------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/anime/components/AnimeInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/anime/components/AnimeInfoHeader.kt index 7ad4d27e6f..835b8ee3b0 100644 --- a/app/src/main/java/eu/kanade/presentation/anime/components/AnimeInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/anime/components/AnimeInfoHeader.kt @@ -577,44 +577,47 @@ private fun ColumnScope.AnimeContentInfo( } } -private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = markdownAnnotator( - annotate = { content, child -> - if (!loadImages && child.type == MarkdownElementTypes.IMAGE) { - val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK) +@Composable +private fun descriptionAnnotator(loadImages: Boolean, linkStyle: SpanStyle) = remember(loadImages, linkStyle) { + markdownAnnotator( + annotate = { content, child -> + if (!loadImages && child.type == MarkdownElementTypes.IMAGE) { + val inlineLink = child.findChildOfType(MarkdownElementTypes.INLINE_LINK) - val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION) - ?.getUnescapedTextInNode(content) - ?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK) - ?.findChildOfType(MarkdownTokenTypes.AUTOLINK) + val url = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_DESTINATION) ?.getUnescapedTextInNode(content) - ?: return@markdownAnnotator false + ?: inlineLink?.findChildOfType(MarkdownElementTypes.AUTOLINK) + ?.findChildOfType(MarkdownTokenTypes.AUTOLINK) + ?.getUnescapedTextInNode(content) + ?: return@markdownAnnotator false - val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE) - ?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT) - val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT) - ?.getUnescapedTextInNode(content).orEmpty() + val textNode = inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TITLE) + ?: inlineLink?.findChildOfType(MarkdownElementTypes.LINK_TEXT) + val altText = textNode?.findChildOfType(MarkdownTokenTypes.TEXT) + ?.getUnescapedTextInNode(content).orEmpty() - withLink(LinkAnnotation.Url(url = url)) { - pushStyle(linkStyle) - appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG) - append(altText) - pop() - } + withLink(LinkAnnotation.Url(url = url)) { + pushStyle(linkStyle) + appendInlineContent(MARKDOWN_INLINE_IMAGE_TAG) + append(altText) + pop() + } - return@markdownAnnotator true - } + return@markdownAnnotator true + } - if (child.type in DISALLOWED_MARKDOWN_TYPES) { - append(content.substring(child.startOffset, child.endOffset)) - return@markdownAnnotator true - } + if (child.type in DISALLOWED_MARKDOWN_TYPES) { + append(content.substring(child.startOffset, child.endOffset)) + return@markdownAnnotator true + } - false - }, - config = markdownAnnotatorConfig( - eolAsNewLine = true, - ), -) + false + }, + config = markdownAnnotatorConfig( + eolAsNewLine = true, + ), + ) +} @Composable private fun AnimeSummary( From dd8375b6f11b33660451a143bd70e8866dba518f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 6 Jan 2026 18:04:38 +0000 Subject: [PATCH 06/40] Update dependency org.jsoup:jsoup to v1.22.1 (#2826) (cherry picked from commit 47fe792ddc5a342ecc4685d57143cfb40f0682f7) --- 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 8ba641af4d..d2fd2a31d1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,7 +30,7 @@ conscrypt-android = "org.conscrypt:conscrypt-android:2.5.3" quickjs-android = { group = "com.github.zhanghai.quickjs-java", name = "quickjs-android", version = "547f5b1597" } -jsoup = "org.jsoup:jsoup:1.21.2" +jsoup = "org.jsoup:jsoup:1.22.1" disklrucache = "com.jakewharton:disklrucache:2.0.2" unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc" From 5a0f0114ff7c0cced9388b37ab3839e42f1b9df3 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 6 Jan 2026 18:14:34 +0000 Subject: [PATCH 07/40] Update dependency org.junit.jupiter:junit-jupiter to v6.0.2 (#2830) (cherry picked from commit 89c4e3bb39b7450a9aedcf9d720c74c04cd560ff) --- 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 d2fd2a31d1..9ecae7e757 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -11,7 +11,7 @@ spotless = "8.1.0" ktlint-core = "1.8.0" firebase-bom = "34.7.0" markdown = "0.39.0" -junit = "6.0.1" +junit = "6.0.2" materialKolor = "5.0.0-alpha04" [libraries] From 398c8e3c38228872bcb0921bf9b7b0c35805065d Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 09:12:58 +0100 Subject: [PATCH 08/40] Optimise MAL search queries by ~11x Previously, the app made one request for the search, and then fired off 1 request per search result to obtain additional data, such as each title's synopsis, etc. However, MAL's search allows field selection during the initial query, which will return all the data in that first response, avoiding the massive bunch of requests (and alleviating some pressure on MAL from our userbase). By combining the selected fields into one constant, I was able to also get rid of the MALUserListSearch entirely because it was redundant. This allows for a unified MALManga->TrackSearch helper, further reducing complexity. I got to my "11x" improvement because on page of search results has 10 elements, and this change turns 11 (1+10 for results) requests into 1. Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .../data/track/myanimelist/MyAnimeListApi.kt | 54 +++++++++---------- .../data/track/myanimelist/dto/MALSearch.kt | 7 +-- .../myanimelist/dto/MALUserListSearch.kt | 25 --------- 3 files changed, 30 insertions(+), 56 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALUserListSearch.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 0728b7a02d..450bc44952 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -10,15 +10,12 @@ import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALListItemStatus import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALSearchResult import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUser -import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALUserSearchResult import eu.kanade.tachiyomi.network.DELETE import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.util.PkceUtil -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll import kotlinx.serialization.json.Json import okhttp3.FormBody import okhttp3.Headers @@ -78,14 +75,14 @@ class MyAnimeListApi( // MAL API throws a 400 when the query is over 64 characters... .appendQueryParameter("q", query.take(64)) .appendQueryParameter("nsfw", "true") + .appendQueryParameter("fields", SEARCH_FIELDS) .build() with(json) { authClient.newCall(GET(url.toString())) .awaitSuccess() .parseAs() .data - .map { async { getAnimeDetails(it.node.id) } } - .awaitAll() + .map { parseSearchItem(it.node) } } } } @@ -94,29 +91,13 @@ class MyAnimeListApi( return withIOContext { val url = "$BASE_API_URL/anime".toUri().buildUpon() .appendPath(id.toString()) - .appendQueryParameter( - "fields", - "id,title,synopsis,num_episodes,mean,main_picture,status,media_type,start_date", - ) + .appendQueryParameter("fields", SEARCH_FIELDS) .build() with(json) { authClient.newCall(GET(url.toString())) .awaitSuccess() .parseAs() - .let { - TrackSearch.create(trackId).apply { - remote_id = it.id - title = it.title - summary = it.synopsis - total_episodes = it.numEpisodes - score = it.mean - cover_url = it.covers?.large.orEmpty() - tracking_url = "https://myanimelist.net/anime/$remote_id" - publishing_status = it.status.replace("_", " ") - publishing_type = it.mediaType.replace("_", " ") - start_date = it.startDate ?: "" - } - } + .let { parseSearchItem(it) } } } } @@ -180,8 +161,7 @@ class MyAnimeListApi( val matches = myListSearchResult.data .filter { it.node.title.contains(query, ignoreCase = true) } - .map { async { getAnimeDetails(it.node.id) } } - .awaitAll() + .map { parseSearchItem(it.node) } // Check next page if there's more if (!myListSearchResult.paging.next.isNullOrBlank()) { @@ -192,10 +172,10 @@ class MyAnimeListApi( } } - private suspend fun getListPage(offset: Int): MALUserSearchResult { + private suspend fun getListPage(offset: Int): MALSearchResult { return withIOContext { val urlBuilder = "$BASE_API_URL/users/@me/animelist".toUri().buildUpon() - .appendQueryParameter("fields", "list_status{start_date,finish_date}") + .appendQueryParameter("fields", SEARCH_FIELDS) .appendQueryParameter("limit", LIST_PAGINATION_AMOUNT.toString()) if (offset > 0) { urlBuilder.appendQueryParameter("offset", offset.toString()) @@ -224,6 +204,21 @@ class MyAnimeListApi( } } + private fun parseSearchItem(searchItem: MALAnime): TrackSearch { + return TrackSearch.create(trackId).apply { + remote_id = searchItem.id + title = searchItem.title + summary = searchItem.synopsis + total_episodes = searchItem.numEpisodes + score = searchItem.mean + cover_url = searchItem.covers?.large.orEmpty() + tracking_url = "https://myanimelist.net/anime/$remote_id" + publishing_status = searchItem.status.replace("_", " ") + publishing_type = searchItem.mediaType.replace("_", " ") + start_date = searchItem.startDate ?: "" + } + } + private fun parseDate(isoDate: String): Long { return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(isoDate)?.time ?: 0L } @@ -235,7 +230,7 @@ class MyAnimeListApi( return try { val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US) outputDf.format(epochTime) - } catch (e: Exception) { + } catch (_: Exception) { null } } @@ -246,6 +241,9 @@ class MyAnimeListApi( private const val BASE_OAUTH_URL = "https://myanimelist.net/v1/oauth2" private const val BASE_API_URL = "https://api.myanimelist.net/v2" + private const val SEARCH_FIELDS = + "id,title,synopsis,num_episodes,mean,main_picture,status,media_type,start_date" + private const val LIST_PAGINATION_AMOUNT = 250 private var codeVerifier: String = "" diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALSearch.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALSearch.kt index 51ef2a6a48..7ce9a867da 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALSearch.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALSearch.kt @@ -5,14 +5,15 @@ import kotlinx.serialization.Serializable @Serializable data class MALSearchResult( val data: List, + val paging: MALSearchPaging, ) @Serializable data class MALSearchResultNode( - val node: MALSearchResultItem, + val node: MALAnime, ) @Serializable -data class MALSearchResultItem( - val id: Int, +data class MALSearchPaging( + val next: String?, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALUserListSearch.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALUserListSearch.kt deleted file mode 100644 index fad099a24b..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALUserListSearch.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.data.track.myanimelist.dto - -import kotlinx.serialization.Serializable - -@Serializable -data class MALUserSearchResult( - val data: List, - val paging: MALUserSearchPaging, -) - -@Serializable -data class MALUserSearchItem( - val node: MALUserSearchItemNode, -) - -@Serializable -data class MALUserSearchPaging( - val next: String?, -) - -@Serializable -data class MALUserSearchItemNode( - val id: Int, - val title: String, -) From f813e4304245591e20a9061e9665f0af751e5b31 Mon Sep 17 00:00:00 2001 From: "Weblate (bot)" Date: Wed, 7 Jan 2026 09:13:21 +0100 Subject: [PATCH 09/40] Translations update from Hosted Weblate (#2806) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ca/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ceb/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/fil/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/it/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon-plurals/ru/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ar/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/bn/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ca/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ceb/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fil/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/fr/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/it/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/ko/ Translate-URL: https://hosted.weblate.org/projects/mihon/mihon/pt_BR/ Translation: Mihon/Mihon Translation: Mihon/Mihon Plurals Co-authored-by: Ahmed TOUCHANE Co-authored-by: Anderhale Co-authored-by: Eduard Ereza Martínez Co-authored-by: Eugene Co-authored-by: Gino Cicatiello Co-authored-by: Hiroshi Co-authored-by: Luis Antonio Co-authored-by: NormalRandomPeople (cherry picked from commit e6f72000ba62302c3e4817a5f17057e5a8d7eafc) --- .../commonMain/moko-resources/ar/strings.xml | 10 +- .../commonMain/moko-resources/bn/strings.xml | 129 ++++++++++-------- .../commonMain/moko-resources/ca/plurals.xml | 15 ++ .../commonMain/moko-resources/ca/strings.xml | 73 +++++++++- .../commonMain/moko-resources/ceb/plurals.xml | 4 + .../commonMain/moko-resources/ceb/strings.xml | 1 + .../commonMain/moko-resources/fil/plurals.xml | 4 +- .../commonMain/moko-resources/fil/strings.xml | 2 + .../commonMain/moko-resources/fr/strings.xml | 6 + .../commonMain/moko-resources/it/plurals.xml | 10 ++ .../commonMain/moko-resources/it/strings.xml | 44 +++++- .../commonMain/moko-resources/ko/strings.xml | 38 +++++- .../moko-resources/pt-rBR/plurals.xml | 9 +- .../moko-resources/pt-rBR/strings.xml | 7 +- .../commonMain/moko-resources/ru/plurals.xml | 8 +- 15 files changed, 284 insertions(+), 76 deletions(-) diff --git a/i18n/src/commonMain/moko-resources/ar/strings.xml b/i18n/src/commonMain/moko-resources/ar/strings.xml index 92daeeed5a..32186ea609 100644 --- a/i18n/src/commonMain/moko-resources/ar/strings.xml +++ b/i18n/src/commonMain/moko-resources/ar/strings.xml @@ -3,9 +3,9 @@ الاسم الفئات إدخالات المكتبة - الفصول - التعقب - التاريخ + فصول + تعقب + تاريخ اﻹعدادات قائمة التنزيلات المكتبة @@ -702,7 +702,7 @@ هذا يزيل التتبع محليًّا. حسنًا وكذلك أزله من %s - احذف ما نُزِّل + حذف التنزيلات تُزامن المكتبة انتهت مزامنة المكتبة وُجدت نتائج @@ -732,7 +732,7 @@ نفِّذ صفِّر لم يُعثَر على مترجمين - المترجم + سكانليتور احجب بعض المترجمين خيارات أكثر محدَّد diff --git a/i18n/src/commonMain/moko-resources/bn/strings.xml b/i18n/src/commonMain/moko-resources/bn/strings.xml index 8053555aaf..a297619430 100644 --- a/i18n/src/commonMain/moko-resources/bn/strings.xml +++ b/i18n/src/commonMain/moko-resources/bn/strings.xml @@ -10,7 +10,7 @@ ডাউনলোড সারি লাইব্রেরি ইতিহাস - হালনাগাদসমূহ + আপডেট ব্যাকআপ এবং পুনরুদ্ধার সেটিংস বিশোধন @@ -30,20 +30,20 @@ বুকমার্ক অধ্যায় বুকমার্ক সরান মুছুন - সংগ্রহশালা হালনাগাদ + লাইব্রেরি আপডেট সম্পাদন করুন যোগ করুন - বিভাগ যোগ করুন - বিভাগ সম্পাদন করুন - বিভাগের নতুন নামকরণ করুন - বিভাগ নির্বাচন করুন + ক্যাটাগরি অ্যাড + ক্যাটাগরি এডিট + ক্যাটাগরির নাম পরিবর্তন + ক্যাটাগরি সেট করুন মোড়ক সম্পাদনা করুন বিরতি দিন পূর্ববর্তী অধ্যায় পরবর্তী আধ্যায় পুনরায় চেষ্টা করুন সরান - পুনরারম্ভ করুন + রিজিউম ব্রাউজারে খুলুন প্রদর্শনের ধরন প্রদর্শন @@ -53,7 +53,7 @@ বাতিল সাজান ইন্সটল করুন - শেয়ার করুন + শেয়ার সংরক্ষণ করুন পুন:স্থাপন পূর্বাবস্থায় ফিরুন @@ -62,10 +62,10 @@ লোড হচ্ছে… অ্যাপটি অনুপলব্ধ সাধারণ - পাঠক - ডাউনলোডগুলো - অনুসরিত - অগ্রবর্তী + রিডার + ডাউনলোডস + ট্র্যাকিং + অ্যাডভান্সড সম্বন্ধে প্রতি সারিতে আইটেম প্রতিকৃতি @@ -98,10 +98,10 @@ সাদা কালো প্রকৃত পঠন ধরন - বাম থেকে ডানে - ডান থেকে বামে - উল্লম্ব - ওয়েবটুন + বাম থেকে ডানে (পেজড) + ডান থেকে বামে (পেজড) + উল্লম্বভাবে (পেজড) + লং স্ট্রিপ স্কেল ধরণ সম্পূর্ণ পর্দা জুড়ে প্রসারন @@ -114,7 +114,7 @@ বামে ডানে মাঝে - প্রকৃত ঘূর্ণনের ধরন + ডিফল্ট ঘূর্ণন মুক্ত লক করা পোর্ট্রেট লক করা ল্যান্ডস্কেপ @@ -151,7 +151,7 @@ ডাটাবেজ পরিষ্কার করুন আপনার সংগ্রহশালাতে যেসব মাংগা সংরক্ষিত নেই সেগুলোর ইতিহাস মুছে ফেলুন এন্ট্রিগুলো মুছে ফেলা হয়েছে - সংস্করণ + ভার্সন ক্র‍্যাশের প্রতিবেদন পাঠান বাগ ঠিক করার জন্য সাহায্য করুন। কোন সংবেদনশীল তথ্য পাঠানো হবে না প্রবেশ করুন %1$sতে @@ -162,10 +162,10 @@ অজানা ত্রুটি বিভাগের হালনাগাদ হচ্ছে আর কোনও ফলাফল নেই - স্থানীয় উৎস + লোকাল সোর্স অন্যান্য সার্বজনীন খোঁজ… - সর্বশেষ + লেটেস্ট অনুসন্ধান চলমান অজানা @@ -229,15 +229,15 @@ কোন নেটওয়ার্ক সংযোগ খুঁজে পাওয়া যায়নি ডাউনলোড বিরতি সাধারণ - স্থানান্তর করুন - এক্সটেনশন গুলো + মাইগ্রেট + এক্সটেনশন এক্সটেনশনের বিবরন হালনাগাদ ইন্সটল প্রক্রিয়াধীন ডাউনলোড হচ্ছে ইন্সটল হচ্ছে - ইন্সটল হয়েছে + ইনস্টল করা বিশ্বাস অনির্ভরযোগ্য আন ইন্সটল @@ -252,7 +252,7 @@ ক্লিপবোর্ডে কপি হয়েছে: \n%1$s যোগ করার জন্য উপাত্ত নির্বাচন করুন - স্থানান্তর + মাইগ্রেট অনুলিপি পেজড উৎস ইন্সটল করা নেই: %1$s @@ -278,23 +278,23 @@ গুণ সাহায্য কোন ফলাফল পাওয়া যায়নি - স্থানান্তর করতে একটি উৎস নির্বাচন করুন + মাইগ্রেট করার জন্য একটি সোর্স সিলেক্ট করুন পেছনে অগ্রবর্তী - রেফ্রেস করুন - সংগ্রহশালা + রিফ্রেশ + লাইব্রেরি অচল এই এক্সটেনশন আর উপলব্ধ নয়। ইমেল ঠিকানা লাইব্রেরিতে আছে - লাইব্রেরিতে রাখো + লাইব্রেরিতে যুক্ত করুন আরও কম উল্টো নির্বাচন করুন তারিখে যোগকৃত সর্বশেষ অধ্যায় তালিকা - উৎস সমূহ + সোর্স আরও প্রদর্শন এটি অ্যাপের মধ্যে প্রাপ্তবয়স্কদের (18+) বিষয়বস্তু সারফেস করা থেকে অনানুষ্ঠানিক বা সম্ভাব্য ভুলভাবে পতাকাঙ্কিত এক্সটেনশানগুলিকে বাধা দেয় না। @@ -308,7 +308,7 @@ তারিখের রীতি ডার্ক লাইট - সিস্টেমকে অনুসরণ করুন + সিস্টেম থিম নীচে সরান শীর্ষে সরান @@ -318,14 +318,14 @@ আলগা করুন আটকান অক্ষম - বিভাগের ট্যাবগুলি দেখুন + ক্যাটাগরি ট্যাবগুলি দেখান আরামপ্রদ গ্রিড - স্থানান্তর করুন - শুরু করুন + মাইগ্রেট + স্টার্ট অধ্যায়গুলি দেখুন সব অক্ষম করুন সব সক্রিয় করুন - মেটাডাটা সয়ংক্রিয়ভাবে হালনাগাদ করুন + মেটাডাটা স্বয়ংক্রিয়ভাবে রিফ্রেশ ব্যাপক আপডেট অ্যাপ পাল্টানোর সময় অ্যাপের কন্টেন্ট লুকান ও স্ক্রিনসট ব্লক করুন সেটিংসে খুঁজুন @@ -351,7 +351,7 @@ পড়া হচ্ছে পঠন ধরন সবসময় অধ্যায় পরিবর্তন দেখান - একটানা উল্লম্ব + ফাঁকসহ লং স্ট্রিপ পঠন ধরন দেখান ধূসর ফিলটারড অধ্যায় গুলো এড়িয়ে যান @@ -360,7 +360,7 @@ ১৮+ অনিষ্পন্ন হালনাগাদ সংগ্রহশালা হালনাগেদের সময় নতুন মোড়ক এবং বর্ণনা খুঁজুন - গাঁথুনিকৃত + পিন করা ট্র্যাকারগুলিতে প্রবেশ হয়নি: রিডার খোলা থাকলে বর্তমান মোড সংক্ষেপে দেখান উভয় @@ -393,11 +393,11 @@ এছাড়াও আমার সংগ্রহশালার মাঙ্গাতে প্রয়োগ করুন অধ্যায় সেটিংস আপলোডের তারিখ অনুযায়ী - সংগ্রহশালার মাঙ্গার মোড়ক হালনাগাদ করুন + লাইব্রেরির কভারগুলো রিফ্রেশ অজানা অবস্থা অজানা লেখক আপনার কোন পিন করা উৎস নেই - স্থানীয় উৎস নির্দেশিকা + লোকাল সোর্স গাইড \"%1$s\" সার্বজনীনভাবে খুঁজুন সর্বশেষ ব্যবহৃত ট্যাব গুলি @@ -406,12 +406,12 @@ আপনি এখন প্রস্থান করেছেন প্রস্থান প্রস্থান করবেন %1$s থেকে? - আপনার লাইব্রেরীর সব মাঙ্গা ফিলটার করে + লাইব্রেরির সব এন্ট্রি ফিল্টার হবে পড়ার ইতিহাসে বিরতি দেয় ছদ্মবেশী মোড - কেবল ডাউনলোড হয়েছে এমন - হালনাগাদ করা হয়েছে v%1$s তে - হালনাগাদ এর জন্য অনুসন্ধান করুন + শুধু ডাউনলোড + v%1$s এ আপডেট হয়েছে + সংস্করণ যাচাই ওপেন সোর্স লাইসেন্স নতুন কি ওয়েবসাইট @@ -459,7 +459,7 @@ ছদ্মবেশী মোড নিষ্ক্রিয় করুন কিছু নির্মাতাদের অতিরিক্ত অ্যাপ সীমাবদ্ধতা রয়েছে যা ব্যাকগ্রাউন্ড পরিষেবাগুলিকে হত্যা করে। এই ওয়েবসাইটে কিভাবে এটি ঠিক করা যায় সে সম্পর্কে আরও তথ্য রয়েছে। পটভূমি কার্যকলাপ - ডাম্প ক্র্যাশ লগ + ক্র্যাশ লগ শেয়ার এইচটিটিপিএস এর উপর ডিএনএস এমআইইউআই অপ্টিমাইজেশন নিষ্ক্রিয় থাকলে ব্যাকআপ/রিস্টোর সঠিকভাবে কাজ নাও করতে পারে। সম্ভবত অনুপস্থিত এক্সটেনশন ইনস্টল করতে হবে এবং ট্র্যাকিং সার্ভিসগুলোতে পরে লগইন করতে হবে এগুলো ব্যবহার করতে। @@ -476,7 +476,7 @@ স্ক্রলে মেনু লুকানোর জন্য সংবেদনশীলতা ভূদৃশ্য প্রতিকৃতি - ঘূর্ণনের ধরন + ঘূর্ণন ডান বাম পরবর্তী @@ -515,7 +515,7 @@ এখনই ডাউনলোড শুরু করুন সকল ত্রুটি দেখুন এই সিরিজের সব বাতিল করুন - স্থানীয় উৎস + লোকাল সোর্স আপনার এখনও কোন বিভাগ নেই | এই অ্যান্ড্রয়েড সংস্করণটি আর সমর্থিত নয় পরিবর্তন নিশ্চিত করতে প্রমাণীকরণ করুন @@ -554,13 +554,13 @@ %1$d আপডেট(গুলি) ব্যর্থ হয়েছে সম্পূর্ণ প্রকাশিত সর্বশেষ হালনাগাদ চেক - আপনি কি \"%s\" বিভাগটি মুছে ফেলতে চান? + আপনি কি “%s” ক্যাটাগরিটি মুছে ফেলতে চান? সম্প্রতি আপনার হালনাগাদকৃত মাঙ্গা দেখুন আপনি কি নিশ্চিত? অপঠিত সংখ্যা - সবকিছু মুছুন + সবকিছু মুছে ফেলুন লেখা দেখান - বিভাগ মুছুন + ক্যাটাগরি ডিলিট ল্যাভেণ্ডার খুঁজুন… শুধু প্রচ্ছদসহ গ্রিড @@ -583,15 +583,15 @@ ম্যানুয়াল ও সয়ংক্রিয় ব্যাকআপ অ্যাপ লক,নিরাপদ পর্দা অপঠিত অধ্যায় থাকায় এড়িয়ে যাওয়া হয়েছে - স্থানীয় + লোকাল ক্লিপবোর্ডে কপি করুন সমস্ত পাঠক সেটিংস আবার পরিসংখ্যান ডাউনলোড হয়েছে শুরু হয়েছে অ্যাপ লক চালু থাকলে Widget পাওয়া যায় না - বিভাগ আপডেট করুন - এলোমেলো এন্ট্রি খুলুন + ক্যাটাগরি আপডেট + যেকোনো র্যান্ডম এন্ট্রি পড়া চালিয়ে যান বোতাম ডাউনলোড চেক করা হচ্ছে পৃষ্ঠার ফাইল পাথ খুঁজে পাওয়া যায়নি %d @@ -607,7 +607,7 @@ নট সিলেক্টেড স্ক্যানলেটর নেভিগেট আপ - ডাটা অন স্টোরেজ + ডাটা অ্যান্ড স্টোরেজ কাস্টমাইজড আনার ব্যবধান ফোল্ডার নির্বাচন করুন অনবর্ডিং গাইড @@ -626,7 +626,7 @@ ইন্টারনাল ইরর: বাকি ইনফরমেশন পেতে ক্যাশ লগ দেখুন স্কিপ করা হয়েছে কারণ আগে কোন চ্যাপ্টার পড়া হয়নি ট্রেকার স্কোর - ঠিক আছে + ওকে পরবর্তী আপডেটের সম্ভাব্য সময় লিংক কপি করুন স্বয়ংক্রিয় @@ -700,7 +700,7 @@ সরান এক্সটেনশন সরাতে চান? আপনি কি নিশ্চিত \"%s\" এক্সটেনশন সরাতে চান? - ভার্সন + সংস্করণ ভাষা বয়স সীমা এক্সটেনশন ইনস্টল করতে অনুমতি লাগবে। অনুমতি দিতে এখানে ট্যাপ করুন। @@ -756,7 +756,7 @@ নিষ্ক্রিয় চওড়া ছবি প্যান হবে অটোমেটিক জুম হবে চওড়া ছবিতে - পোর্ট্রেট উল্টানো হবে + উল্টো প্রতিকৃতি জুম আউট বন্ধ স্টোরেজ লোকেশন সেট নেই ভুল লোকেশন: %s @@ -826,4 +826,23 @@ এটি %s থেকে পূর্বে নির্বাচিত শুরুর তারিখ মুছে ফেলবে এটি %s থেকে পূর্বে নির্বাচিত শেষ তারিখ মুছে ফেলবে %s ট্র্যাকিং মুছে ফেলা হবে? + লাইব্রেরি সর্বশেষ আপডেট: %s + আসন্ন আপডেটগুলো দেখুন + আসন্ন গাইড + আগামী মাস + গত মাস + এটি লোকাল ট্র্যাকিং থেকে মুছে যাবে। + সর্বশেষ: %1$s + অজানা + লাইব্রেরিতে মিল থাকা নামে এন্ট্রি রয়েছে।\n\nমাইগ্রেট করার জন্য একটি এন্ট্রি সিলেক্ট করুন অথবা তবুও অ্যাড করুন। + মাইগ্রেট + মাইগ্রেট করবেন না + এখনই মাইগ্রেট + কপি + মাইগ্রেট + ক্যানসেল + এই মাঙ্গাটি সম্পূর্ণ হয়েছে, অথবা পরবর্তী প্রকাশের কোনো সম্ভাব্য তারিখ নেই। + ডিফল্ট ইউজার এজেন্ট স্ট্রিং রিসেট + ডাউনলোড করা অধ্যায়গুলো জোরপূর্বক পুনরায় যাচাই হবে + ডাউনলোড ইনডেক্স অকার্যকর হয়েছে diff --git a/i18n/src/commonMain/moko-resources/ca/plurals.xml b/i18n/src/commonMain/moko-resources/ca/plurals.xml index db6f57a18c..e485426e8e 100644 --- a/i18n/src/commonMain/moko-resources/ca/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ca/plurals.xml @@ -95,4 +95,19 @@ %1$s pàgines %1$s pàgines + + Voleu migrar %1$d element? + Voleu migrar %1$d elements? + Voleu migrar %1$d elements? + + + Voleu copiar %1$d element? + Voleu copiar %1$d elements? + Voleu copiar %1$d elements? + + + S’ha omès un element + S’han omès %1$d elements + S’han omès %1$d elements + diff --git a/i18n/src/commonMain/moko-resources/ca/strings.xml b/i18n/src/commonMain/moko-resources/ca/strings.xml index 1124631a0f..d37917a4de 100644 --- a/i18n/src/commonMain/moko-resources/ca/strings.xml +++ b/i18n/src/commonMain/moko-resources/ca/strings.xml @@ -333,7 +333,7 @@ Inverteix la selecció Fixades Fixa - Segueix + Afegeix un seguiment Espaiat lateral Tira vertical amb separació Desfixa @@ -679,8 +679,8 @@ Copia al porta-retalls Actualitza la categoria Divideix les imatges altes - Acció en lliscar cap a la dreta - Acció en lliscar cap a l’esquerra + En lliscar cap a la dreta en un capítol + En lliscar cap a l’esquerra en un capítol Toca dues vegades per a ampliar Superposició Inverteix l’orientació de les pàgines amples rotades @@ -833,4 +833,71 @@ Monocrom Llista de la biblioteca Segueix de manera privada + Dóna + Capítols no llegits + Notes + Edita les notes + Tanca la pestanya + Gatputxino + Renderitza les imatges a les descripcions dels mangues + Comportament + Marca els capítols duplicats de llegits com a llegits + Després de llegir un capítol + Després d’obtenir un capítol nou + Amaga els indicadors de capítols que manquen + No permetis noms de fitxer que no siguin ASCII + Assegura la compatibiloitat amb alguns mitjans d’emmagatzematge que no admeten Unicode. Quan ho activeu, caldrà que canvieu de nom manualment les carpetes de les fonts i dels mangues substituint-ne els caràcters que no siguin ASCII per llurs representacions hexadecimals UTF-8 en minúscules. No cal que canvieu de nom els fitxers dels capítols. + Utilitza el decodificador antic per al lector de tira llarga + No s’ha pogut crear el directori de baixada + No s’ha pogut crear el directori: %s + Baixades concurrents de fonts + Baixades concurrents de pàgines + Pàgines baixades simultàniament per font + Suprimireu elements de la base de dades + Els capítols llegits i el progrés dels elements que no siguin a la biblioteca es perdran + Conserva els elements amb capítols llegits + Actualitza els títols dels mangues de la biblioteca perquè encaixin amb la font + Advertència: si un manga canvia de nom, se suprimirà de la cua de baixades (si hi és). + S’està iniciant la sessió… + Possibles duplicats + Ja teniu elements a la biblioteca amb un nom similar.\n\nSeleccioneu un element per a migrar-lo o afegiu-lo igualment. + M’ha agradat la part en què… + Seleccionats + Disponibles + Selecciona-ho tot + No seleccionis res + Selecciona les fonts activades + Selecciona les fonts fixades + Continua + Dades que es migraran + Suprimeix les baixades de l’element actual després de la migració + Paraules clau addicionals (opcional) + Ajuda a acotar els resultats de la cerca afegint-hi paraules clau addicionals + Amaga els elements sense coincidències + Amaga els elements sense capítols nous + Mostra un element només si la coincidència té capítols addicionals + Aquestes opcions són lentes i perilloses, i poden implicar restriccions per part de les fonts + Mode de cerca avançat + Parteix el títol en paraules clau per a una cerca més àmplia + Cerca coincidències basant-se en el número de capítol + Si ho activeu, cerca la coincidència de més endavant. En cas contrari, agafa la primera coincidència per prioritat de les fonts. + Migració + S’està migrant (%1$d/%2$d) + Copia + Migra + No s’ha trobat cap alternativa + Darrer: %1$s + Desconegut + Cerca manualment + No migris + Migra ara + Copia ara + Voleu aturar la migració? + Atura + Cancel·la + Copia + Migra + Cancel·la + Cancel·la + No s’ha trobat cap capítol, aquest element no es pot utilitzar per a migrar diff --git a/i18n/src/commonMain/moko-resources/ceb/plurals.xml b/i18n/src/commonMain/moko-resources/ceb/plurals.xml index 111de37f15..71880a9247 100644 --- a/i18n/src/commonMain/moko-resources/ceb/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ceb/plurals.xml @@ -20,4 +20,8 @@ %1$s ang nahibilin %1$s ang nahibilin + + Ugma + Sa %1$d ka adlaw + diff --git a/i18n/src/commonMain/moko-resources/ceb/strings.xml b/i18n/src/commonMain/moko-resources/ceb/strings.xml index c79f8f0e60..05e96a8894 100644 --- a/i18n/src/commonMain/moko-resources/ceb/strings.xml +++ b/i18n/src/commonMain/moko-resources/ceb/strings.xml @@ -442,4 +442,5 @@ Dili karon Gikinahanlan ang WebView alang sa Mihon *gikinahanlan + Napili diff --git a/i18n/src/commonMain/moko-resources/fil/plurals.xml b/i18n/src/commonMain/moko-resources/fil/plurals.xml index f7ca20c512..5cf2086a3f 100644 --- a/i18n/src/commonMain/moko-resources/fil/plurals.xml +++ b/i18n/src/commonMain/moko-resources/fil/plurals.xml @@ -85,7 +85,7 @@ Kopyahin ang %1$d na mga entry? - Mayroong entry ay nalaktawan - %1$d na mga entry ay nalaktawan + Isang entry ang nilaktawan + %1$d (na) entry ang nilaktawan diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index 91c27c05fc..78991eb567 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -907,4 +907,6 @@ Kasabay na pag-download ng pahina Mga pahina na nai-download nang sabay-sabay kada source Isara ang tab + Catppuccin + Maaaring Magamit diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index ce7568179d..664699d89e 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -903,4 +903,10 @@ Annuler Donation Rendre les images dans les descriptions de mangas + Fermer l\'onglet + Interdire les noms de fichiers non ASCII + Assure la compatibilité avec certains supports de stockage qui ne prennent pas en charge Unicode. Lorsque cette option est activée, vous devrez renommer manuellement les dossiers source et manga en remplaçant les caractères non ASCII par leur représentation hexadécimale UTF-8 en minuscules. Les fichiers de chapitre n\'ont pas besoin d\'être renommés. + Téléchargements simultanés de sources + Téléchargements simultanés de pages + Pages téléchargées simultanément par source diff --git a/i18n/src/commonMain/moko-resources/it/plurals.xml b/i18n/src/commonMain/moko-resources/it/plurals.xml index df452a6942..2e84ecf0af 100644 --- a/i18n/src/commonMain/moko-resources/it/plurals.xml +++ b/i18n/src/commonMain/moko-resources/it/plurals.xml @@ -100,4 +100,14 @@ Migrare %1$d voci? Migrare %1$d voci? + + Copia %1$d voce? + Copia %1$d voci? + Copia %1$d voci? + + + Una voce è stata saltata + %1$d voci sono state saltate + %1$d voci sono state saltate + diff --git a/i18n/src/commonMain/moko-resources/it/strings.xml b/i18n/src/commonMain/moko-resources/it/strings.xml index 5eab673cd6..8c0e44ee9f 100644 --- a/i18n/src/commonMain/moko-resources/it/strings.xml +++ b/i18n/src/commonMain/moko-resources/it/strings.xml @@ -216,7 +216,7 @@ Fonte locale Eliminare i capitoli scaricati? In pausa - Tracking + Tracciamento Aggiungere alla libreria? Download in pausa Migra @@ -668,7 +668,7 @@ Punteggio medio %d o Totale - Trackers + Tracciatori %d s %d g Non ora @@ -869,4 +869,44 @@ Creazione della cartella download fallita Creazione della cartella %s fallita Mi è piaciuta la parte dove… + Dona + Chiudi scheda + Renderizza immagini nella descrizioni dei manga + Nascondi gli indicatori dei capitoli mancanti + Non consentire nomi di file non ASCII + Garantisce la compatibilità con alcuni supporti di memorizzazione che non supportano Unicode. Quando questa opzione è abilitata, sarà necessario rinominare manualmente le cartelle sorgente e manga sostituendo i caratteri non ASCII con le relative rappresentazioni esadecimali UTF-8 minuscole. Non è necessario rinominare i file dei capitoli. + Download da fonti simultanee + Download da pagine simultanee + Pagine scaricate simultaneamente per fonte + Dati da migrare + Elimina i download della voce corrente dopo la migrazione + Parole chiave aggiuntive (facoltative) + Aiuta a restringere i risultati della ricerca aggiungendo parole chiave aggiuntive + Nascondi le voci senza corrispondenza + Nascondi le voci senza nuovi capitoli + Mostra la voce solo se ha capitoli aggiuntivi + Queste opzioni sono lente e pericolose e possono comportare restrizioni da parte delle fonti + Modalità di ricerca avanzata + Suddivide il titolo in parole chiave per una ricerca più ampia + Corrispondenza in base al numero del capitolo + Se abilitato, sceglie l\'ultima corrispondenza. Altrimenti, sceglie la prima corrispondenza in base alla priorità della fonte. + Migrazione + Migrazione (%1$d/%2$d) + Copia + Migra + Nessuna alternativa trovata + Ultimo: %1$s + Sconosciuto + Cerca manualmente + Non migrare + Migra ora + Copia ora + Interrompere la migrazione? + Interrompi + Annulla + Copia + Migra + Annulla + Annulla + Nessun capitolo trovato, questa voce non può essere utilizzata per la migrazione diff --git a/i18n/src/commonMain/moko-resources/ko/strings.xml b/i18n/src/commonMain/moko-resources/ko/strings.xml index 25f2fed426..bdc7e8556d 100644 --- a/i18n/src/commonMain/moko-resources/ko/strings.xml +++ b/i18n/src/commonMain/moko-resources/ko/strings.xml @@ -306,7 +306,7 @@ MIUI 최적화가 꺼져 있을 경우 백업/복원 기능이 정상 작동하지 않을 수 있습니다. 복원이 이미 진행중 입니다 앱을 재시작한 후에 적용됩니다 - DNS over HTTPS (DoH) + HTTPS를 통한 DNS (DoH) 데이터 백업이 이미 진행중입니다 백업 복원 실패 @@ -872,4 +872,40 @@ 동시 소스 다운로드 동시 페이지 다운로드 소스별 동시에 다운로드된 페이지 수 + 인상 깊었던 부분은… + 사용 가능 + 활성화된 소스 선택 + 고정된 소스 선택 + 계속 + 마이그레이션할 데이터 + 마이그레이션 후 현재 항목 다운로드 삭제 + 추가 키워드 (선택사항) + 추가 키워드를 입력하면 검색 결과를 좁히는 데 도움이 됩니다 + 일치하지 않는 항목 숨기기 + 새로운 챕터가 없는 항목 숨기기 + 매칭된 항목에 새로운 챕터가 있을 때만 표시 + 이 옵션들은 속도가 느리고 위험할 수 있으며, 소스로부터 제한을 받을 수 있습니다 + 고급 검색 모드 + 제목을 키워드로 분석해 검색 범위를 넓힙니다 + 챕터 번호 기준 매칭 + 활성화된 경우 가장 진도가 나간 매칭을 선택합니다. 그렇지 않으면 소스 우선순위에 따라 첫 번째 매칭을 선택합니다. + 마이그레이션 + 마이그레이션 (%1$d/%2$d) + 복사 + 마이그레이션 + 대안을 찾을 수 없습니다 + 최신: %1$s + 알 수 없음 + 수동 검색 + 마이그레이션하지 않기 + 지금 마이그레이션 + 지금 복사 + 마이그레이션을 중지하시겠습니까? + 중지 + 취소 + 복사 + 마이그레이션 + 취소 + 취소 + 챕터를 찾을 수 없어 이 항목은 마이그레이션에 사용할 수 없습니다 diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml index f83c2407bc..e55abba583 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/plurals.xml @@ -93,16 +93,21 @@ Migrar %1$d entrada? Migrar %1$d entradas? - + Copiar %1$d entrada? Copiar %1$d entradas? - + Uma entrada foi ignorada %1$d entradas foram ignoradas + + + + 1 página + %1$s páginas diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml index 57da5b4ac9..9bb51d4df3 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -65,7 +65,7 @@ Monitoramento Avançado Sobre - Tamanho da grade + Itens por linha Retrato Paisagem Atualizações automáticas @@ -687,7 +687,7 @@ Rotacionar páginas largas para caber Inverter a orientação das páginas largas rotacionadas Informações de depuração - Ação de deslizar para a direita + Capitulo ao deslizar para a direita Ação de deslizar para a esquerda Toque duplo para dar zoom Próxima atualização esperada @@ -901,4 +901,7 @@ Editar anotações Anotações Catppuccin + Doar + Fechar aba + Renderizar imagens em descrições de mangá diff --git a/i18n/src/commonMain/moko-resources/ru/plurals.xml b/i18n/src/commonMain/moko-resources/ru/plurals.xml index 945456e523..d9ff94cf45 100644 --- a/i18n/src/commonMain/moko-resources/ru/plurals.xml +++ b/i18n/src/commonMain/moko-resources/ru/plurals.xml @@ -61,10 +61,10 @@ %d сервисов отслеживания - Отсутствует %d глава - Отсутствуют %d главы - Отсутствуют %d глав - Отсутствуют %d глав + Отсутствует %d глава в источнике или была отфильтрована + Отсутствуют %d главы в источнике или были отфильтрованы + Отсутствуют %d глав в источнике или были отфильтрованы + Отсутствуют %d глав в источнике или были отфильтрованы Вчера From dd6a1b7d307cbd458a43fc4ad2d81014f616472a Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 09:18:15 +0100 Subject: [PATCH 10/40] Add studios to MAL search results Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .../tachiyomi/data/track/myanimelist/MyAnimeListApi.kt | 3 ++- .../kanade/tachiyomi/data/track/myanimelist/dto/MALAnime.kt | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt index 450bc44952..0f1b19fe3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/MyAnimeListApi.kt @@ -216,6 +216,7 @@ class MyAnimeListApi( publishing_status = searchItem.status.replace("_", " ") publishing_type = searchItem.mediaType.replace("_", " ") start_date = searchItem.startDate ?: "" + authors = searchItem.studios.map { it.name } } } @@ -242,7 +243,7 @@ class MyAnimeListApi( private const val BASE_API_URL = "https://api.myanimelist.net/v2" private const val SEARCH_FIELDS = - "id,title,synopsis,num_episodes,mean,main_picture,status,media_type,start_date" + "id,title,synopsis,num_episodes,mean,main_picture,status,media_type,start_date,studios" private const val LIST_PAGINATION_AMOUNT = 250 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALAnime.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALAnime.kt index 022cb6c099..6ce987cfde 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALAnime.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/myanimelist/dto/MALAnime.kt @@ -18,6 +18,12 @@ data class MALAnime( val mediaType: String, @SerialName("start_date") val startDate: String?, + val studios: List = emptyList(), +) + +@Serializable +data class MALStudioNode( + val name: String, ) @Serializable From ba268eb3da682413a3211aa514de080c9abcc277 Mon Sep 17 00:00:00 2001 From: NGB-Was-Taken <76197326+ngb-was-taken@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:17:53 +0100 Subject: [PATCH 11/40] Fix crash when trying to install/update extensions while shizuku isn't running (#2837) (cherry picked from commit 4ce249c1a0ee9f3a20d91214fd09145e0924b2e4) --- .../extension/installer/ShizukuInstaller.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt index 303a2dad88..834a40d6fa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt @@ -109,9 +109,9 @@ class ShizukuInstaller(private val service: Service) : Installer(service) { override fun processEntry(entry: Entry) { super.processEntry(entry) try { - shellInterface?.install( - service.contentResolver.openAssetFileDescriptor(entry.uri, "r"), - ) + service.contentResolver.openAssetFileDescriptor(entry.uri, "r").use { + shellInterface?.install(it) + } } catch (e: Exception) { logcat(LogPriority.ERROR, e) { "Failed to install extension ${entry.downloadId} ${entry.uri}" } continueQueue(InstallStep.Error) @@ -124,7 +124,13 @@ class ShizukuInstaller(private val service: Service) : Installer(service) { override fun onDestroy() { Shizuku.removeBinderDeadListener(shizukuDeadListener) Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener) - Shizuku.unbindUserService(shizukuArgs, connection, true) + if (Shizuku.pingBinder()) { + try { + Shizuku.unbindUserService(shizukuArgs, connection, true) + } catch (e: Exception) { + logcat(LogPriority.WARN, e) { "Failed to unbind shizuku service" } + } + } service.unregisterReceiver(receiver) logcat { "ShizukuInstaller destroy" } scope.cancel() From 3af6f1f66b49c1230fab0975f959ed7f10906b0d Mon Sep 17 00:00:00 2001 From: NGB-Was-Taken <76197326+ngb-was-taken@users.noreply.github.com> Date: Wed, 7 Jan 2026 17:22:52 +0100 Subject: [PATCH 12/40] Enable logcat logging on stable and debug builds without enabling verbose logging (#2836) (cherry picked from commit a4f5a8184c956a3d92183e52a1c96d786cfe9517) --- app/src/main/java/eu/kanade/tachiyomi/App.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index f0c9564e99..066cda1936 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -144,8 +144,14 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor startSyncJob(syncPreferences.getSyncTriggerOptions().syncOnAppStart) // <-- AM (SYNC) - if (!LogcatLogger.isInstalled && networkPreferences.verboseLogging().get()) { - LogcatLogger.install(AndroidLogcatLogger(LogPriority.VERBOSE)) + if (!LogcatLogger.isInstalled) { + val minLogPriority = when { + networkPreferences.verboseLogging().get() -> LogPriority.VERBOSE + BuildConfig.DEBUG -> LogPriority.DEBUG + else -> LogPriority.INFO + } + LogcatLogger.install() + LogcatLogger.loggers += AndroidLogcatLogger(minLogPriority) } initializeMigrator() From b7eb577e19e5c7c39488e1e5582e644a1a5899d7 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 14 Jan 2026 15:35:37 +0000 Subject: [PATCH 13/40] Update markdown to v0.39.1 (#2850) (cherry picked from commit cf13012629571e2097796b50df238f69e62843b3) --- 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 9ecae7e757..15be66bfbc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ voyager = "1.1.0-beta03" spotless = "8.1.0" ktlint-core = "1.8.0" firebase-bom = "34.7.0" -markdown = "0.39.0" +markdown = "0.39.1" junit = "6.0.2" materialKolor = "5.0.0-alpha04" From c7bd79ad262b71a39d4b14dd320f13b48884485f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 14 Jan 2026 15:35:51 +0000 Subject: [PATCH 14/40] Update dependency com.materialkolor:material-kolor to v5.0.0-alpha05 (#2849) (cherry picked from commit 99e6fa3c06e1dc24255a73aa5741a72c82d94d91) --- 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 15be66bfbc..ee7dd22eb9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ ktlint-core = "1.8.0" firebase-bom = "34.7.0" markdown = "0.39.1" junit = "6.0.2" -materialKolor = "5.0.0-alpha04" +materialKolor = "5.0.0-alpha05" [libraries] desugar = "com.android.tools:desugar_jdk_libs:2.1.5" From 6883b54cd3dc9ff5aeee47c1c1608f823a7599c5 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Thu, 15 Jan 2026 13:50:16 +0000 Subject: [PATCH 15/40] Update dependency androidx.compose:compose-bom to v2026 (#2853) (cherry picked from commit e659e90c26814c9a29bf668545b4f10294f3c6b9) --- 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 ccd2659d00..ac88d4d231 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compose-bom = "2025.12.01" +compose-bom = "2026.01.00" [libraries] activity = "androidx.activity:activity-compose:1.12.2" From 5ecf136deb15694232cbf4cf1f8dd8e3136c4a7d Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 16 Jan 2026 11:06:02 +0000 Subject: [PATCH 16/40] Update dependency com.google.firebase:firebase-bom to v34.8.0 (#2856) (cherry picked from commit 13975d6f7eea21b3ba4be31736038ef2f855bfea) --- 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 ee7dd22eb9..ff58e0d7d9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,7 +9,7 @@ sqlite = "2.6.2" voyager = "1.1.0-beta03" spotless = "8.1.0" ktlint-core = "1.8.0" -firebase-bom = "34.7.0" +firebase-bom = "34.8.0" markdown = "0.39.1" junit = "6.0.2" materialKolor = "5.0.0-alpha05" From c999e9ffcfd4b9d4c07cea1369b0d9541249d047 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 17:38:05 +0100 Subject: [PATCH 17/40] Add Filters to Updates screen * Add Filters to Updates screen Behaves basically like the filters in the library: - Unread: Show/Don't show unread chapters - Downloaded: Show/Don't show downloaded chapters - Started: Show/Don't show chapters that have some progress but aren't fully Read - Bookmarked: Show/Don't show chapters that have been bookmarked Started behaves differently from its Library counterpart because the actual manga data is not available at this point in time and I thought calling getManga for each entry without caching would be a pretty bad idea. I have modelled this closely on the filter control flow in the Library, but I'm sure this can be simplified/adjusted in some way. * Move most filtering logic to SQL Unread, Started, and Bookmarked filters are now part of the SQL query. Download state cannot be filtered in the database so it remains in Kotlin. Because the Downloaded filter has to be run in Kotlin, the combine flow uses the preferences flow twice, once to get the SQL query params and once for the Kotlin filters (only Downloaded at this time). * Add "Hide excluded scanlators" to update filters Based on the work done in #1623 but integrated with the other filters in this PR. Added the user as a co-author for credit. Co-authored-by: Dani <17619547+shabnix@users.noreply.github.com> Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .../main/java/eu/kanade/core/util/FlowUtil.kt | 27 ++++ ....kt => UpdatesDeleteConfirmationDialog.kt} | 0 .../updates/UpdatesFilterDialog.kt | 121 +++++++++++++++++ .../presentation/updates/UpdatesScreen.kt | 12 ++ .../kanade/tachiyomi/di/PreferenceModule.kt | 4 + .../browse/extension/ExtensionsScreenModel.kt | 1 - .../kanade/tachiyomi/ui/recents/RecentsTab.kt | 6 +- .../ui/updates/UpdatesScreenModel.kt | 124 ++++++++++++++++-- .../ui/updates/UpdatesSettingsScreenModel.kt | 20 +++ .../kanade/tachiyomi/ui/updates/UpdatesTab.kt | 13 +- .../data/updates/UpdatesRepositoryImpl.kt | 24 +++- .../sqldelight/tachiyomi/migrations/137.sqm | 35 +++++ .../sqldelight/tachiyomi/view/updatesView.sq | 24 +++- .../domain/updates/interactor/GetUpdates.kt | 19 ++- .../updates/repository/UpdatesRepository.kt | 10 +- .../updates/service/UpdatesPreferences.kt | 40 ++++++ .../moko-resources/base/strings.xml | 1 + .../moko-resources/base/strings.xml | 1 + 18 files changed, 463 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/eu/kanade/core/util/FlowUtil.kt rename app/src/main/java/eu/kanade/presentation/updates/{UpdatesDialog.kt => UpdatesDeleteConfirmationDialog.kt} (100%) create mode 100644 app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt create mode 100644 data/src/main/sqldelight/tachiyomi/migrations/137.sqm create mode 100644 domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt diff --git a/app/src/main/java/eu/kanade/core/util/FlowUtil.kt b/app/src/main/java/eu/kanade/core/util/FlowUtil.kt new file mode 100644 index 0000000000..ab92eadb5a --- /dev/null +++ b/app/src/main/java/eu/kanade/core/util/FlowUtil.kt @@ -0,0 +1,27 @@ +// AM --> +package eu.kanade.core.util + +import kotlinx.coroutines.flow.Flow + +inline fun combine( + flow: Flow, + flow2: Flow, + flow3: Flow, + flow4: Flow, + flow5: Flow, + flow6: Flow, + crossinline transform: suspend (T1, T2, T3, T4, T5, T6) -> R, +): Flow { + return kotlinx.coroutines.flow.combine(flow, flow2, flow3, flow4, flow5, flow6) { args: Array<*> -> + @Suppress("UNCHECKED_CAST") + transform( + args[0] as T1, + args[1] as T2, + args[2] as T3, + args[3] as T4, + args[4] as T5, + args[5] as T6, + ) + } +} +// <-- AM diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesDeleteConfirmationDialog.kt similarity index 100% rename from app/src/main/java/eu/kanade/presentation/updates/UpdatesDialog.kt rename to app/src/main/java/eu/kanade/presentation/updates/UpdatesDeleteConfirmationDialog.kt diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt new file mode 100644 index 0000000000..f4644bf4bb --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesFilterDialog.kt @@ -0,0 +1,121 @@ +package eu.kanade.presentation.updates + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import eu.kanade.presentation.components.TabbedDialog +import eu.kanade.presentation.components.TabbedDialogPaddings +import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel +import kotlinx.collections.immutable.persistentListOf +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.domain.updates.service.UpdatesPreferences +import tachiyomi.i18n.MR +import tachiyomi.i18n.animiru.AMMR +import tachiyomi.presentation.core.components.SettingsItemsPaddings +import tachiyomi.presentation.core.components.TriStateItem +import tachiyomi.presentation.core.components.material.padding +import tachiyomi.presentation.core.i18n.stringResource +import tachiyomi.presentation.core.util.collectAsState + +@Composable +fun UpdatesFilterDialog( + onDismissRequest: () -> Unit, + screenModel: UpdatesSettingsScreenModel, +) { + TabbedDialog( + onDismissRequest = onDismissRequest, + tabTitles = persistentListOf( + stringResource(MR.strings.action_filter), + ), + ) { + Column( + modifier = Modifier + .padding(vertical = TabbedDialogPaddings.Vertical) + .verticalScroll(rememberScrollState()), + ) { + FilterSheet(screenModel = screenModel) + } + } +} + +@Composable +private fun ColumnScope.FilterSheet( + screenModel: UpdatesSettingsScreenModel, +) { + val filterDownloaded by screenModel.updatesPreferences.filterDownloaded().collectAsState() + TriStateItem( + label = stringResource(MR.strings.label_downloaded), + state = filterDownloaded, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterDownloaded) }, + ) + + val filterUnseen by screenModel.updatesPreferences.filterUnseen().collectAsState() + TriStateItem( + label = stringResource(AMMR.strings.am_action_filter_unseen), + state = filterUnseen, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterUnseen) }, + ) + + val filterStarted by screenModel.updatesPreferences.filterStarted().collectAsState() + TriStateItem( + label = stringResource(MR.strings.label_started), + state = filterStarted, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterStarted) }, + ) + + val filterBookmarked by screenModel.updatesPreferences.filterBookmarked().collectAsState() + TriStateItem( + label = stringResource(MR.strings.action_filter_bookmarked), + state = filterBookmarked, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterBookmarked) }, + ) + + // AM --> + val filterFillermarked by screenModel.updatesPreferences.filterFillermarked().collectAsState() + TriStateItem( + label = stringResource(AMMR.strings.action_filter_fillermarked), + state = filterFillermarked, + onClick = { screenModel.toggleFilter(UpdatesPreferences::filterFillermarked) }, + ) + // <-- AM + + HorizontalDivider(modifier = Modifier.padding(MaterialTheme.padding.small)) + + val filterExcludedScanlators by screenModel.updatesPreferences.filterExcludedScanlators().collectAsState() + + fun toggleScanlatorFilter() = screenModel.updatesPreferences.filterExcludedScanlators().getAndSet { !it } + + Row( + modifier = Modifier + .clickable { toggleScanlatorFilter() } + .fillMaxWidth() + .padding(horizontal = SettingsItemsPaddings.Horizontal), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = stringResource(MR.strings.action_filter_excluded_scanlators), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyMedium, + ) + + Switch( + checked = filterExcludedScanlators, + onCheckedChange = { toggleScanlatorFilter() }, + ) + } +} diff --git a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt index 5bb0a46d4a..07fb30910d 100644 --- a/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/updates/UpdatesScreen.kt @@ -5,9 +5,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.CalendarMonth +import androidx.compose.material.icons.outlined.FilterList import androidx.compose.material.icons.outlined.FlipToBack import androidx.compose.material.icons.outlined.Refresh import androidx.compose.material.icons.outlined.SelectAll +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.MaterialTheme import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -35,6 +38,7 @@ import tachiyomi.presentation.core.components.material.PullRefresh import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.LoadingScreen +import tachiyomi.presentation.core.theme.active import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.time.LocalDate @@ -100,6 +104,8 @@ fun UpdateScreen( fun UpdatesTopBar( onCalendarClicked: () -> Unit, onUpdateLibrary: () -> Unit, + onFilterClicked: () -> Unit, + hasFilters: Boolean, // For action mode actionModeCounter: Int, onSelectAll: () -> Unit, @@ -114,6 +120,12 @@ fun UpdatesTopBar( actions = { AppBarActions( persistentListOf( + AppBar.Action( + title = stringResource(MR.strings.action_filter), + icon = Icons.Outlined.FilterList, + iconTint = if (hasFilters) MaterialTheme.colorScheme.active else LocalContentColor.current, + onClick = onFilterClicked, + ), AppBar.Action( title = stringResource(MR.strings.action_view_upcoming), icon = Icons.Outlined.CalendarMonth, diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt index 8d1c847f80..8acb326277 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt @@ -23,6 +23,7 @@ import tachiyomi.domain.backup.service.BackupPreferences import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.storage.service.StoragePreferences +import tachiyomi.domain.updates.service.UpdatesPreferences import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.addSingletonFactory @@ -49,6 +50,9 @@ class PreferenceModule(val app: Application) : InjektModule { addSingletonFactory { LibraryPreferences(get()) } + addSingletonFactory { + UpdatesPreferences(get()) + } // AY --> addSingletonFactory { PlayerPreferences(get()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt index 4954cb80a4..b4c420ab7b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionsScreenModel.kt @@ -111,7 +111,6 @@ class ExtensionsScreenModel( .launchIn(screenModelScope) } - fun searchQueryPredicate(query: String): (Extension) -> Boolean { val subqueries = query.split(",") .map { it.trim() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsTab.kt index 15f8277e9e..4984db7acb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsTab.kt @@ -54,6 +54,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.player.settings.PlayerPreferences import eu.kanade.tachiyomi.ui.updates.AnimeUpdatesHalfTab import eu.kanade.tachiyomi.ui.updates.UpdatesScreenModel +import eu.kanade.tachiyomi.ui.updates.UpdatesSettingsScreenModel import eu.kanade.tachiyomi.ui.updates.openEpisode import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.channels.Channel @@ -106,6 +107,7 @@ data object RecentsTab : Tab { val historyScreenModel = rememberScreenModel { HistoryScreenModel() } // AM (RECENTS_FILTER_CHIP) --> val updatesScreenModel = rememberScreenModel { UpdatesScreenModel() } + val updatesSettingsScreenModel = rememberScreenModel { UpdatesSettingsScreenModel() } // AM (TAB_HOLD) --> val snackbarHostState = SnackbarHostState() // <-- AM (TAB_HOLD) @@ -120,7 +122,7 @@ data object RecentsTab : Tab { ) { contentPadding -> Crossfade(targetState = showHistoryScreen, label = "recents_crossfade") { showHistory -> if (!showHistory) { - AnimeUpdatesHalfTab(updatesScreenModel, contentPadding) + AnimeUpdatesHalfTab(updatesScreenModel, updatesSettingsScreenModel, contentPadding) } else { HistoryHalfTab(historyScreenModel, snackbarHostState, contentPadding) } @@ -182,6 +184,8 @@ fun RecentsScaffold( if (!showHistoryScreen) { UpdatesTopBar( onCalendarClicked = { navigator.push(UpcomingScreen()) }, + onFilterClicked = updatesScreenModel::showFilterDialog, + hasFilters = updatesState.hasActiveFilters, onUpdateLibrary = updatesScreenModel::updateLibrary, actionModeCounter = updatesState.selected.size, onSelectAll = { updatesScreenModel.toggleAllSelection(true) }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index e1558ac0bb..3d40e19861 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -4,6 +4,7 @@ import android.app.Application import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Immutable import androidx.compose.runtime.getValue +import androidx.compose.ui.util.fastFilter import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.screenModelScope import eu.kanade.core.preference.asState @@ -27,15 +28,21 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import logcat.LogPriority +import tachiyomi.core.common.preference.TriState import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchNonCancellable import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.anime.interactor.GetAnime +import tachiyomi.domain.anime.model.applyFilter import tachiyomi.domain.download.service.DownloadPreferences import tachiyomi.domain.episode.interactor.GetEpisode import tachiyomi.domain.episode.interactor.UpdateEpisode @@ -44,6 +51,7 @@ import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.source.service.SourceManager import tachiyomi.domain.updates.interactor.GetUpdates import tachiyomi.domain.updates.model.UpdatesWithRelations +import tachiyomi.domain.updates.service.UpdatesPreferences import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.time.ZonedDateTime @@ -58,6 +66,7 @@ class UpdatesScreenModel( private val getAnime: GetAnime = Injekt.get(), private val getEpisode: GetEpisode = Injekt.get(), private val libraryPreferences: LibraryPreferences = Injekt.get(), + private val updatesPreferences: UpdatesPreferences = Injekt.get(), val snackbarHostState: SnackbarHostState = SnackbarHostState(), // AY --> downloadPreferences: DownloadPreferences = Injekt.get(), @@ -83,19 +92,36 @@ class UpdatesScreenModel( val limit = ZonedDateTime.now().minusMonths(3).toInstant() combine( - getUpdates.subscribe(limit).distinctUntilChanged(), + // needed for SQL filters (unread, started, bookmarked, etc) + getUpdatesItemPreferenceFlow() + .distinctUntilChanged() + .flatMapLatest { + getUpdates.subscribe( + limit, + unseen = it.filterUnseen.toBooleanOrNull(), + started = it.filterStarted.toBooleanOrNull(), + bookmarked = it.filterBookmarked.toBooleanOrNull(), + fillermarked = it.filterFillermarked.toBooleanOrNull(), + hideExcludedScanlators = it.filterExcludedScanlators, + ).distinctUntilChanged() + }, downloadCache.changes, downloadManager.queueState, - ) { updates, _, _ -> updates } - .catch { - logcat(LogPriority.ERROR, it) - _events.send(Event.InternalError) - } - .collectLatest { updates -> + // needed for Kotlin filters (downloaded) + getUpdatesItemPreferenceFlow().distinctUntilChanged { old, new -> + old.filterDownloaded == new.filterDownloaded + }, + ) { updates, _, _, itemPreferences -> + updates + .toUpdateItems() + .applyFilters(itemPreferences) + .toPersistentList() + } + .collectLatest { updateItems -> mutableState.update { it.copy( isLoading = false, - items = updates.toUpdateItems(), + items = updateItems, ) } } @@ -106,9 +132,44 @@ class UpdatesScreenModel( .catch { logcat(LogPriority.ERROR, it) } .collect(this@UpdatesScreenModel::updateDownloadState) } + + getUpdatesItemPreferenceFlow() + .map { prefs -> + listOf( + prefs.filterUnseen, + prefs.filterDownloaded, + prefs.filterStarted, + prefs.filterBookmarked, + prefs.filterFillermarked, + ) + .any { it != TriState.DISABLED } + } + .distinctUntilChanged() + .onEach { + mutableState.update { state -> + state.copy(hasActiveFilters = it) + } + } + .launchIn(screenModelScope) } - private fun List.toUpdateItems(): PersistentList { + private fun List.applyFilters( + preferences: ItemPreferences, + ): List { + val filterDownloaded = preferences.filterDownloaded + + val filterFnDownloaded: (UpdatesItem) -> Boolean = { + applyFilter(filterDownloaded) { + it.downloadStateProvider() == Download.State.DOWNLOADED + } + } + + return fastFilter { + filterFnDownloaded(it) + } + } + + private fun List.toUpdateItems(): List { return this .map { update -> val activeDownload = downloadManager.getQueuedDownloadOrNull(update.episodeId) @@ -136,7 +197,6 @@ class UpdatesScreenModel( // <-- AM (FILE_SIZE) ) } - .toPersistentList() } fun updateLibrary(): Boolean { @@ -410,9 +470,44 @@ class UpdatesScreenModel( libraryPreferences.newUpdatesCount().set(0) } + private fun getUpdatesItemPreferenceFlow(): Flow { + return eu.kanade.core.util.combine( + updatesPreferences.filterDownloaded().changes(), + updatesPreferences.filterUnseen().changes(), + updatesPreferences.filterStarted().changes(), + updatesPreferences.filterBookmarked().changes(), + updatesPreferences.filterFillermarked().changes(), + updatesPreferences.filterExcludedScanlators().changes(), + ) { downloaded, unseen, started, bookmarked, fillermarked, excludedScanlators -> + ItemPreferences( + filterDownloaded = downloaded, + filterUnseen = unseen, + filterStarted = started, + filterBookmarked = bookmarked, + filterFillermarked = fillermarked, + filterExcludedScanlators = excludedScanlators, + ) + } + } + + fun showFilterDialog() { + mutableState.update { it.copy(dialog = Dialog.FilterSheet) } + } + + @Immutable + private data class ItemPreferences( + val filterDownloaded: TriState, + val filterUnseen: TriState, + val filterStarted: TriState, + val filterBookmarked: TriState, + val filterFillermarked: TriState, + val filterExcludedScanlators: Boolean, + ) + @Immutable data class State( val isLoading: Boolean = true, + val hasActiveFilters: Boolean = false, val items: PersistentList = persistentListOf(), val dialog: Dialog? = null, ) { @@ -436,6 +531,7 @@ class UpdatesScreenModel( sealed interface Dialog { data class DeleteConfirmation(val toDelete: List) : Dialog + data object FilterSheet : Dialog // AY --> data class ShowQualities( @@ -453,6 +549,14 @@ class UpdatesScreenModel( } } +private fun TriState.toBooleanOrNull(): Boolean? { + return when (this) { + TriState.DISABLED -> null + TriState.ENABLED_IS -> true + TriState.ENABLED_NOT -> false + } +} + @Immutable data class UpdatesItem( val update: UpdatesWithRelations, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt new file mode 100644 index 0000000000..1e909d4847 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesSettingsScreenModel.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.ui.updates + +import cafe.adriel.voyager.core.model.ScreenModel +import tachiyomi.core.common.preference.Preference +import tachiyomi.core.common.preference.TriState +import tachiyomi.core.common.preference.getAndSet +import tachiyomi.domain.updates.service.UpdatesPreferences +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get + +class UpdatesSettingsScreenModel( + val updatesPreferences: UpdatesPreferences = Injekt.get(), +) : ScreenModel { + + fun toggleFilter(preference: (UpdatesPreferences) -> Preference) { + preference(updatesPreferences).getAndSet { + it.next() + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt index 2be74eca69..36c2285096 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesTab.kt @@ -15,6 +15,7 @@ import eu.kanade.presentation.anime.EpisodeOptionsDialogScreen import eu.kanade.presentation.components.NavigatorAdaptiveSheet import eu.kanade.presentation.updates.UpdateScreen import eu.kanade.presentation.updates.UpdatesDeleteConfirmationDialog +import eu.kanade.presentation.updates.UpdatesFilterDialog import eu.kanade.tachiyomi.ui.anime.AnimeScreen import eu.kanade.tachiyomi.ui.home.HomeScreen import eu.kanade.tachiyomi.ui.main.MainActivity @@ -28,7 +29,11 @@ import uy.kohesive.injekt.injectLazy // AM (RECENTS_FILTER_CHIP) --> @Composable -fun AnimeUpdatesHalfTab(screenModel: UpdatesScreenModel, contentPadding: PaddingValues) { +fun AnimeUpdatesHalfTab( + screenModel: UpdatesScreenModel, + settingsScreenModel: UpdatesSettingsScreenModel, + contentPadding: PaddingValues, +) { val context = LocalContext.current val navigator = LocalNavigator.currentOrThrow val scope = rememberCoroutineScope() @@ -59,6 +64,12 @@ fun AnimeUpdatesHalfTab(screenModel: UpdatesScreenModel, contentPadding: Padding onConfirm = { screenModel.deleteEpisodes(dialog.toDelete) }, ) } + is UpdatesScreenModel.Dialog.FilterSheet -> { + UpdatesFilterDialog( + onDismissRequest = onDismissDialog, + screenModel = settingsScreenModel, + ) + } is UpdatesScreenModel.Dialog.ShowQualities -> { EpisodeOptionsDialogScreen.onDismissDialog = onDismissDialog NavigatorAdaptiveSheet( diff --git a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt index df17aeb3a5..9c5eaabe88 100644 --- a/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/updates/UpdatesRepositoryImpl.kt @@ -1,6 +1,7 @@ package tachiyomi.data.updates import kotlinx.coroutines.flow.Flow +import tachiyomi.core.common.util.lang.toLong import tachiyomi.data.DatabaseHandler import tachiyomi.domain.anime.model.AnimeCover import tachiyomi.domain.updates.model.UpdatesWithRelations @@ -25,9 +26,27 @@ class UpdatesRepositoryImpl( } } - override fun subscribeAll(after: Long, limit: Long): Flow> { + override fun subscribeAll( + after: Long, + limit: Long, + unseen: Boolean?, + started: Boolean?, + bookmarked: Boolean?, + fillermarked: Boolean?, + hideExcludedScanlators: Boolean, + ): Flow> { return databaseHandler.subscribeToList { - updatesViewQueries.getRecentUpdates(after, limit, ::mapUpdatesWithRelations) + updatesViewQueries.getRecentUpdatesWithFilters( + after = after, + limit = limit, + // invert because unseen in Kotlin -> seen column in SQL + seen = unseen?.let { !it }, + started = started?.toLong(), + bookmarked = bookmarked, + fillermarked = fillermarked, + hideExcludedScanlators = hideExcludedScanlators.toLong(), + mapper = ::mapUpdatesWithRelations, + ) } } @@ -68,6 +87,7 @@ class UpdatesRepositoryImpl( coverLastModified: Long, dateUpload: Long, dateFetch: Long, + excludedScanlator: String?, ): UpdatesWithRelations = UpdatesWithRelations( animeId = animeId, // AM (CUSTOM_INFORMATION) --> diff --git a/data/src/main/sqldelight/tachiyomi/migrations/137.sqm b/data/src/main/sqldelight/tachiyomi/migrations/137.sqm new file mode 100644 index 0000000000..1770301c13 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/137.sqm @@ -0,0 +1,35 @@ +-- Add excluded_scanlators to updatesView +DROP VIEW IF EXISTS updatesView; + +CREATE VIEW updatesView AS +SELECT + animes._id AS animeId, + animes.title AS animeTitle, + episodes._id AS episodeId, + episodes.name AS episodeName, + episodes.scanlator, + episodes.url AS episodeUrl, + episodes.seen, + episodes.bookmark, + -- AY --> + episodes.fillermark, + -- <-- AY + episodes.last_second_seen, + -- AY --> + episodes.total_seconds AS totalSeconds, + -- <-- AY + animes.source, + animes.favorite, + animes.thumbnail_url AS thumbnailUrl, + animes.cover_last_modified AS coverLastModified, + episodes.date_upload AS dateUpload, + episodes.date_fetch AS datefetch, + excluded_scanlators.scanlator AS excludedScanlator +FROM animes JOIN episodes +ON animes._id = episodes.anime_id +LEFT JOIN excluded_scanlators +ON animes._id = excluded_scanlators.anime_id +AND episodes.scanlator = excluded_scanlators.scanlator +WHERE favorite = 1 +AND date_fetch > date_added +ORDER BY date_fetch DESC; diff --git a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq index bb4248b60f..d8017e70fd 100644 --- a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq @@ -20,9 +20,13 @@ SELECT animes.thumbnail_url AS thumbnailUrl, animes.cover_last_modified AS coverLastModified, episodes.date_upload AS dateUpload, - episodes.date_fetch AS datefetch + episodes.date_fetch AS datefetch, + excluded_scanlators.scanlator AS excludedScanlator FROM animes JOIN episodes ON animes._id = episodes.anime_id +LEFT JOIN excluded_scanlators +ON animes._id = excluded_scanlators.anime_id +AND episodes.scanlator = excluded_scanlators.scanlator WHERE favorite = 1 AND date_fetch > date_added ORDER BY date_fetch DESC; @@ -33,6 +37,24 @@ FROM updatesView WHERE dateUpload > :after LIMIT :limit; +getRecentUpdatesWithFilters: +SELECT * +FROM updatesView +WHERE dateUpload > :after +AND (:seen IS NULL OR seen = :seen) +-- Started means some progress but not finished, Seen means finished episode, thus: +AND ( + :started IS NULL + OR (:started = 1 AND last_second_seen > 0 AND seen = 0) + OR (:started = 0 AND last_second_seen = 0 AND seen = 0) +) +AND (:bookmarked IS NULL OR bookmark = :bookmarked) +AND (:fillermarked IS NULL OR fillermark = :fillermarked) +AND ( + (excludedScanlator IS NULL OR :hideExcludedScanlators = 0) +) +LIMIT :limit; + getUpdatesBySeenStatus: SELECT * FROM updatesView diff --git a/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt b/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt index e9f8152ebd..59269bd32c 100644 --- a/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt +++ b/domain/src/main/java/tachiyomi/domain/updates/interactor/GetUpdates.kt @@ -13,8 +13,23 @@ class GetUpdates( return repository.awaitWithSeen(seen, after, limit = 500) } - fun subscribe(instant: Instant): Flow> { - return repository.subscribeAll(instant.toEpochMilli(), limit = 500) + fun subscribe( + instant: Instant, + unseen: Boolean?, + started: Boolean?, + bookmarked: Boolean?, + fillermarked: Boolean?, + hideExcludedScanlators: Boolean, + ): Flow> { + return repository.subscribeAll( + instant.toEpochMilli(), + limit = 500, + unseen = unseen, + started = started, + bookmarked = bookmarked, + fillermarked = fillermarked, + hideExcludedScanlators = hideExcludedScanlators, + ) } fun subscribe(seen: Boolean, after: Long): Flow> { diff --git a/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt b/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt index 528e937dd0..435d56f130 100644 --- a/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt +++ b/domain/src/main/java/tachiyomi/domain/updates/repository/UpdatesRepository.kt @@ -7,7 +7,15 @@ interface UpdatesRepository { suspend fun awaitWithSeen(seen: Boolean, after: Long, limit: Long): List - fun subscribeAll(after: Long, limit: Long): Flow> + fun subscribeAll( + after: Long, + limit: Long, + unseen: Boolean?, + started: Boolean?, + bookmarked: Boolean?, + fillermarked: Boolean?, + hideExcludedScanlators: Boolean, + ): Flow> fun subscribeWithSeen(seen: Boolean, after: Long, limit: Long): Flow> } diff --git a/domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt b/domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt new file mode 100644 index 0000000000..468b6db486 --- /dev/null +++ b/domain/src/main/java/tachiyomi/domain/updates/service/UpdatesPreferences.kt @@ -0,0 +1,40 @@ +package tachiyomi.domain.updates.service + +import tachiyomi.core.common.preference.PreferenceStore +import tachiyomi.core.common.preference.TriState +import tachiyomi.core.common.preference.getEnum + +class UpdatesPreferences( + private val preferenceStore: PreferenceStore, +) { + + fun filterDownloaded() = preferenceStore.getEnum( + "pref_filter_updates_downloaded", + TriState.DISABLED, + ) + + fun filterUnseen() = preferenceStore.getEnum( + "pref_filter_updates_unseen", + TriState.DISABLED, + ) + + fun filterStarted() = preferenceStore.getEnum( + "pref_filter_updates_started", + TriState.DISABLED, + ) + + fun filterBookmarked() = preferenceStore.getEnum( + "pref_filter_updates_bookmarked", + TriState.DISABLED, + ) + + fun filterFillermarked() = preferenceStore.getEnum( + "pref_filter_updates_fillermarked", + TriState.DISABLED, + ) + + fun filterExcludedScanlators() = preferenceStore.getBoolean( + "pref_filter_updates_hide_excluded_scanlators", + false, + ) +} diff --git a/i18n-animiru/src/commonMain/moko-resources/base/strings.xml b/i18n-animiru/src/commonMain/moko-resources/base/strings.xml index 06732059e5..51542a398c 100644 --- a/i18n-animiru/src/commonMain/moko-resources/base/strings.xml +++ b/i18n-animiru/src/commonMain/moko-resources/base/strings.xml @@ -58,6 +58,7 @@ No episodes found, this entry cannot be used for migration No seasons found, this entry cannot be used for migration Ensures compatibility with certain storage media that don't support Unicode. When this is enabled, you'll need to manually rename source and anime folders by replacing non-ASCII characters with their lowercase UTF-8 hexadecimal representations. Episode files don't need to be renamed. + Unseen Mismatched fetch type diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 6092636a07..2955a786e7 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -854,6 +854,7 @@ Just now Never View Upcoming Updates + Filter excluded scanlators Upcoming Guide From d8a403ba8cc49d9debc55580774a79701ec724bd Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 20 Jan 2026 14:39:01 +0000 Subject: [PATCH 18/40] Update dependency io.kotest:kotest-assertions-core to v6.1.0 (#2870) (cherry picked from commit 49c4d08b22b6a440b93b58bd78280807550d6198) --- 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 ff58e0d7d9..506e7a14df 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } -kotest-assertions = "io.kotest:kotest-assertions-core:6.0.7" +kotest-assertions = "io.kotest:kotest-assertions-core:6.1.0" mockk = "io.mockk:mockk:1.14.7" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } From dc8a6c2a0d78982656fe1efad1682df51dc0eee0 Mon Sep 17 00:00:00 2001 From: MajorTanya <39014446+majortanya@users.noreply.github.com> Date: Tue, 20 Jan 2026 15:58:41 +0100 Subject: [PATCH 19/40] Reword download index message (#2874) I'm tired of people thinking the current wording is an error. Improved wordings welcome, this was just my first decent guess. (cherry picked from commit 05d90ea4d652cd7ead385ec954e0ae2dc332a012) --- i18n/src/commonMain/moko-resources/base/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index 2955a786e7..2db93d49dc 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -607,7 +607,7 @@ Cookies cleared Reindex downloads Force app to recheck downloaded chapters - Downloads index invalidated + Recreating download index Clear database Delete history for entries that are not saved in your library %1$d non-library entries in database From 9800d32e7bb0084ef25e1e81de5a3f8c4ab780ac Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 24 Jan 2026 14:38:36 +0100 Subject: [PATCH 20/40] Update GitHub Actions (#2884) (cherry picked from commit fa966fcb977c2aabd9930c10c6bb99305e22ee37) --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d7f497c5c..5f0c4897b2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,14 +34,14 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Dependency Review if: github.event_name == 'pull_request' uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2 - name: Set up JDK - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: 17 distribution: temurin diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a3d256b1c..7c1ab7f33d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,10 +35,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - name: Set up JDK - uses: actions/setup-java@f2beeb24e141e01a676f977032f5a29d81c9e27e # v5.1.0 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: java-version: 17 distribution: temurin From 81c5204a6316a6cde355f99668442f8b547a172d Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 24 Jan 2026 13:41:59 +0000 Subject: [PATCH 21/40] Update Gradle to v8.14.4 (#2894) (cherry picked from commit 2037ec45006b54da0e64b3e07bae4c87e261ee59) --- 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 d4081da476..aaaabb3cb9 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.14.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.4-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From f830248218b13775171ce27e6915e824b803644b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 24 Jan 2026 13:42:16 +0000 Subject: [PATCH 22/40] Update dependency io.kotest:kotest-assertions-core to v6.1.1 (#2893) (cherry picked from commit ede2f56bc6531f69553d0f95cf422b7f6d15b90b) --- 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 506e7a14df..dc540fb751 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } -kotest-assertions = "io.kotest:kotest-assertions-core:6.1.0" +kotest-assertions = "io.kotest:kotest-assertions-core:6.1.1" mockk = "io.mockk:mockk:1.14.7" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } From 47cd910d5a7c9f3a36a8e8e214f46709d7f80dc0 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 24 Jan 2026 13:42:37 +0000 Subject: [PATCH 23/40] Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.0 (#2892) (cherry picked from commit e6c67003511b601d49bc175b83596568ddfe09d9) --- 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 dc540fb751..176311935a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ shizuku_version = "13.1.5" sqldelight = "2.2.1" sqlite = "2.6.2" voyager = "1.1.0-beta03" -spotless = "8.1.0" +spotless = "8.2.0" ktlint-core = "1.8.0" firebase-bom = "34.8.0" markdown = "0.39.1" From f789b92c155652bde5132e0379058a44c3188709 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Sat, 24 Jan 2026 13:48:43 +0000 Subject: [PATCH 24/40] Update serialization.version to v1.10.0 (#2879) (cherry picked from commit d6e17b04c39c9d800c57d75e29714a51fbf902f8) --- gradle/kotlinx.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/kotlinx.versions.toml b/gradle/kotlinx.versions.toml index 81142d4547..c33ba7668f 100644 --- a/gradle/kotlinx.versions.toml +++ b/gradle/kotlinx.versions.toml @@ -1,6 +1,6 @@ [versions] kotlin_version = "2.3.0" -serialization_version = "1.9.0" +serialization_version = "1.10.0" xml_serialization_version = "0.91.3" [libraries] From 805a2fb017002b5256089777a9124747d95e4188 Mon Sep 17 00:00:00 2001 From: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> Date: Sat, 24 Jan 2026 14:50:15 +0100 Subject: [PATCH 25/40] Fix memoization in manga bottom action menus Co-authored-by: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> --- .../anime/components/AnimeBottomActionMenu.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/anime/components/AnimeBottomActionMenu.kt b/app/src/main/java/eu/kanade/presentation/anime/components/AnimeBottomActionMenu.kt index 6f25d6f978..ddc38fdba4 100644 --- a/app/src/main/java/eu/kanade/presentation/anime/components/AnimeBottomActionMenu.kt +++ b/app/src/main/java/eu/kanade/presentation/anime/components/AnimeBottomActionMenu.kt @@ -114,12 +114,11 @@ fun AnimeBottomActionMenu( val confirm = remember { mutableStateListOf(false, false, false, false, false, false, false, false, false, false, false) } - val confirmRange = 0..<11 // <-- AY - var resetJob: Job? = remember { null } + var resetJob by remember { mutableStateOf(null) } val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> haptic.performHapticFeedback(HapticFeedbackType.LongPress) - (confirmRange).forEach { i -> confirm[i] = i == toConfirmIndex } + confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex } resetJob?.cancel() resetJob = scope.launch { delay(1.seconds) @@ -319,10 +318,10 @@ fun LibraryBottomActionMenu( ) { val haptic = LocalHapticFeedback.current val confirm = remember { mutableStateListOf(false, false, false, false, false, false) } - var resetJob: Job? = remember { null } + var resetJob by remember { mutableStateOf(null) } val onLongClickItem: (Int) -> Unit = { toConfirmIndex -> haptic.performHapticFeedback(HapticFeedbackType.LongPress) - (0..5).forEach { i -> confirm[i] = i == toConfirmIndex } + confirm.indices.forEach { i -> confirm[i] = i == toConfirmIndex } resetJob?.cancel() resetJob = scope.launch { delay(1.seconds) From debb0f9cece391ff29dbd32cfa38e1d9b06b7f7b Mon Sep 17 00:00:00 2001 From: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> Date: Sat, 24 Jan 2026 14:50:15 +0100 Subject: [PATCH 26/40] Fix Add Repo input not taking up the full dialog width (#2816) (cherry picked from commit 82ffc8efa69cb86229fe8b677f12942c80b88a15) --- .../settings/screen/browse/components/ExtensionReposDialogs.kt | 2 ++ 1 file changed, 2 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 39a57d4f84..bd7c1ceddd 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.layout.fillMaxWidth import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.AlertDialog import androidx.compose.material3.OutlinedTextField @@ -61,6 +62,7 @@ fun ExtensionRepoCreateDialog( OutlinedTextField( modifier = Modifier + .fillMaxWidth() .focusRequester(focusRequester), value = name, onValueChange = { name = it }, From 4cedcbffa8e619f245fc943df0a70dc5241d7d06 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 26 Jan 2026 19:24:06 +0000 Subject: [PATCH 27/40] Update dependency io.mockk:mockk to v1.14.9 (#2904) (cherry picked from commit c0a1203541931a9090e6769acdb596c9ea4a011b) --- 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 176311935a..694694b610 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -92,7 +92,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } kotest-assertions = "io.kotest:kotest-assertions-core:6.1.1" -mockk = "io.mockk:mockk:1.14.7" +mockk = "io.mockk:mockk:1.14.9" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } From 60184815b79ef4f72be95269e43a3b15692fdb47 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 28 Jan 2026 01:30:23 +0000 Subject: [PATCH 28/40] Update dependency com.diffplug.spotless:spotless-plugin-gradle to v8.2.1 (#2909) (cherry picked from commit 31a9ff261e5e195fa2e27615bae0a86956a3c90a) --- 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 694694b610..d20f4314f6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ shizuku_version = "13.1.5" sqldelight = "2.2.1" sqlite = "2.6.2" voyager = "1.1.0-beta03" -spotless = "8.2.0" +spotless = "8.2.1" ktlint-core = "1.8.0" firebase-bom = "34.8.0" markdown = "0.39.1" From 921c75f0d159c5226998404f58a96627d98cad13 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 28 Jan 2026 01:30:38 +0000 Subject: [PATCH 29/40] Update dependency io.kotest:kotest-assertions-core to v6.1.2 (#2908) (cherry picked from commit f6f1d13addea504ff7ed6ec87276d1cfdfcf2013) --- 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 d20f4314f6..b3c0b33a02 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } -kotest-assertions = "io.kotest:kotest-assertions-core:6.1.1" +kotest-assertions = "io.kotest:kotest-assertions-core:6.1.2" mockk = "io.mockk:mockk:1.14.9" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } From 8389f871ed360e8b3d10666510724e510d87e392 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 2 Feb 2026 06:27:27 +0000 Subject: [PATCH 30/40] Update markdown to v0.39.2 (#2923) (cherry picked from commit 9c0eebb55ceb6a7edc50e414b4be19faf256bcda) --- 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 b3c0b33a02..5b5147a454 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -10,7 +10,7 @@ voyager = "1.1.0-beta03" spotless = "8.2.1" ktlint-core = "1.8.0" firebase-bom = "34.8.0" -markdown = "0.39.1" +markdown = "0.39.2" junit = "6.0.2" materialKolor = "5.0.0-alpha05" From 394510bb35ef31ceeaf265e7d2770e11ca223b08 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 2 Feb 2026 06:27:47 +0000 Subject: [PATCH 31/40] Update dependency androidx.activity:activity-compose to v1.12.3 (#2917) (cherry picked from commit b70edfac58e8e5722a51be398212ac025b818258) --- 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 ac88d4d231..c42e77d351 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -2,7 +2,7 @@ compose-bom = "2026.01.00" [libraries] -activity = "androidx.activity:activity-compose:1.12.2" +activity = "androidx.activity:activity-compose:1.12.3" bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } foundation = { module = "androidx.compose.foundation:foundation" } animation = { module = "androidx.compose.animation:animation" } From 7f1bd21a7ef14b285980d282d2da14090373321f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 2 Feb 2026 06:28:25 +0000 Subject: [PATCH 32/40] Update paging.version to v3.4.0 (#2916) (cherry picked from commit 1bf4eff931781eff11e3c1d59ca3747ffb069203) --- 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 c77ce2f3d8..248f281cb2 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,7 +1,7 @@ [versions] agp_version = "8.13.2" lifecycle_version = "2.10.0" -paging_version = "3.3.6" +paging_version = "3.4.0" interpolator_version = "1.0.0" [libraries] From 899a02111bbebd1ff508527b60240eee6a011859 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 2 Feb 2026 06:31:29 +0000 Subject: [PATCH 33/40] Update dependency androidx.work:work-runtime to v2.11.1 (#2914) (cherry picked from commit e092b4208a0f8476c81ed58a2b0efd3a7c5996cf) --- 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 248f281cb2..aeb199ce7a 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -21,7 +21,7 @@ lifecycle-common = { module = "androidx.lifecycle:lifecycle-common", version.ref lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "lifecycle_version" } lifecycle-runtimektx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle_version" } -workmanager = "androidx.work:work-runtime:2.11.0" +workmanager = "androidx.work:work-runtime:2.11.1" paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "paging_version" } paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging_version" } From 878884c71e4e69644a96397cc542e3205275124f Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 2 Feb 2026 07:31:47 +0100 Subject: [PATCH 34/40] Update gradle/actions action to v5.0.1 (#2912) (cherry picked from commit 86344e20d3d58aa41810df6cd5cc2f39ec5b967e) --- .github/workflows/build.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5f0c4897b2..fafd52e0d2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -47,7 +47,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 + uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 - name: Check code format run: ./gradlew spotlessCheck diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c1ab7f33d..b6ce85aabf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -53,7 +53,7 @@ jobs: # <-- AM (SYNC_DRIVE) - name: Set up Gradle - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 + uses: gradle/actions/setup-gradle@f29f5a9d7b09a7c6b29859002d29d24e1674c884 # v5.0.1 - name: Build run: ./gradlew assembleRelease -Penable-updater From 4c188343ab721247cabeaf75e3f6408ed66d59e8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 2 Feb 2026 06:33:28 +0000 Subject: [PATCH 35/40] Update dependency androidx.compose:compose-bom to v2026.01.01 (#2913) (cherry picked from commit 100cea0757c930b153e50ca2ba52e4c06f14800d) --- 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 c42e77d351..87c0cefade 100644 --- a/gradle/compose.versions.toml +++ b/gradle/compose.versions.toml @@ -1,5 +1,5 @@ [versions] -compose-bom = "2026.01.00" +compose-bom = "2026.01.01" [libraries] activity = "androidx.activity:activity-compose:1.12.3" From 96f9250d5f8c59f6ec49c91b9a15d14f3149f825 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 17:46:46 +0100 Subject: [PATCH 36/40] Add "src:" prefix to search by source ID Allows users to search for the exact source ID in their library. Similar to the Browse > Migrate screen, but filtered in the Library, where users can take all the usual actions. Could be used in the future to change the search behaviour of tapping the source name in the title info view to search by the ID with this prefix. Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .../main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index c3559d28c6..7b70b7e912 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -23,9 +23,12 @@ data class LibraryItem( * @return true if the anime matches the query, false otherwise. */ fun matches(constraint: String): Boolean { - val sourceName by lazy { sourceManager.getOrStub(libraryAnime.anime.source).getNameForAnimeInfo() } + val source = sourceManager.getOrStub(libraryAnime.anime.source) + val sourceName by lazy { source.getNameForAnimeInfo() } if (constraint.startsWith("id:", true)) { return id == constraint.substringAfter("id:").toLongOrNull() + } else if (constraint.startsWith("src:", true)) { + return source.id == constraint.substringAfter("src:").toLongOrNull() } return libraryAnime.anime.title.contains(constraint, true) || (libraryAnime.anime.author?.contains(constraint, true) ?: false) || From 3bce489312499d21e823a8e5f5b0bb98dd033da2 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 18:02:32 +0100 Subject: [PATCH 37/40] Clean up some build warnings * Replace deprecated rememberPlainTooltipPositionProvider * Remove superfluous when branch This when is marked as exhaustive. * Replace deprecated LibrariesContainer call AboutLibraries now wants us to produce the libraries ourselves. * Replace deprecated ClipboardManager with Clipboard Clipboard uses suspend functions, hence the coroutine scope addition. * Use multi-dollar strs to simplify GraphQL queries These have been available since Kotlin 2.1. * Remove various redundant casts & conversions - WebViewScreenContent: loadingState is in the LoadingState.Loading branch, no need to cast at all - Bangumi: username is not modified, make val - Kavita: token is already a String - PagerViewerAdapter: insertPageLastPage is already null-checked - PagerViewerAdapter: use reified filterIsInstance - ReaderViewModel: chapter IDs are already Longs - CloudflareInterceptor: webview is smart-cast to non-null here * Replace deprecated MenuAnchorType Literally just a typealias for ExposedDropdownMenuAnchorType anyway. * OptimizeNonSkippingGroups is enabled by default * Suppress shadowing warning This is explicitly intentional according to the KDocs. * Migrate Context Receivers to Context Parameters Requires changing the compiler arg, but that is part of the migration: https://blog.jetbrains.com/kotlin/2025/04/update-on-context-parameters Apparently, the only visible change is that names are required now. "_" can be used for anonymous context parameters. * Fix expression bodies with explicit return Naming conflict resolved by aliasing. From 2.4/2.5 onward, these will only be allowed with explicit return types, or have to be turned into a block body. I opted for the latter since the function is reasonably dense already. see: https://youtrack.jetbrains.com/issue/KTLC-288 * Suppress deprecation of non-AutoMirrored icons We use these arrows for navigation in the Upcoming screen. I strongly doubt the AutoMirrored versions would make sense for our use-case. * Explicitly opt-in to new annotation default rules affects the following annotated value-parameters: - Preference.SliderPreference.steps (`@IntRange`) - ReaderViewModel.State.brightnessOverlayValue (`@IntRange`) - ReadingMode.iconRes (`@DrawableRes`) - MigrationListScreenModel.Dialog.Progress.progress (`@FloatRange`) see: https://youtrack.jetbrains.com/issue/KT-73255 see: https://github.com/Kotlin/KEEP/blob/change-defaulting-rule/proposals/annotation-target-in-properties.md Warning message was the following: This annotation is currently applied to the value parameter only, but in the future it will also be applied to field. - To opt in to applying to both value parameter and field, add '-Xannotation-default-target=param-property' to your compiler arguments. - To keep applying to the value parameter only, use the '@param:' annotation target. Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- app/build.gradle.kts | 1 + .../kanade/presentation/components/AppBar.kt | 11 +++--- .../more/settings/screen/about/AboutScreen.kt | 1 - .../screen/about/OpenSourceLicensesScreen.kt | 5 +++ .../presentation/track/TrackerSearch.kt | 17 ++++++++-- .../presentation/util/ExceptionFormatter.kt | 14 ++++---- .../util/FastScrollAnimateItem.kt | 6 ++-- .../webview/WebViewScreenContent.kt | 2 +- .../data/track/anilist/AnilistApi.kt | 34 +++++++++---------- .../tachiyomi/data/track/bangumi/Bangumi.kt | 2 +- .../tachiyomi/data/track/jellyfin/Jellyfin.kt | 2 +- .../util/view/EditTextPreferenceExtensions.kt | 1 + .../components/calendar/CalendarHeader.kt | 2 ++ .../mihon/buildlogic/ProjectExtensions.kt | 5 +-- .../tachiyomi/network/OkHttpExtensions.kt | 6 ++-- .../interceptor/CloudflareInterceptor.kt | 4 +-- .../core/components/SettingsItems.kt | 4 +-- 17 files changed, 69 insertions(+), 48 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2eb8530826..993179eaaa 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -165,6 +165,7 @@ kotlin { "-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.coroutines.InternalCoroutinesApi", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", + "-Xannotation-default-target=param-property", ) } } 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 b400917c99..1efeb34e30 100644 --- a/app/src/main/java/eu/kanade/presentation/components/AppBar.kt +++ b/app/src/main/java/eu/kanade/presentation/components/AppBar.kt @@ -21,8 +21,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PlainTooltip import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.TooltipAnchorPosition import androidx.compose.material3.TooltipBox -import androidx.compose.material3.TooltipDefaults +import androidx.compose.material3.TooltipDefaults.rememberTooltipPositionProvider import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarScrollBehavior @@ -195,7 +196,7 @@ fun AppBarActions( actions.filterIsInstance().map { TooltipBox( - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), tooltip = { PlainTooltip { Text(it.title) @@ -220,7 +221,7 @@ fun AppBarActions( val overflowActions = actions.filterIsInstance() if (overflowActions.isNotEmpty()) { TooltipBox( - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), tooltip = { PlainTooltip { Text(stringResource(MR.strings.action_menu_overflow_description)) @@ -349,7 +350,7 @@ fun SearchToolbar( // Don't show search action } else if (searchQuery == null) { TooltipBox( - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), tooltip = { PlainTooltip { Text(stringResource(MR.strings.action_search)) @@ -369,7 +370,7 @@ fun SearchToolbar( } } else if (searchQuery.isNotEmpty()) { TooltipBox( - positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), + positionProvider = rememberTooltipPositionProvider(TooltipAnchorPosition.Above), tooltip = { PlainTooltip { Text(stringResource(MR.strings.action_reset)) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt index ff5c69b1fa..1c50fc8318 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/AboutScreen.kt @@ -220,7 +220,6 @@ object AboutScreen : Screen() { is GetApplicationRelease.Result.OsTooOld -> { context.toast(MR.strings.update_check_eol) } - else -> {} } } catch (e: Exception) { context.toast(e.message) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt index b879f0d2d0..3385f7430b 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/about/OpenSourceLicensesScreen.kt @@ -2,13 +2,16 @@ package eu.kanade.presentation.more.settings.screen.about import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow +import com.mikepenz.aboutlibraries.ui.compose.android.produceLibraries import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer import com.mikepenz.aboutlibraries.ui.compose.util.htmlReadyLicenseContent import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.util.Screen +import eu.kanade.tachiyomi.R import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.i18n.stringResource @@ -27,7 +30,9 @@ class OpenSourceLicensesScreen : Screen() { ) }, ) { contentPadding -> + val libraries by produceLibraries(R.raw.aboutlibraries) LibrariesContainer( + libraries = libraries, modifier = Modifier .fillMaxSize(), contentPadding = contentPadding, diff --git a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt index 02b14a7b4d..a291594d43 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt @@ -1,5 +1,6 @@ package eu.kanade.presentation.track +import android.content.ClipData import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -47,6 +48,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -55,10 +57,13 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.Clipboard import androidx.compose.ui.platform.ClipboardManager +import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.toClipEntry import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.input.ImeAction @@ -73,6 +78,7 @@ import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.theme.TachiyomiPreviewTheme import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.util.system.openInBrowser +import kotlinx.coroutines.launch import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Scaffold @@ -240,7 +246,7 @@ private fun SearchResultItem( onClick: () -> Unit, ) { val context = LocalContext.current - val clipboardManager: ClipboardManager = LocalClipboardManager.current + val clipboard: Clipboard = LocalClipboard.current val focusManager = LocalFocusManager.current val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current) val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current) @@ -248,6 +254,7 @@ private fun SearchResultItem( val shape = RoundedCornerShape(16.dp) val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent var dropDownMenuExpanded by remember { mutableStateOf(false) } + val scope = rememberCoroutineScope() Box( modifier = Modifier .fillMaxWidth() @@ -295,7 +302,13 @@ private fun SearchResultItem( expanded = dropDownMenuExpanded, onCollapseMenu = { dropDownMenuExpanded = false }, onCopyName = { - clipboardManager.setText(AnnotatedString(trackSearch.title)) + scope.launch { + val clipEntry = ClipData.newPlainText( + trackSearch.title, + trackSearch.title, + ).toClipEntry() + clipboard.setClipEntry(clipEntry) + } }, onOpenInBrowser = { val url = trackSearch.tracking_url diff --git a/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt b/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt index e4e4eed039..db470a0f4b 100644 --- a/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt +++ b/app/src/main/java/eu/kanade/presentation/util/ExceptionFormatter.kt @@ -9,21 +9,21 @@ import tachiyomi.domain.source.model.SourceNotInstalledException import tachiyomi.i18n.MR import java.net.UnknownHostException -context(Context) +context(context: Context) val Throwable.formattedMessage: String get() { when (this) { - is HttpException -> return stringResource(MR.strings.exception_http, code) + is HttpException -> return context.stringResource(MR.strings.exception_http, code) is UnknownHostException -> { - return if (!isOnline()) { - stringResource(MR.strings.exception_offline) + return if (!context.isOnline()) { + context.stringResource(MR.strings.exception_offline) } else { - stringResource(MR.strings.exception_unknown_host, message ?: "") + context.stringResource(MR.strings.exception_unknown_host, message ?: "") } } - is NoResultsException -> return stringResource(MR.strings.no_results_found) - is SourceNotInstalledException -> return stringResource(MR.strings.loader_not_implemented_error) + is NoResultsException -> return context.stringResource(MR.strings.no_results_found) + is SourceNotInstalledException -> return context.stringResource(MR.strings.loader_not_implemented_error) } return when (val className = this::class.simpleName) { "Exception", "IOException" -> message ?: className diff --git a/app/src/main/java/eu/kanade/presentation/util/FastScrollAnimateItem.kt b/app/src/main/java/eu/kanade/presentation/util/FastScrollAnimateItem.kt index a6c9f70198..9d24b8520e 100644 --- a/app/src/main/java/eu/kanade/presentation/util/FastScrollAnimateItem.kt +++ b/app/src/main/java/eu/kanade/presentation/util/FastScrollAnimateItem.kt @@ -4,5 +4,7 @@ import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.ui.Modifier // https://issuetracker.google.com/352584409 -context(LazyItemScope) -fun Modifier.animateItemFastScroll() = this.animateItem(fadeInSpec = null, fadeOutSpec = null) +context(itemScope: LazyItemScope) +fun Modifier.animateItemFastScroll() = with(itemScope) { + this@animateItemFastScroll.animateItem(fadeInSpec = null, fadeOutSpec = null) +} diff --git a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt index af0f238919..1dfab78af3 100644 --- a/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt +++ b/app/src/main/java/eu/kanade/presentation/webview/WebViewScreenContent.kt @@ -273,7 +273,7 @@ fun WebViewScreenContent( .align(Alignment.BottomCenter), ) is LoadingState.Loading -> LinearProgressIndicator( - progress = { (loadingState as? LoadingState.Loading)?.progress ?: 1f }, + progress = { loadingState.progress }, modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt index 055c882ab4..81e39db7e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/anilist/AnilistApi.kt @@ -41,9 +41,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { suspend fun addLibAnime(track: Track): Track { return withIOContext { - val query = """ - |mutation AddAnime(${'$'}animeId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean) { - |SaveMediaListEntry (mediaId: ${'$'}animeId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private) { + val query = $$""" + |mutation AddAnime($animeId: Int, $progress: Int, $status: MediaListStatus, $private: Boolean) { + |SaveMediaListEntry (mediaId: $animeId, progress: $progress, status: $status, private: $private) { | id | status |} @@ -78,14 +78,14 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { suspend fun updateLibAnime(track: Track): Track { return withIOContext { - val query = """ + val query = $$""" |mutation UpdateAnime( - |${'$'}listId: Int, ${'$'}progress: Int, ${'$'}status: MediaListStatus, ${'$'}private: Boolean, - |${'$'}score: Int, ${'$'}startedAt: FuzzyDateInput, ${'$'}completedAt: FuzzyDateInput + |$listId: Int, $progress: Int, $status: MediaListStatus, $private: Boolean, + |$score: Int, $startedAt: FuzzyDateInput, $completedAt: FuzzyDateInput |) { |SaveMediaListEntry( - |id: ${'$'}listId, progress: ${'$'}progress, status: ${'$'}status, private: ${'$'}private, - |scoreRaw: ${'$'}score, startedAt: ${'$'}startedAt, completedAt: ${'$'}completedAt + |id: $listId, progress: $progress, status: $status, private: $private, + |scoreRaw: $score, startedAt: $startedAt, completedAt: $completedAt |) { |id |status @@ -114,9 +114,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { suspend fun deleteLibAnime(track: DomainTrack) { withIOContext { - val query = """ - |mutation DeleteAnime(${'$'}listId: Int) { - |DeleteMediaListEntry(id: ${'$'}listId) { + val query = $$""" + |mutation DeleteAnime($listId: Int) { + |DeleteMediaListEntry(id: $listId) { |deleted |} |} @@ -135,10 +135,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { suspend fun search(search: String): List { return withIOContext { - val query = """ - |query Search(${'$'}query: String) { + val query = $$""" + |query Search($query: String) { |Page (perPage: 50) { - |media(search: ${'$'}query, type: ANIME) { + |media(search: $query, type: ANIME) { |id |studios { |edges { @@ -192,10 +192,10 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) { suspend fun findLibAnime(track: Track, userid: Int): Track? { return withIOContext { - val query = """ - |query (${'$'}id: Int!, ${'$'}anime_id: Int!) { + val query = $$""" + |query ($id: Int!, $anime_id: Int!) { |Page { - |mediaList(userId: ${'$'}id, type: ANIME, mediaId: ${'$'}anime_id) { + |mediaList(userId: $id, type: ANIME, mediaId: $anime_id) { |id |status |scoreRaw: score(format: POINT_100) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt index e30972cecb..dcf766b914 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/bangumi/Bangumi.kt @@ -109,7 +109,7 @@ class Bangumi(id: Long) : BaseTracker(id, "Bangumi") { // Users can set a 'username' (not nickname) once which effectively // replaces the stringified ID in certain queries. // If no username is set, the API returns the user ID as a strings - var username = api.getUsername() + val username = api.getUsername() saveCredentials(username, oauth.accessToken) } catch (_: Throwable) { logout() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/track/jellyfin/Jellyfin.kt b/app/src/main/java/eu/kanade/tachiyomi/data/track/jellyfin/Jellyfin.kt index 50fb293e28..d25efa7002 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/track/jellyfin/Jellyfin.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/track/jellyfin/Jellyfin.kt @@ -85,7 +85,7 @@ class Jellyfin(id: Long) : BaseTracker(id, "Jellyfin"), EnhancedTracker { override suspend fun match(anime: Anime): TrackSearch? = try { api.getTrackSearch(anime.url) - } catch (e: Exception) { + } catch (_: Exception) { null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt index 4428fb9ae1..da3cb6d7dc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/view/EditTextPreferenceExtensions.kt @@ -5,6 +5,7 @@ package androidx.preference /** * Returns package-private [EditTextPreference.getOnBindEditTextListener] */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") fun EditTextPreference.getOnBindEditTextListener(): EditTextPreference.OnBindEditTextListener? { return onBindEditTextListener } diff --git a/app/src/main/java/mihon/feature/upcoming/components/calendar/CalendarHeader.kt b/app/src/main/java/mihon/feature/upcoming/components/calendar/CalendarHeader.kt index f5dbf5c485..b2cbbee6ee 100644 --- a/app/src/main/java/mihon/feature/upcoming/components/calendar/CalendarHeader.kt +++ b/app/src/main/java/mihon/feature/upcoming/components/calendar/CalendarHeader.kt @@ -54,9 +54,11 @@ fun CalenderHeader( } Row { IconButton(onClick = onPreviousClick) { + @Suppress("DEPRECATION") Icon(Icons.Default.KeyboardArrowLeft, stringResource(MR.strings.upcoming_calendar_prev)) } IconButton(onClick = onNextClick) { + @Suppress("DEPRECATION") Icon(Icons.Default.KeyboardArrowRight, stringResource(MR.strings.upcoming_calendar_next)) } } diff --git a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt index 916ec38f42..a4f5577d3b 100644 --- a/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt +++ b/buildSrc/src/main/kotlin/mihon/buildlogic/ProjectExtensions.kt @@ -14,7 +14,6 @@ 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.compose.compiler.gradle.ComposeFeatureFlag import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.File @@ -42,7 +41,7 @@ internal fun Project.configureAndroid(commonExtension: CommonExtension<*, *, *, compilerOptions { jvmTarget.set(AndroidConfig.JvmTarget) freeCompilerArgs.addAll( - "-Xcontext-receivers", + "-Xcontext-parameters", "-opt-in=kotlin.RequiresOptIn", ) @@ -73,8 +72,6 @@ internal fun Project.configureCompose(commonExtension: CommonExtension<*, *, *, } extensions.configure { - featureFlags.set(setOf(ComposeFeatureFlag.OptimizeNonSkippingGroups)) - val enableMetrics = project.providers.gradleProperty("enableComposeCompilerMetrics").orNull.toBoolean() val enableReports = project.providers.gradleProperty("enableComposeCompilerReports").orNull.toBoolean() diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 2417f6b90c..0a860d0697 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -134,18 +134,18 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre return progressClient.newCall(request) } -context(Json) +context(_: Json) inline fun Response.parseAs(): T { return decodeFromJsonResponse(serializer(), this) } -context(Json) +context(json: Json) fun decodeFromJsonResponse( deserializer: DeserializationStrategy, response: Response, ): T { return response.body.source().use { - decodeFromBufferedSource(deserializer, it) + json.decodeFromBufferedSource(deserializer, it) } } diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index e978a52cf8..517679aca8 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -87,7 +87,7 @@ class CloudflareInterceptor( executor.execute { webview = createWebView(originalRequest) - webview?.webViewClient = object : WebViewClient() { + webview.webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView, url: String) { fun isCloudFlareBypassed(): Boolean { return cookieManager.get(origRequestUrl.toHttpUrl()) @@ -123,7 +123,7 @@ class CloudflareInterceptor( } } - webview?.loadUrl(origRequestUrl, headers) + webview.loadUrl(origRequestUrl, headers) } latch.awaitFor30Seconds() 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 0e35f2d334..b03e49a213 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 @@ -23,11 +23,11 @@ import androidx.compose.material.icons.rounded.CheckBoxOutlineBlank import androidx.compose.material.icons.rounded.DisabledByDefault import androidx.compose.material3.Checkbox import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExposedDropdownMenuAnchorType import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.RadioButton import androidx.compose.material3.Surface @@ -296,7 +296,7 @@ fun SelectItem( ) { OutlinedTextField( modifier = Modifier - .menuAnchor(MenuAnchorType.PrimaryNotEditable) + .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable) .fillMaxWidth() .padding( horizontal = SettingsItemsPaddings.Horizontal, From 53319fb5304d123236d1bc2d1fbcaa39b315c438 Mon Sep 17 00:00:00 2001 From: Secozzi Date: Tue, 10 Feb 2026 18:03:52 +0100 Subject: [PATCH 38/40] Add src:local search alias for Local Source Co-authored-by: MajorTanya <39014446+MajorTanya@users.noreply.github.com> --- .../java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt index 7b70b7e912..58ccaae44f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt @@ -3,9 +3,12 @@ package eu.kanade.tachiyomi.ui.library import eu.kanade.tachiyomi.source.getNameForAnimeInfo import tachiyomi.domain.library.model.LibraryAnime import tachiyomi.domain.source.service.SourceManager +import tachiyomi.source.local.LocalSource import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +private const val LOCAL_SOURCE_ID_ALIAS = "local" + data class LibraryItem( val libraryAnime: LibraryAnime, val downloadCount: Long = -1, @@ -28,7 +31,12 @@ data class LibraryItem( if (constraint.startsWith("id:", true)) { return id == constraint.substringAfter("id:").toLongOrNull() } else if (constraint.startsWith("src:", true)) { - return source.id == constraint.substringAfter("src:").toLongOrNull() + val querySource = constraint.substringAfter("src:") + return if (querySource.equals(LOCAL_SOURCE_ID_ALIAS, ignoreCase = true)) { + source.id == LocalSource.ID + } else { + source.id == querySource.toLongOrNull() + } } return libraryAnime.anime.title.contains(constraint, true) || (libraryAnime.anime.author?.contains(constraint, true) ?: false) || From 656cccb2faf9cc37ac3c1739b6d943c238c6d670 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 9 Feb 2026 00:31:53 +0000 Subject: [PATCH 39/40] Update dependency com.materialkolor:material-kolor to v5.0.0-alpha06 (#2938) (cherry picked from commit b609166702e0788160a557fa1470c6dd8e585169) --- 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 5b5147a454..4df29ae398 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ ktlint-core = "1.8.0" firebase-bom = "34.8.0" markdown = "0.39.2" junit = "6.0.2" -materialKolor = "5.0.0-alpha05" +materialKolor = "5.0.0-alpha06" [libraries] desugar = "com.android.tools:desugar_jdk_libs:2.1.5" From 4456e281c486d7a8ca6dca4175672df7101831ad Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 9 Feb 2026 00:32:07 +0000 Subject: [PATCH 40/40] Update dependency io.kotest:kotest-assertions-core to v6.1.3 (#2939) (cherry picked from commit d17976c91068944be1a5cba9a959c3763f589590) --- 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 4df29ae398..41efe2a52f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,7 @@ sqldelight-dialects-sql = { module = "app.cash.sqldelight:sqlite-3-38-dialect", junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } -kotest-assertions = "io.kotest:kotest-assertions-core:6.1.2" +kotest-assertions = "io.kotest:kotest-assertions-core:6.1.3" mockk = "io.mockk:mockk:1.14.9" voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }