diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt index a3592e1f4..d5cfc248d 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt @@ -81,9 +81,9 @@ class UpdateManga( dateTime: ZonedDateTime = ZonedDateTime.now(), window: Pair = fetchInterval.getWindow(dateTime), ): Boolean { - return fetchInterval.toMangaUpdateOrNull(manga, dateTime, window) - ?.let { mangaRepository.update(it) } - ?: false + return mangaRepository.update( + fetchInterval.toMangaUpdate(manga, dateTime, window), + ) } suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean { diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt b/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt index e22d8980f..f55532e12 100644 --- a/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt +++ b/app/src/main/java/eu/kanade/domain/source/interactor/CreateSourceRepo.kt @@ -11,7 +11,7 @@ class CreateSourceRepo(private val preferences: SourcePreferences) { return Result.InvalidUrl } - preferences.extensionRepos() += name.substringBeforeLast("/index.min.json") + preferences.extensionRepos() += name.removeSuffix("/index.min.json") return Result.Success } diff --git a/app/src/main/java/eu/kanade/domain/source/interactor/TrustExtension.kt b/app/src/main/java/eu/kanade/domain/source/interactor/TrustExtension.kt index f6da6613d..cd0e02274 100644 --- a/app/src/main/java/eu/kanade/domain/source/interactor/TrustExtension.kt +++ b/app/src/main/java/eu/kanade/domain/source/interactor/TrustExtension.kt @@ -17,11 +17,15 @@ class TrustExtension( fun trust(pkgName: String, versionCode: Long, signatureHash: String) { preferences.trustedExtensions().getAndSet { exts -> // Remove previously trusted versions - val removed = exts.filter { it.startsWith("$pkgName:") }.toMutableSet() + val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet() removed.also { it += "$pkgName:$versionCode:$signatureHash" } } } + + fun revokeAll() { + preferences.trustedExtensions().delete() + } } diff --git a/app/src/main/java/eu/kanade/domain/track/model/Track.kt b/app/src/main/java/eu/kanade/domain/track/model/Track.kt index e84e28ff0..332ddd8d5 100644 --- a/app/src/main/java/eu/kanade/domain/track/model/Track.kt +++ b/app/src/main/java/eu/kanade/domain/track/model/Track.kt @@ -40,7 +40,9 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? { lastChapterRead = last_chapter_read.toDouble(), totalChapters = total_chapters.toLong(), status = status.toLong(), - score = score.toDouble(), + // Jank workaround due to precision issues while converting + // See https://github.com/tachiyomiorg/tachiyomi/issues/10343 + score = score.toString().toDouble(), remoteUrl = tracking_url, startDate = started_reading_date, finishDate = finished_reading_date, diff --git a/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt b/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt index 2fdeb0e3c..06194f8c9 100644 --- a/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt +++ b/app/src/main/java/eu/kanade/domain/ui/model/AppTheme.kt @@ -9,6 +9,7 @@ enum class AppTheme(val titleRes: StringResource?) { GREEN_APPLE(MR.strings.theme_greenapple), LAVENDER(MR.strings.theme_lavender), MIDNIGHT_DUSK(MR.strings.theme_midnightdusk), + NORD(MR.strings.theme_nord), STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri), TAKO(MR.strings.theme_tako), TEALTURQUOISE(MR.strings.theme_tealturquoise), diff --git a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt index 3c4b8e2ca..213fe9dd2 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/ExtensionDetailsScreen.kt @@ -53,6 +53,7 @@ import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel import eu.kanade.tachiyomi.util.system.LocaleHelper +import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ScrollbarLazyColumn @@ -138,7 +139,7 @@ fun ExtensionDetailsScreen( private fun ExtensionDetails( contentPadding: PaddingValues, extension: Extension.Installed, - sources: List, + sources: ImmutableList, onClickSourcePreferences: (sourceId: Long) -> Unit, onClickUninstall: () -> Unit, onClickSource: (sourceId: Long) -> Unit, @@ -150,18 +151,24 @@ private fun ExtensionDetails( contentPadding = contentPadding, ) { when { - extension.isRepoSource -> + extension.isFromExternalRepo -> item { val uriHandler = LocalUriHandler.current + val url = remember(extension) { + val regex = """https://raw.githubusercontent.com/(.+?)/(.+?)/.+""".toRegex() + regex.find(extension.repoUrl.orEmpty()) + ?.let { + val (user, repo) = it.destructured + "https://github.com/$user/$repo" + } + ?: extension.repoUrl + } + WarningBanner( MR.strings.repo_extension_message, modifier = Modifier.clickable { - extension.repoUrl ?: return@clickable - uriHandler.openUri( - extension.repoUrl - .replace("https://raw.githubusercontent.com", "https://github.com") - .removeSuffix("/repo/"), - ) + url ?: return@clickable + uriHandler.openUri(url) }, ) } diff --git a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt index 5c9b092a3..1ab73504a 100644 --- a/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt +++ b/app/src/main/java/eu/kanade/presentation/library/LibrarySettingsDialog.kt @@ -74,6 +74,8 @@ private fun ColumnScope.FilterPage( ) { val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState() val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState() + val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState() + TriStateItem( label = stringResource(MR.strings.label_downloaded), state = if (downloadedOnly) { @@ -108,6 +110,14 @@ private fun ColumnScope.FilterPage( state = filterCompleted, onClick = { screenModel.toggleFilter(LibraryPreferences::filterCompleted) }, ) + if (LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in autoUpdateMangaRestrictions) { + val filterIntervalCustom by screenModel.libraryPreferences.filterIntervalCustom().collectAsState() + TriStateItem( + label = stringResource(MR.strings.action_filter_interval_custom), + state = filterIntervalCustom, + onClick = { screenModel.toggleFilter(LibraryPreferences::filterIntervalCustom) }, + ) + } val trackers = remember { screenModel.trackers } when (trackers.size) { diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt index 655601a0a..a6ef3f9a5 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaDialogs.kt @@ -30,6 +30,7 @@ import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.stringResource import java.time.Instant import java.time.temporal.ChronoUnit +import kotlin.math.absoluteValue @Composable fun DeleteChaptersDialog( @@ -85,7 +86,7 @@ fun SetIntervalDialog( title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) }, text = { Column { - if (nextUpdateDays != null && nextUpdateDays >= 0) { + if (nextUpdateDays != null && nextUpdateDays >= 0 && interval >= 0) { Text( stringResource( MR.strings.manga_interval_expected_update, @@ -96,8 +97,8 @@ fun SetIntervalDialog( ), pluralStringResource( MR.plurals.day, - count = interval, - interval, + count = interval.absoluteValue, + interval.absoluteValue, ), ), ) @@ -105,7 +106,6 @@ fun SetIntervalDialog( Spacer(Modifier.height(MaterialTheme.padding.small)) } - // TODO: selecting "1" then doesn't allow for future changes unless defaulting first? if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) { Text(stringResource(MR.strings.manga_interval_custom_amount)) diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt index f1d228534..0ad66a9a8 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt @@ -201,14 +201,14 @@ fun MangaActionRow( onLongClick = onEditCategory, ) MangaActionButton( - title = if (nextUpdateDays != null) { - pluralStringResource( + title = when (nextUpdateDays) { + null -> stringResource(MR.strings.not_applicable) + 0 -> stringResource(MR.strings.manga_interval_expected_update_soon) + else -> pluralStringResource( MR.plurals.day, count = nextUpdateDays, nextUpdateDays, ) - } else { - stringResource(MR.strings.not_applicable) }, icon = Icons.Default.HourglassEmpty, color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor, diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt index 4a486b0b4..9f046480e 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt @@ -24,6 +24,7 @@ import androidx.core.net.toUri import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.domain.base.BasePreferences +import eu.kanade.domain.source.interactor.TrustExtension import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen @@ -340,6 +341,7 @@ object SettingsAdvancedScreen : SearchableSettings { val uriHandler = LocalUriHandler.current val extensionInstallerPref = basePreferences.extensionInstaller() var shizukuMissing by rememberSaveable { mutableStateOf(false) } + val trustExtension = remember { Injekt.get() } if (shizukuMissing) { val dismiss = { shizukuMissing = false } @@ -392,6 +394,13 @@ object SettingsAdvancedScreen : SearchableSettings { } }, ), + Preference.PreferenceItem.TextPreference( + title = stringResource(MR.strings.ext_revoke_trust), + onClick = { + trustExtension.revokeAll() + context.toast(MR.strings.requires_app_restart) + }, + ), ), ) } 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 c464ceb3a..fd650f53c 100644 --- a/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt +++ b/app/src/main/java/eu/kanade/presentation/theme/TachiyomiTheme.kt @@ -12,6 +12,7 @@ import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme import eu.kanade.presentation.theme.colorscheme.MonetColorScheme +import eu.kanade.presentation.theme.colorscheme.NordColorScheme import eu.kanade.presentation.theme.colorscheme.StrawberryColorScheme import eu.kanade.presentation.theme.colorscheme.TachiyomiColorScheme import eu.kanade.presentation.theme.colorscheme.TakoColorScheme @@ -47,6 +48,7 @@ private fun getThemeColorScheme( AppTheme.GREEN_APPLE -> GreenAppleColorScheme AppTheme.LAVENDER -> LavenderColorScheme AppTheme.MIDNIGHT_DUSK -> MidnightDuskColorScheme + AppTheme.NORD -> NordColorScheme AppTheme.STRAWBERRY_DAIQUIRI -> StrawberryColorScheme AppTheme.TAKO -> TakoColorScheme AppTheme.TEALTURQUOISE -> TealTurqoiseColorScheme diff --git a/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt new file mode 100644 index 000000000..d493e2d62 --- /dev/null +++ b/app/src/main/java/eu/kanade/presentation/theme/colorscheme/NordColorScheme.kt @@ -0,0 +1,72 @@ +package eu.kanade.presentation.theme.colorscheme + +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.ui.graphics.Color + +/** + * Colors for Nord theme + * https://www.nordtheme.com/docs/colors-and-palettes + * for the light theme, the primary color is switched with the tertiary for better contrast in some case + */ +internal object NordColorScheme : BaseColorScheme() { + + override val darkScheme = darkColorScheme( + primary = Color(0xFF88C0D0), + onPrimary = Color(0xFF2E3440), + primaryContainer = Color(0xFF88C0D0), + onPrimaryContainer = Color(0xFF2E3440), + inversePrimary = Color(0xFF397E91), + secondary = Color(0xFF81A1C1), + onSecondary = Color(0xFF2E3440), + secondaryContainer = Color(0xFF81A1C1), + onSecondaryContainer = Color(0xFF2E3440), + tertiary = Color(0xFF5E81AC), + onTertiary = Color(0xFF000000), + tertiaryContainer = Color(0xFF5E81AC), + onTertiaryContainer = Color(0xFF000000), + background = Color(0xFF2E3440), + onBackground = Color(0xFFECEFF4), + surface = Color(0xFF3B4252), + onSurface = Color(0xFFECEFF4), + surfaceVariant = Color(0xFF2E3440), + onSurfaceVariant = Color(0xFFECEFF4), + surfaceTint = Color(0xFF88C0D0), + inverseSurface = Color(0xFFD8DEE9), + inverseOnSurface = Color(0xFF2E3440), + outline = Color(0xFF6d717b), + outlineVariant = Color(0xFF90939a), + onError = Color(0xFF2E3440), + errorContainer = Color(0xFFBF616A), + onErrorContainer = Color(0xFF000000), + ) + + override val lightScheme = lightColorScheme( + primary = Color(0xFF5E81AC), + onPrimary = Color(0xFF000000), + primaryContainer = Color(0xFF5E81AC), + onPrimaryContainer = Color(0xFF000000), + inversePrimary = Color(0xFF8CA8CD), + secondary = Color(0xFF81A1C1), + onSecondary = Color(0xFF2E3440), + secondaryContainer = Color(0xFF81A1C1), + onSecondaryContainer = Color(0xFF2E3440), + tertiary = Color(0xFF88C0D0), + onTertiary = Color(0xFF2E3440), + tertiaryContainer = Color(0xFF88C0D0), + onTertiaryContainer = Color(0xFF2E3440), + background = Color(0xFFECEFF4), + onBackground = Color(0xFF2E3440), + surface = Color(0xFFE5E9F0), + onSurface = Color(0xFF2E3440), + surfaceVariant = Color(0xFFffffff), + onSurfaceVariant = Color(0xFF2E3440), + surfaceTint = Color(0xFF5E81AC), + inverseSurface = Color(0xFF3B4252), + inverseOnSurface = Color(0xFFECEFF4), + outline = Color(0xFF2E3440), + onError = Color(0xFFECEFF4), + errorContainer = Color(0xFFBF616A), + onErrorContainer = Color(0xFF000000), + ) +} 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 3ed269e2f..08738c2ee 100644 --- a/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt +++ b/app/src/main/java/eu/kanade/presentation/track/TrackerSearch.kt @@ -7,6 +7,7 @@ import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -22,7 +23,6 @@ import androidx.compose.foundation.layout.paddingFromBaseline import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions @@ -33,6 +33,7 @@ import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.Close import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -40,7 +41,10 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -48,7 +52,11 @@ 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.ClipboardManager +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.TextFieldValue @@ -58,9 +66,11 @@ import androidx.compose.ui.text.toLowerCase import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp +import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.tachiyomi.data.track.model.TrackSearch +import eu.kanade.tachiyomi.util.system.openInBrowser import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.material.Scaffold @@ -188,13 +198,7 @@ fun TrackerSearch( key = { it.hashCode() }, ) { SearchResultItem( - title = it.title, - coverUrl = it.cover_url, - type = it.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current), - startDate = it.start_date, - status = it.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current), - score = it.score, - description = it.summary.trim(), + trackSearch = it, selected = it == selected, onClick = { onSelectedChange(it) }, ) @@ -214,18 +218,18 @@ fun TrackerSearch( @Composable private fun SearchResultItem( - title: String, - coverUrl: String, - type: String, - startDate: String, - status: String, - score: Float, - description: String, + trackSearch: TrackSearch, selected: Boolean, onClick: () -> Unit, ) { + val context = LocalContext.current + val clipboardManager: ClipboardManager = LocalClipboardManager.current + val type = trackSearch.publishing_type.toLowerCase(Locale.current).capitalize(Locale.current) + val status = trackSearch.publishing_status.toLowerCase(Locale.current).capitalize(Locale.current) + val description = trackSearch.summary.trim() val shape = RoundedCornerShape(16.dp) val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent + var dropDownMenuExpanded by remember { mutableStateOf(false) } Box( modifier = Modifier .fillMaxWidth() @@ -237,7 +241,10 @@ private fun SearchResultItem( color = borderColor, shape = shape, ) - .selectable(selected = selected, onClick = onClick) + .combinedClickable( + onLongClick = { dropDownMenuExpanded = true }, + onClick = onClick, + ) .padding(12.dp), ) { if (selected) { @@ -251,28 +258,41 @@ private fun SearchResultItem( Column { Row { MangaCover.Book( - data = coverUrl, + data = trackSearch.cover_url, modifier = Modifier.height(96.dp), ) Spacer(modifier = Modifier.width(12.dp)) Column { Text( - text = title, + text = trackSearch.title, modifier = Modifier.padding(end = 28.dp), maxLines = 2, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.titleMedium, ) + SearchResultItemDropDownMenu( + expanded = dropDownMenuExpanded, + onCollapseMenu = { dropDownMenuExpanded = false }, + onCopyName = { + clipboardManager.setText(AnnotatedString(trackSearch.title)) + }, + onOpenInBrowser = { + val url = trackSearch.tracking_url + if (url.isNotBlank()) { + context.openInBrowser(url) + } + }, + ) if (type.isNotBlank()) { SearchResultItemDetails( title = stringResource(MR.strings.track_type), text = type, ) } - if (startDate.isNotBlank()) { + if (trackSearch.start_date.isNotBlank()) { SearchResultItemDetails( title = stringResource(MR.strings.label_started), - text = startDate, + text = trackSearch.start_date, ) } if (status.isNotBlank()) { @@ -281,10 +301,10 @@ private fun SearchResultItem( text = status, ) } - if (score != -1f) { + if (trackSearch.score != -1f) { SearchResultItemDetails( title = stringResource(MR.strings.score), - text = score.toString(), + text = trackSearch.score.toString(), ) } } @@ -304,6 +324,33 @@ private fun SearchResultItem( } } +@Composable +private fun SearchResultItemDropDownMenu( + expanded: Boolean, + onCollapseMenu: () -> Unit, + onCopyName: () -> Unit, + onOpenInBrowser: () -> Unit, +) { + DropdownMenu( + expanded = expanded, + onDismissRequest = onCollapseMenu, + ) { + DropdownMenuItem( + text = { Text(stringResource(MR.strings.action_copy_to_clipboard)) }, + onClick = { + onCopyName() + onCollapseMenu() + }, + ) + DropdownMenuItem( + text = { Text(stringResource(MR.strings.action_open_in_browser)) }, + onClick = { + onOpenInBrowser() + }, + ) + } +} + @Composable private fun SearchResultItemDetails( title: String, diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 942e8f4a1..156d881c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -185,7 +185,17 @@ class ExtensionManager( val hasUpdate = installedExt.updateExists(availableExt) if (installedExt.hasUpdate != hasUpdate) { - mutInstalledExtensions[index] = installedExt.copy(hasUpdate = hasUpdate) + mutInstalledExtensions[index] = installedExt.copy( + hasUpdate = hasUpdate, + isFromExternalRepo = availableExt.isFromExternalRepo, + repoUrl = availableExt.repoUrl, + ) + changed = true + } else if (availableExt.isFromExternalRepo) { + mutInstalledExtensions[index] = installedExt.copy( + isFromExternalRepo = true, + repoUrl = availableExt.repoUrl, + ) changed = true } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt index e885652b3..d0d493a97 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionApi.kt @@ -133,7 +133,7 @@ internal class ExtensionApi { apkName = it.apk, iconUrl = "$repoUrl/icon/${it.pkg}.png", repoUrl = repoUrl, - isRepoSource = isRepoSource, + isFromExternalRepo = isRepoSource, ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt index 4dbf09a26..4982ef6e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/model/Extension.kt @@ -30,7 +30,7 @@ sealed class Extension { val isUnofficial: Boolean = false, val isShared: Boolean, val repoUrl: String? = null, - val isRepoSource: Boolean = false, + val isFromExternalRepo: Boolean = false, ) : Extension() data class Available( @@ -45,7 +45,7 @@ sealed class Extension { val apkName: String, val iconUrl: String, val repoUrl: String, - val isRepoSource: Boolean, + val isFromExternalRepo: Boolean, ) : Extension() { data class Source( diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt index c8846a34c..c792333e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/delegate/ThemingDelegate.kt @@ -26,6 +26,9 @@ interface ThemingDelegate { AppTheme.MIDNIGHT_DUSK -> { resIds += R.style.Theme_Tachiyomi_MidnightDusk } + AppTheme.NORD -> { + resIds += R.style.Theme_Tachiyomi_Nord + } AppTheme.STRAWBERRY_DAIQUIRI -> { resIds += R.style.Theme_Tachiyomi_StrawberryDaiquiri } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt index 105f1e2b3..a1b6c3b1b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt @@ -157,6 +157,7 @@ class LibraryScreenModel( prefs.filterStarted, prefs.filterBookmarked, prefs.filterCompleted, + prefs.filterIntervalCustom, ) + trackFilter.values ).any { it != TriState.DISABLED } } @@ -178,12 +179,13 @@ class LibraryScreenModel( ): LibraryMap { val prefs = getLibraryItemPreferencesFlow().first() val downloadedOnly = prefs.globalFilterDownloaded - val filterDownloaded = - if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded + val skipOutsideReleasePeriod = prefs.skipOutsideReleasePeriod + val filterDownloaded = if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded val filterUnread = prefs.filterUnread val filterStarted = prefs.filterStarted val filterBookmarked = prefs.filterBookmarked val filterCompleted = prefs.filterCompleted + val filterIntervalCustom = prefs.filterIntervalCustom val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty() @@ -215,6 +217,14 @@ class LibraryScreenModel( applyFilter(filterCompleted) { it.libraryManga.manga.status.toInt() == SManga.COMPLETED } } + val filterFnIntervalCustom: (LibraryItem) -> Boolean = { + if (skipOutsideReleasePeriod) { + applyFilter(filterIntervalCustom) { it.libraryManga.manga.fetchInterval < 0 } + } else { + true + } + } + val filterFnTracking: (LibraryItem) -> Boolean = tracking@{ item -> if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true @@ -225,7 +235,7 @@ class LibraryScreenModel( val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks } val isIncluded = includedTracks.isEmpty() || mangaTracks.fastAny { it in includedTracks } - return@tracking !isExcluded && isIncluded + !isExcluded && isIncluded } val filterFn: (LibraryItem) -> Boolean = { @@ -234,6 +244,7 @@ class LibraryScreenModel( filterFnStarted(it) && filterFnBookmarked(it) && filterFnCompleted(it) && + filterFnIntervalCustom(it) && filterFnTracking(it) } @@ -320,6 +331,7 @@ class LibraryScreenModel( libraryPreferences.downloadBadge().changes(), libraryPreferences.localBadge().changes(), libraryPreferences.languageBadge().changes(), + libraryPreferences.autoUpdateMangaRestrictions().changes(), preferences.downloadedOnly().changes(), libraryPreferences.filterDownloaded().changes(), @@ -327,20 +339,22 @@ class LibraryScreenModel( libraryPreferences.filterStarted().changes(), libraryPreferences.filterBookmarked().changes(), libraryPreferences.filterCompleted().changes(), - transform = { - ItemPreferences( - downloadBadge = it[0] as Boolean, - localBadge = it[1] as Boolean, - languageBadge = it[2] as Boolean, - globalFilterDownloaded = it[3] as Boolean, - filterDownloaded = it[4] as TriState, - filterUnread = it[5] as TriState, - filterStarted = it[6] as TriState, - filterBookmarked = it[7] as TriState, - filterCompleted = it[8] as TriState, - ) - }, - ) + libraryPreferences.filterIntervalCustom().changes(), + ) { + ItemPreferences( + downloadBadge = it[0] as Boolean, + localBadge = it[1] as Boolean, + languageBadge = it[2] as Boolean, + skipOutsideReleasePeriod = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in (it[3] as Set<*>), + globalFilterDownloaded = it[4] as Boolean, + filterDownloaded = it[5] as TriState, + filterUnread = it[6] as TriState, + filterStarted = it[7] as TriState, + filterBookmarked = it[8] as TriState, + filterCompleted = it[9] as TriState, + filterIntervalCustom = it[10] as TriState, + ) + } } /** @@ -699,6 +713,7 @@ class LibraryScreenModel( val downloadBadge: Boolean, val localBadge: Boolean, val languageBadge: Boolean, + val skipOutsideReleasePeriod: Boolean, val globalFilterDownloaded: Boolean, val filterDownloaded: TriState, @@ -706,6 +721,7 @@ class LibraryScreenModel( val filterStarted: TriState, val filterBookmarked: TriState, val filterCompleted: TriState, + val filterIntervalCustom: TriState, ) @Immutable diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index e7446e600..ae61519ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -378,12 +378,15 @@ class MangaScreenModel( fun setFetchInterval(manga: Manga, interval: Int) { screenModelScope.launchIO { - updateManga.awaitUpdateFetchInterval( - // Custom intervals are negative - manga.copy(fetchInterval = -interval), - ) - val updatedManga = mangaRepository.getMangaById(manga.id) - updateSuccessState { it.copy(manga = updatedManga) } + if ( + updateManga.awaitUpdateFetchInterval( + // Custom intervals are negative + manga.copy(fetchInterval = -interval), + ) + ) { + val updatedManga = mangaRepository.getMangaById(manga.id) + updateSuccessState { it.copy(manga = updatedManga) } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 789d26e3e..b7f4c9c14 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -462,7 +462,8 @@ class ReaderViewModel @JvmOverloads constructor( manga.title, manga.source, ) - if (isNextChapterDownloaded) return@launchIO + if (!isNextChapterDownloaded) return@launchIO + val chaptersToDownload = getNextChapters.await(manga.id, nextChapter.id!!).run { if (readerPreferences.skipDupe().get()) { removeDuplicates(nextChapter.toDomainChapter()!!) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt index b6abc4834..d86619cdf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonRecyclerView.kt @@ -214,6 +214,9 @@ class WebtoonRecyclerView @JvmOverloads constructor( if (!isZooming && doubleTapZoom) { if (scaleX != DEFAULT_RATE) { zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f) + layoutParams.height = originalHeight + halfHeight = layoutParams.height / 2 + requestLayout() } else { val toScale = 2f val toX = (halfWidth - ev.x) * (toScale - 1) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index b61c34eb9..f6597c58b 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -333,6 +333,37 @@ @color/tidalwave_primaryInverse + + +