Merge branch 'sync-part-final' into feat/add-sync-triggers-experimental

This commit is contained in:
KaiserBh 2024-01-09 02:08:53 +11:00
commit 9859a3d129
No known key found for this signature in database
GPG Key ID: 14D73B142042BBA9
28 changed files with 383 additions and 130 deletions

View File

@ -81,9 +81,9 @@ class UpdateManga(
dateTime: ZonedDateTime = ZonedDateTime.now(), dateTime: ZonedDateTime = ZonedDateTime.now(),
window: Pair<Long, Long> = fetchInterval.getWindow(dateTime), window: Pair<Long, Long> = fetchInterval.getWindow(dateTime),
): Boolean { ): Boolean {
return fetchInterval.toMangaUpdateOrNull(manga, dateTime, window) return mangaRepository.update(
?.let { mangaRepository.update(it) } fetchInterval.toMangaUpdate(manga, dateTime, window),
?: false )
} }
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean { suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {

View File

@ -11,7 +11,7 @@ class CreateSourceRepo(private val preferences: SourcePreferences) {
return Result.InvalidUrl return Result.InvalidUrl
} }
preferences.extensionRepos() += name.substringBeforeLast("/index.min.json") preferences.extensionRepos() += name.removeSuffix("/index.min.json")
return Result.Success return Result.Success
} }

View File

@ -17,11 +17,15 @@ class TrustExtension(
fun trust(pkgName: String, versionCode: Long, signatureHash: String) { fun trust(pkgName: String, versionCode: Long, signatureHash: String) {
preferences.trustedExtensions().getAndSet { exts -> preferences.trustedExtensions().getAndSet { exts ->
// Remove previously trusted versions // Remove previously trusted versions
val removed = exts.filter { it.startsWith("$pkgName:") }.toMutableSet() val removed = exts.filterNot { it.startsWith("$pkgName:") }.toMutableSet()
removed.also { removed.also {
it += "$pkgName:$versionCode:$signatureHash" it += "$pkgName:$versionCode:$signatureHash"
} }
} }
} }
fun revokeAll() {
preferences.trustedExtensions().delete()
}
} }

View File

@ -40,7 +40,9 @@ fun DbTrack.toDomainTrack(idRequired: Boolean = true): Track? {
lastChapterRead = last_chapter_read.toDouble(), lastChapterRead = last_chapter_read.toDouble(),
totalChapters = total_chapters.toLong(), totalChapters = total_chapters.toLong(),
status = status.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, remoteUrl = tracking_url,
startDate = started_reading_date, startDate = started_reading_date,
finishDate = finished_reading_date, finishDate = finished_reading_date,

View File

@ -9,6 +9,7 @@ enum class AppTheme(val titleRes: StringResource?) {
GREEN_APPLE(MR.strings.theme_greenapple), GREEN_APPLE(MR.strings.theme_greenapple),
LAVENDER(MR.strings.theme_lavender), LAVENDER(MR.strings.theme_lavender),
MIDNIGHT_DUSK(MR.strings.theme_midnightdusk), MIDNIGHT_DUSK(MR.strings.theme_midnightdusk),
NORD(MR.strings.theme_nord),
STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri), STRAWBERRY_DAIQUIRI(MR.strings.theme_strawberrydaiquiri),
TAKO(MR.strings.theme_tako), TAKO(MR.strings.theme_tako),
TEALTURQUOISE(MR.strings.theme_tealturquoise), TEALTURQUOISE(MR.strings.theme_tealturquoise),

View File

@ -53,6 +53,7 @@ import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel import eu.kanade.tachiyomi.ui.browse.extension.details.ExtensionDetailsScreenModel
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
@ -138,7 +139,7 @@ fun ExtensionDetailsScreen(
private fun ExtensionDetails( private fun ExtensionDetails(
contentPadding: PaddingValues, contentPadding: PaddingValues,
extension: Extension.Installed, extension: Extension.Installed,
sources: List<ExtensionSourceItem>, sources: ImmutableList<ExtensionSourceItem>,
onClickSourcePreferences: (sourceId: Long) -> Unit, onClickSourcePreferences: (sourceId: Long) -> Unit,
onClickUninstall: () -> Unit, onClickUninstall: () -> Unit,
onClickSource: (sourceId: Long) -> Unit, onClickSource: (sourceId: Long) -> Unit,
@ -150,18 +151,24 @@ private fun ExtensionDetails(
contentPadding = contentPadding, contentPadding = contentPadding,
) { ) {
when { when {
extension.isRepoSource -> extension.isFromExternalRepo ->
item { item {
val uriHandler = LocalUriHandler.current 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( WarningBanner(
MR.strings.repo_extension_message, MR.strings.repo_extension_message,
modifier = Modifier.clickable { modifier = Modifier.clickable {
extension.repoUrl ?: return@clickable url ?: return@clickable
uriHandler.openUri( uriHandler.openUri(url)
extension.repoUrl
.replace("https://raw.githubusercontent.com", "https://github.com")
.removeSuffix("/repo/"),
)
}, },
) )
} }

View File

@ -74,6 +74,8 @@ private fun ColumnScope.FilterPage(
) { ) {
val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState() val filterDownloaded by screenModel.libraryPreferences.filterDownloaded().collectAsState()
val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState() val downloadedOnly by screenModel.preferences.downloadedOnly().collectAsState()
val autoUpdateMangaRestrictions by screenModel.libraryPreferences.autoUpdateMangaRestrictions().collectAsState()
TriStateItem( TriStateItem(
label = stringResource(MR.strings.label_downloaded), label = stringResource(MR.strings.label_downloaded),
state = if (downloadedOnly) { state = if (downloadedOnly) {
@ -108,6 +110,14 @@ private fun ColumnScope.FilterPage(
state = filterCompleted, state = filterCompleted,
onClick = { screenModel.toggleFilter(LibraryPreferences::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 } val trackers = remember { screenModel.trackers }
when (trackers.size) { when (trackers.size) {

View File

@ -30,6 +30,7 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import kotlin.math.absoluteValue
@Composable @Composable
fun DeleteChaptersDialog( fun DeleteChaptersDialog(
@ -85,7 +86,7 @@ fun SetIntervalDialog(
title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) }, title = { Text(stringResource(MR.strings.pref_library_update_smart_update)) },
text = { text = {
Column { Column {
if (nextUpdateDays != null && nextUpdateDays >= 0) { if (nextUpdateDays != null && nextUpdateDays >= 0 && interval >= 0) {
Text( Text(
stringResource( stringResource(
MR.strings.manga_interval_expected_update, MR.strings.manga_interval_expected_update,
@ -96,8 +97,8 @@ fun SetIntervalDialog(
), ),
pluralStringResource( pluralStringResource(
MR.plurals.day, MR.plurals.day,
count = interval, count = interval.absoluteValue,
interval, interval.absoluteValue,
), ),
), ),
) )
@ -105,7 +106,6 @@ fun SetIntervalDialog(
Spacer(Modifier.height(MaterialTheme.padding.small)) Spacer(Modifier.height(MaterialTheme.padding.small))
} }
// TODO: selecting "1" then doesn't allow for future changes unless defaulting first?
if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) { if (onValueChanged != null && (isDevFlavor || isPreviewBuildType)) {
Text(stringResource(MR.strings.manga_interval_custom_amount)) Text(stringResource(MR.strings.manga_interval_custom_amount))

View File

@ -201,14 +201,14 @@ fun MangaActionRow(
onLongClick = onEditCategory, onLongClick = onEditCategory,
) )
MangaActionButton( MangaActionButton(
title = if (nextUpdateDays != null) { title = when (nextUpdateDays) {
pluralStringResource( null -> stringResource(MR.strings.not_applicable)
0 -> stringResource(MR.strings.manga_interval_expected_update_soon)
else -> pluralStringResource(
MR.plurals.day, MR.plurals.day,
count = nextUpdateDays, count = nextUpdateDays,
nextUpdateDays, nextUpdateDays,
) )
} else {
stringResource(MR.strings.not_applicable)
}, },
icon = Icons.Default.HourglassEmpty, icon = Icons.Default.HourglassEmpty,
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor, color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,

View File

@ -24,6 +24,7 @@ import androidx.core.net.toUri
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.domain.base.BasePreferences 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.Preference
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
@ -340,6 +341,7 @@ object SettingsAdvancedScreen : SearchableSettings {
val uriHandler = LocalUriHandler.current val uriHandler = LocalUriHandler.current
val extensionInstallerPref = basePreferences.extensionInstaller() val extensionInstallerPref = basePreferences.extensionInstaller()
var shizukuMissing by rememberSaveable { mutableStateOf(false) } var shizukuMissing by rememberSaveable { mutableStateOf(false) }
val trustExtension = remember { Injekt.get<TrustExtension>() }
if (shizukuMissing) { if (shizukuMissing) {
val dismiss = { shizukuMissing = false } 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)
},
),
), ),
) )
} }

View File

@ -12,6 +12,7 @@ import eu.kanade.presentation.theme.colorscheme.GreenAppleColorScheme
import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme import eu.kanade.presentation.theme.colorscheme.LavenderColorScheme
import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme import eu.kanade.presentation.theme.colorscheme.MidnightDuskColorScheme
import eu.kanade.presentation.theme.colorscheme.MonetColorScheme 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.StrawberryColorScheme
import eu.kanade.presentation.theme.colorscheme.TachiyomiColorScheme import eu.kanade.presentation.theme.colorscheme.TachiyomiColorScheme
import eu.kanade.presentation.theme.colorscheme.TakoColorScheme import eu.kanade.presentation.theme.colorscheme.TakoColorScheme
@ -47,6 +48,7 @@ private fun getThemeColorScheme(
AppTheme.GREEN_APPLE -> GreenAppleColorScheme AppTheme.GREEN_APPLE -> GreenAppleColorScheme
AppTheme.LAVENDER -> LavenderColorScheme AppTheme.LAVENDER -> LavenderColorScheme
AppTheme.MIDNIGHT_DUSK -> MidnightDuskColorScheme AppTheme.MIDNIGHT_DUSK -> MidnightDuskColorScheme
AppTheme.NORD -> NordColorScheme
AppTheme.STRAWBERRY_DAIQUIRI -> StrawberryColorScheme AppTheme.STRAWBERRY_DAIQUIRI -> StrawberryColorScheme
AppTheme.TAKO -> TakoColorScheme AppTheme.TAKO -> TakoColorScheme
AppTheme.TEALTURQUOISE -> TealTurqoiseColorScheme AppTheme.TEALTURQUOISE -> TealTurqoiseColorScheme

View File

@ -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),
)
}

View File

@ -7,6 +7,7 @@ import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.width
import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions 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.material.icons.filled.Close
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -40,7 +41,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip 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.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor 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.platform.LocalFocusManager
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.capitalize import androidx.compose.ui.text.capitalize
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue 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.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.presentation.manga.components.MangaCover import eu.kanade.presentation.manga.components.MangaCover
import eu.kanade.presentation.theme.TachiyomiTheme import eu.kanade.presentation.theme.TachiyomiTheme
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.util.system.openInBrowser
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.ScrollbarLazyColumn import tachiyomi.presentation.core.components.ScrollbarLazyColumn
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@ -188,13 +198,7 @@ fun TrackerSearch(
key = { it.hashCode() }, key = { it.hashCode() },
) { ) {
SearchResultItem( SearchResultItem(
title = it.title, trackSearch = it,
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(),
selected = it == selected, selected = it == selected,
onClick = { onSelectedChange(it) }, onClick = { onSelectedChange(it) },
) )
@ -214,18 +218,18 @@ fun TrackerSearch(
@Composable @Composable
private fun SearchResultItem( private fun SearchResultItem(
title: String, trackSearch: TrackSearch,
coverUrl: String,
type: String,
startDate: String,
status: String,
score: Float,
description: String,
selected: Boolean, selected: Boolean,
onClick: () -> Unit, 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 shape = RoundedCornerShape(16.dp)
val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent val borderColor = if (selected) MaterialTheme.colorScheme.outline else Color.Transparent
var dropDownMenuExpanded by remember { mutableStateOf(false) }
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -237,7 +241,10 @@ private fun SearchResultItem(
color = borderColor, color = borderColor,
shape = shape, shape = shape,
) )
.selectable(selected = selected, onClick = onClick) .combinedClickable(
onLongClick = { dropDownMenuExpanded = true },
onClick = onClick,
)
.padding(12.dp), .padding(12.dp),
) { ) {
if (selected) { if (selected) {
@ -251,28 +258,41 @@ private fun SearchResultItem(
Column { Column {
Row { Row {
MangaCover.Book( MangaCover.Book(
data = coverUrl, data = trackSearch.cover_url,
modifier = Modifier.height(96.dp), modifier = Modifier.height(96.dp),
) )
Spacer(modifier = Modifier.width(12.dp)) Spacer(modifier = Modifier.width(12.dp))
Column { Column {
Text( Text(
text = title, text = trackSearch.title,
modifier = Modifier.padding(end = 28.dp), modifier = Modifier.padding(end = 28.dp),
maxLines = 2, maxLines = 2,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.titleMedium, 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()) { if (type.isNotBlank()) {
SearchResultItemDetails( SearchResultItemDetails(
title = stringResource(MR.strings.track_type), title = stringResource(MR.strings.track_type),
text = type, text = type,
) )
} }
if (startDate.isNotBlank()) { if (trackSearch.start_date.isNotBlank()) {
SearchResultItemDetails( SearchResultItemDetails(
title = stringResource(MR.strings.label_started), title = stringResource(MR.strings.label_started),
text = startDate, text = trackSearch.start_date,
) )
} }
if (status.isNotBlank()) { if (status.isNotBlank()) {
@ -281,10 +301,10 @@ private fun SearchResultItem(
text = status, text = status,
) )
} }
if (score != -1f) { if (trackSearch.score != -1f) {
SearchResultItemDetails( SearchResultItemDetails(
title = stringResource(MR.strings.score), 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 @Composable
private fun SearchResultItemDetails( private fun SearchResultItemDetails(
title: String, title: String,

View File

@ -185,7 +185,17 @@ class ExtensionManager(
val hasUpdate = installedExt.updateExists(availableExt) val hasUpdate = installedExt.updateExists(availableExt)
if (installedExt.hasUpdate != hasUpdate) { 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 changed = true
} }
} }

View File

@ -133,7 +133,7 @@ internal class ExtensionApi {
apkName = it.apk, apkName = it.apk,
iconUrl = "$repoUrl/icon/${it.pkg}.png", iconUrl = "$repoUrl/icon/${it.pkg}.png",
repoUrl = repoUrl, repoUrl = repoUrl,
isRepoSource = isRepoSource, isFromExternalRepo = isRepoSource,
) )
} }
} }

View File

@ -30,7 +30,7 @@ sealed class Extension {
val isUnofficial: Boolean = false, val isUnofficial: Boolean = false,
val isShared: Boolean, val isShared: Boolean,
val repoUrl: String? = null, val repoUrl: String? = null,
val isRepoSource: Boolean = false, val isFromExternalRepo: Boolean = false,
) : Extension() ) : Extension()
data class Available( data class Available(
@ -45,7 +45,7 @@ sealed class Extension {
val apkName: String, val apkName: String,
val iconUrl: String, val iconUrl: String,
val repoUrl: String, val repoUrl: String,
val isRepoSource: Boolean, val isFromExternalRepo: Boolean,
) : Extension() { ) : Extension() {
data class Source( data class Source(

View File

@ -26,6 +26,9 @@ interface ThemingDelegate {
AppTheme.MIDNIGHT_DUSK -> { AppTheme.MIDNIGHT_DUSK -> {
resIds += R.style.Theme_Tachiyomi_MidnightDusk resIds += R.style.Theme_Tachiyomi_MidnightDusk
} }
AppTheme.NORD -> {
resIds += R.style.Theme_Tachiyomi_Nord
}
AppTheme.STRAWBERRY_DAIQUIRI -> { AppTheme.STRAWBERRY_DAIQUIRI -> {
resIds += R.style.Theme_Tachiyomi_StrawberryDaiquiri resIds += R.style.Theme_Tachiyomi_StrawberryDaiquiri
} }

View File

@ -157,6 +157,7 @@ class LibraryScreenModel(
prefs.filterStarted, prefs.filterStarted,
prefs.filterBookmarked, prefs.filterBookmarked,
prefs.filterCompleted, prefs.filterCompleted,
prefs.filterIntervalCustom,
) + trackFilter.values ) + trackFilter.values
).any { it != TriState.DISABLED } ).any { it != TriState.DISABLED }
} }
@ -178,12 +179,13 @@ class LibraryScreenModel(
): LibraryMap { ): LibraryMap {
val prefs = getLibraryItemPreferencesFlow().first() val prefs = getLibraryItemPreferencesFlow().first()
val downloadedOnly = prefs.globalFilterDownloaded val downloadedOnly = prefs.globalFilterDownloaded
val filterDownloaded = val skipOutsideReleasePeriod = prefs.skipOutsideReleasePeriod
if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded val filterDownloaded = if (downloadedOnly) TriState.ENABLED_IS else prefs.filterDownloaded
val filterUnread = prefs.filterUnread val filterUnread = prefs.filterUnread
val filterStarted = prefs.filterStarted val filterStarted = prefs.filterStarted
val filterBookmarked = prefs.filterBookmarked val filterBookmarked = prefs.filterBookmarked
val filterCompleted = prefs.filterCompleted val filterCompleted = prefs.filterCompleted
val filterIntervalCustom = prefs.filterIntervalCustom
val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty() val isNotLoggedInAnyTrack = loggedInTrackers.isEmpty()
@ -215,6 +217,14 @@ class LibraryScreenModel(
applyFilter(filterCompleted) { it.libraryManga.manga.status.toInt() == SManga.COMPLETED } 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 -> val filterFnTracking: (LibraryItem) -> Boolean = tracking@{ item ->
if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true if (isNotLoggedInAnyTrack || trackFiltersIsIgnored) return@tracking true
@ -225,7 +235,7 @@ class LibraryScreenModel(
val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks } val isExcluded = excludedTracks.isNotEmpty() && mangaTracks.fastAny { it in excludedTracks }
val isIncluded = includedTracks.isEmpty() || mangaTracks.fastAny { it in includedTracks } val isIncluded = includedTracks.isEmpty() || mangaTracks.fastAny { it in includedTracks }
return@tracking !isExcluded && isIncluded !isExcluded && isIncluded
} }
val filterFn: (LibraryItem) -> Boolean = { val filterFn: (LibraryItem) -> Boolean = {
@ -234,6 +244,7 @@ class LibraryScreenModel(
filterFnStarted(it) && filterFnStarted(it) &&
filterFnBookmarked(it) && filterFnBookmarked(it) &&
filterFnCompleted(it) && filterFnCompleted(it) &&
filterFnIntervalCustom(it) &&
filterFnTracking(it) filterFnTracking(it)
} }
@ -320,6 +331,7 @@ class LibraryScreenModel(
libraryPreferences.downloadBadge().changes(), libraryPreferences.downloadBadge().changes(),
libraryPreferences.localBadge().changes(), libraryPreferences.localBadge().changes(),
libraryPreferences.languageBadge().changes(), libraryPreferences.languageBadge().changes(),
libraryPreferences.autoUpdateMangaRestrictions().changes(),
preferences.downloadedOnly().changes(), preferences.downloadedOnly().changes(),
libraryPreferences.filterDownloaded().changes(), libraryPreferences.filterDownloaded().changes(),
@ -327,21 +339,23 @@ class LibraryScreenModel(
libraryPreferences.filterStarted().changes(), libraryPreferences.filterStarted().changes(),
libraryPreferences.filterBookmarked().changes(), libraryPreferences.filterBookmarked().changes(),
libraryPreferences.filterCompleted().changes(), libraryPreferences.filterCompleted().changes(),
transform = { libraryPreferences.filterIntervalCustom().changes(),
) {
ItemPreferences( ItemPreferences(
downloadBadge = it[0] as Boolean, downloadBadge = it[0] as Boolean,
localBadge = it[1] as Boolean, localBadge = it[1] as Boolean,
languageBadge = it[2] as Boolean, languageBadge = it[2] as Boolean,
globalFilterDownloaded = it[3] as Boolean, skipOutsideReleasePeriod = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in (it[3] as Set<*>),
filterDownloaded = it[4] as TriState, globalFilterDownloaded = it[4] as Boolean,
filterUnread = it[5] as TriState, filterDownloaded = it[5] as TriState,
filterStarted = it[6] as TriState, filterUnread = it[6] as TriState,
filterBookmarked = it[7] as TriState, filterStarted = it[7] as TriState,
filterCompleted = it[8] as TriState, filterBookmarked = it[8] as TriState,
) filterCompleted = it[9] as TriState,
}, filterIntervalCustom = it[10] as TriState,
) )
} }
}
/** /**
* Get the categories and all its manga from the database. * Get the categories and all its manga from the database.
@ -699,6 +713,7 @@ class LibraryScreenModel(
val downloadBadge: Boolean, val downloadBadge: Boolean,
val localBadge: Boolean, val localBadge: Boolean,
val languageBadge: Boolean, val languageBadge: Boolean,
val skipOutsideReleasePeriod: Boolean,
val globalFilterDownloaded: Boolean, val globalFilterDownloaded: Boolean,
val filterDownloaded: TriState, val filterDownloaded: TriState,
@ -706,6 +721,7 @@ class LibraryScreenModel(
val filterStarted: TriState, val filterStarted: TriState,
val filterBookmarked: TriState, val filterBookmarked: TriState,
val filterCompleted: TriState, val filterCompleted: TriState,
val filterIntervalCustom: TriState,
) )
@Immutable @Immutable

View File

@ -378,14 +378,17 @@ class MangaScreenModel(
fun setFetchInterval(manga: Manga, interval: Int) { fun setFetchInterval(manga: Manga, interval: Int) {
screenModelScope.launchIO { screenModelScope.launchIO {
if (
updateManga.awaitUpdateFetchInterval( updateManga.awaitUpdateFetchInterval(
// Custom intervals are negative // Custom intervals are negative
manga.copy(fetchInterval = -interval), manga.copy(fetchInterval = -interval),
) )
) {
val updatedManga = mangaRepository.getMangaById(manga.id) val updatedManga = mangaRepository.getMangaById(manga.id)
updateSuccessState { it.copy(manga = updatedManga) } updateSuccessState { it.copy(manga = updatedManga) }
} }
} }
}
/** /**
* Returns true if the manga has any downloads. * Returns true if the manga has any downloads.

View File

@ -465,7 +465,8 @@ class ReaderViewModel @JvmOverloads constructor(
manga.title, manga.title,
manga.source, manga.source,
) )
if (isNextChapterDownloaded) return@launchIO if (!isNextChapterDownloaded) return@launchIO
val chaptersToDownload = getNextChapters.await(manga.id, nextChapter.id!!).run { val chaptersToDownload = getNextChapters.await(manga.id, nextChapter.id!!).run {
if (readerPreferences.skipDupe().get()) { if (readerPreferences.skipDupe().get()) {
removeDuplicates(nextChapter.toDomainChapter()!!) removeDuplicates(nextChapter.toDomainChapter()!!)

View File

@ -214,6 +214,9 @@ class WebtoonRecyclerView @JvmOverloads constructor(
if (!isZooming && doubleTapZoom) { if (!isZooming && doubleTapZoom) {
if (scaleX != DEFAULT_RATE) { if (scaleX != DEFAULT_RATE) {
zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f) zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f)
layoutParams.height = originalHeight
halfHeight = layoutParams.height / 2
requestLayout()
} else { } else {
val toScale = 2f val toScale = 2f
val toX = (halfWidth - ev.x) * (toScale - 1) val toX = (halfWidth - ev.x) * (toScale - 1)

View File

@ -333,6 +333,37 @@
<item name="colorPrimaryInverse">@color/tidalwave_primaryInverse</item> <item name="colorPrimaryInverse">@color/tidalwave_primaryInverse</item>
</style> </style>
<!--== Nord Theme ==-->
<style name="Theme.Tachiyomi.Nord">
<!-- Theme Colors -->
<item name="colorPrimary">@color/nord_primary</item>
<item name="colorOnPrimary">@color/nord_onPrimary</item>
<item name="colorPrimaryContainer">@color/nord_primaryContainer</item>
<item name="colorOnPrimaryContainer">@color/nord_onPrimaryContainer</item>
<item name="colorSecondary">@color/nord_secondary</item>
<item name="colorOnSecondary">@color/nord_onSecondary</item>
<item name="colorSecondaryContainer">@color/nord_secondaryContainer</item>
<item name="colorOnSecondaryContainer">@color/nord_onSecondaryContainer</item>
<item name="colorTertiary">@color/nord_tertiary</item>
<item name="colorOnTertiary">@color/nord_onTertiary</item>
<item name="colorTertiaryContainer">@color/nord_tertiaryContainer</item>
<item name="colorOnTertiaryContainer">@color/nord_onTertiaryContainer</item>
<item name="android:colorBackground">@color/nord_background</item>
<item name="colorOnBackground">@color/nord_onBackground</item>
<item name="colorSurface">@color/nord_surface</item>
<item name="colorOnSurface">@color/nord_onSurface</item>
<item name="colorSurfaceVariant">@color/nord_surfaceVariant</item>
<item name="colorOnSurfaceVariant">@color/nord_onSurfaceVariant</item>
<item name="colorOutline">@color/nord_outline</item>
<item name="colorOnSurfaceInverse">@color/nord_inverseOnSurface</item>
<item name="colorSurfaceInverse">@color/nord_inverseSurface</item>
<item name="colorPrimaryInverse">@color/nord_primaryInverse</item>
<item name="colorOnError">@color/nord_onError</item>
<item name="colorErrorContainer">@color/nord_errorContainer</item>
<item name="colorOnErrorContainer">@color/nord_onErrorContainer</item>
<item name="elevationOverlayColor">@color/nord_elevationOverlay</item>
</style>
<!--== AMOLED Mode Overlay ==--> <!--== AMOLED Mode Overlay ==-->
<style name="ThemeOverlay.Tachiyomi.Amoled" parent="" /> <style name="ThemeOverlay.Tachiyomi.Amoled" parent="" />

View File

@ -85,26 +85,6 @@ class LibraryPreferences(
TriState.DISABLED, TriState.DISABLED,
) )
fun filterIntervalLong() = preferenceStore.getEnum(
"pref_filter_library_interval_long",
TriState.DISABLED,
)
fun filterIntervalLate() = preferenceStore.getEnum(
"pref_filter_library_interval_late",
TriState.DISABLED,
)
fun filterIntervalDropped() = preferenceStore.getEnum(
"pref_filter_library_interval_dropped",
TriState.DISABLED,
)
fun filterIntervalPassed() = preferenceStore.getEnum(
"pref_filter_library_interval_passed",
TriState.DISABLED,
)
fun filterTracking(id: Int) = preferenceStore.getEnum( fun filterTracking(id: Int) = preferenceStore.getEnum(
"pref_filter_library_tracked_${id}_v2", "pref_filter_library_tracked_${id}_v2",
TriState.DISABLED, TriState.DISABLED,

View File

@ -14,11 +14,11 @@ class FetchInterval(
private val getChaptersByMangaId: GetChaptersByMangaId, private val getChaptersByMangaId: GetChaptersByMangaId,
) { ) {
suspend fun toMangaUpdateOrNull( suspend fun toMangaUpdate(
manga: Manga, manga: Manga,
dateTime: ZonedDateTime, dateTime: ZonedDateTime,
window: Pair<Long, Long>, window: Pair<Long, Long>,
): MangaUpdate? { ): MangaUpdate {
val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval( val interval = manga.fetchInterval.takeIf { it < 0 } ?: calculateInterval(
chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true), chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true),
zone = dateTime.zone, zone = dateTime.zone,
@ -30,11 +30,7 @@ class FetchInterval(
} }
val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow) val nextUpdate = calculateNextUpdate(manga, interval, dateTime, currentWindow)
return if (manga.nextUpdate == nextUpdate && manga.fetchInterval == interval) { return MangaUpdate(id = manga.id, nextUpdate = nextUpdate, fetchInterval = interval)
null
} else {
MangaUpdate(id = manga.id, nextUpdate = nextUpdate, fetchInterval = interval)
}
} }
fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> { fun getWindow(dateTime: ZonedDateTime): Pair<Long, Long> {
@ -96,10 +92,10 @@ class FetchInterval(
dateTime: ZonedDateTime, dateTime: ZonedDateTime,
window: Pair<Long, Long>, window: Pair<Long, Long>,
): Long { ): Long {
return if ( if (manga.nextUpdate in window.first.rangeTo(window.second + 1)) {
manga.nextUpdate !in window.first.rangeTo(window.second + 1) || return manga.nextUpdate
manga.fetchInterval == 0 }
) {
val latestDate = ZonedDateTime.ofInstant( val latestDate = ZonedDateTime.ofInstant(
if (manga.lastUpdate > 0) Instant.ofEpochMilli(manga.lastUpdate) else Instant.now(), if (manga.lastUpdate > 0) Instant.ofEpochMilli(manga.lastUpdate) else Instant.now(),
dateTime.zone, dateTime.zone,
@ -109,21 +105,18 @@ class FetchInterval(
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt() val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt()
val cycle = timeSinceLatest.floorDiv( val cycle = timeSinceLatest.floorDiv(
interval.absoluteValue.takeIf { interval < 0 } interval.absoluteValue.takeIf { interval < 0 }
?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10), ?: increaseInterval(interval, timeSinceLatest, increaseWhenOver = 10),
) )
latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(dateTime.offset) * 1000 return latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(dateTime.offset) * 1000
} else {
manga.nextUpdate
}
} }
private fun doubleInterval(delta: Int, timeSinceLatest: Int, doubleWhenOver: Int): Int { private fun increaseInterval(delta: Int, timeSinceLatest: Int, increaseWhenOver: Int): Int {
if (delta >= MAX_INTERVAL) return MAX_INTERVAL if (delta >= MAX_INTERVAL) return MAX_INTERVAL
// double delta again if missed more than 9 check in new delta // double delta again if missed more than 9 check in new delta
val cycle = timeSinceLatest.floorDiv(delta) + 1 val cycle = timeSinceLatest.floorDiv(delta) + 1
return if (cycle > doubleWhenOver) { return if (cycle > increaseWhenOver) {
doubleInterval(delta * 2, timeSinceLatest, doubleWhenOver) increaseInterval(delta * 2, timeSinceLatest, increaseWhenOver)
} else { } else {
delta delta
} }

View File

@ -89,7 +89,7 @@ sqldelight-gradle = { module = "app.cash.sqldelight:gradle-plugin", version.ref
junit = "org.junit.jupiter:junit-jupiter:5.10.1" junit = "org.junit.jupiter:junit-jupiter:5.10.1"
kotest-assertions = "io.kotest:kotest-assertions-core:5.8.0" kotest-assertions = "io.kotest:kotest-assertions-core:5.8.0"
mockk = "io.mockk:mockk:1.13.8" mockk = "io.mockk:mockk:1.13.9"
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" } voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" } voyager-screenmodel = { module = "cafe.adriel.voyager:voyager-screenmodel", version.ref = "voyager" }

View File

@ -55,11 +55,7 @@
<string name="action_filter_bookmarked">Bookmarked</string> <string name="action_filter_bookmarked">Bookmarked</string>
<string name="action_filter_tracked">Tracked</string> <string name="action_filter_tracked">Tracked</string>
<string name="action_filter_unread">Unread</string> <string name="action_filter_unread">Unread</string>
<string name="action_filter_interval_custom">Customized fetch interval</string> <string name="action_filter_interval_custom">Customized update frequency</string>
<string name="action_filter_interval_long">Fetch monthly (28 days)</string>
<string name="action_filter_interval_late">Late 10+ check</string>
<string name="action_filter_interval_dropped">Dropped? Late 20+ and 2 months</string>
<string name="action_filter_interval_passed">Passed check period</string>
<!-- reserved for #4048 --> <!-- reserved for #4048 -->
<string name="action_filter_empty">Remove filter</string> <string name="action_filter_empty">Remove filter</string>
<string name="action_sort_alpha">Alphabetically</string> <string name="action_sort_alpha">Alphabetically</string>
@ -227,6 +223,7 @@
<string name="theme_greenapple">Green Apple</string> <string name="theme_greenapple">Green Apple</string>
<string name="theme_lavender">Lavender</string> <string name="theme_lavender">Lavender</string>
<string name="theme_midnightdusk">Midnight Dusk</string> <string name="theme_midnightdusk">Midnight Dusk</string>
<string name="theme_nord">Nord</string>
<string name="theme_strawberrydaiquiri">Strawberry Daiquiri</string> <string name="theme_strawberrydaiquiri">Strawberry Daiquiri</string>
<string name="theme_tako">Tako</string> <string name="theme_tako">Tako</string>
<string name="theme_tealturquoise">Teal &amp; Turquoise</string> <string name="theme_tealturquoise">Teal &amp; Turquoise</string>
@ -326,8 +323,8 @@
<string name="untrusted_extension">Untrusted extension</string> <string name="untrusted_extension">Untrusted extension</string>
<string name="untrusted_extension_message">This extension was signed by any unknown author and wasn\'t loaded.\n\nMalicious extensions can read any stored login credentials or execute arbitrary code.\n\nBy trusting this extension\'s certificate, you accept these risks.</string> <string name="untrusted_extension_message">This extension was signed by any unknown author and wasn\'t loaded.\n\nMalicious extensions can read any stored login credentials or execute arbitrary code.\n\nBy trusting this extension\'s certificate, you accept these risks.</string>
<string name="obsolete_extension_message">This extension is no longer available. It may not function properly and can cause issues with the app. Uninstalling it is recommended.</string> <string name="obsolete_extension_message">This extension is no longer available. It may not function properly and can cause issues with the app. Uninstalling it is recommended.</string>
<string name="unofficial_extension_message">This extension is not from the official list.</string> <string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="extension_api_error">Failed to get extensions list</string> <string name="extension_api_error">Failed to fetch available extensions</string>
<string name="ext_info_version">Version</string> <string name="ext_info_version">Version</string>
<string name="ext_info_language">Language</string> <string name="ext_info_language">Language</string>
<string name="ext_info_age_rating">Age rating</string> <string name="ext_info_age_rating">Age rating</string>
@ -342,6 +339,7 @@
<string name="ext_installer_private" translatable="false">Private</string> <string name="ext_installer_private" translatable="false">Private</string>
<string name="ext_installer_shizuku_stopped">Shizuku is not running</string> <string name="ext_installer_shizuku_stopped">Shizuku is not running</string>
<string name="ext_installer_shizuku_unavailable_dialog">Install and start Shizuku to use Shizuku as extension installer.</string> <string name="ext_installer_shizuku_unavailable_dialog">Install and start Shizuku to use Shizuku as extension installer.</string>
<string name="ext_revoke_trust">Revoke trusted unknown extensions</string>
<!-- Extension repos --> <!-- Extension repos -->
<string name="label_extension_repos">Extension repos</string> <string name="label_extension_repos">Extension repos</string>
@ -728,9 +726,9 @@
<string name="display_mode_chapter">Chapter %1$s</string> <string name="display_mode_chapter">Chapter %1$s</string>
<string name="manga_display_interval_title">Estimate every</string> <string name="manga_display_interval_title">Estimate every</string>
<string name="manga_display_modified_interval_title">Set to update every</string> <string name="manga_display_modified_interval_title">Set to update every</string>
<string name="manga_interval_header">Next update</string>
<!-- "... around 2 days" --> <!-- "... around 2 days" -->
<string name="manga_interval_expected_update">Next update expected in around %1$s, checking around every %2$s</string> <string name="manga_interval_expected_update">New chapters predicted to be released in around %1$s, checking around every %2$s.</string>
<string name="manga_interval_expected_update_soon">Soon</string>
<string name="manga_interval_custom_amount">Custom update frequency:</string> <string name="manga_interval_custom_amount">Custom update frequency:</string>
<string name="chapter_downloading_progress">Downloading (%1$d/%2$d)</string> <string name="chapter_downloading_progress">Downloading (%1$d/%2$d)</string>
<string name="chapter_error">Error</string> <string name="chapter_error">Error</string>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--https://www.nordtheme.com/docs/colors-and-palettes-->
<resources>
<color name="nord_primary">#88C0D0</color>
<color name="nord_onPrimary">#2E3440</color>
<color name="nord_primaryContainer">#88C0D0</color>
<color name="nord_onPrimaryContainer">#2E3440</color>
<color name="nord_secondary">#81A1C1</color>
<color name="nord_onSecondary">#2E3440</color>
<color name="nord_secondaryContainer">#81A1C1</color>
<color name="nord_onSecondaryContainer">#2E3440</color>
<color name="nord_tertiary">#5E81AC</color>
<color name="nord_onTertiary">#000000</color>
<color name="nord_tertiaryContainer">#5E81AC</color>
<color name="nord_onTertiaryContainer">#000000</color>
<color name="nord_background">#2E3440</color>
<color name="nord_onBackground">#ECEFF4</color>
<color name="nord_surface">#3B4252</color>
<color name="nord_onSurface">#ECEFF4</color>
<color name="nord_surfaceVariant">#2E3440</color>
<color name="nord_onSurfaceVariant">#ECEFF4</color>
<color name="nord_outline">#D8DEE9</color>
<color name="nord_inverseOnSurface">#2E3440</color>
<color name="nord_inverseSurface">#D8DEE9</color>
<color name="nord_primaryInverse">#397E91</color>
<color name="nord_elevationOverlay">#434C5E</color>
<color name="nord_onError">#2E3440</color>
<color name="nord_errorContainer">#BF616A</color>
<color name="nord_onErrorContainer">#000000</color>
</resources>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--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-->
<resources>
<color name="nord_primary">#5E81AC</color>
<color name="nord_onPrimary">#000000</color>
<color name="nord_primaryContainer">#5E81AC</color>
<color name="nord_onPrimaryContainer">#000000</color>
<color name="nord_secondary">#81A1C1</color>
<color name="nord_onSecondary">#2E3440</color>
<color name="nord_secondaryContainer">#81A1C1</color>
<color name="nord_onSecondaryContainer">#2E3440</color>
<color name="nord_tertiary">#88C0D0</color>
<color name="nord_onTertiary">#2E3440</color>
<color name="nord_tertiaryContainer">#88C0D0</color>
<color name="nord_onTertiaryContainer">#2E3440</color>
<color name="nord_background">#ECEFF4</color>
<color name="nord_onBackground">#2E3440</color>
<color name="nord_surface">#E5E9F0</color>
<color name="nord_onSurface">#2E3440</color>
<color name="nord_surfaceVariant">#ffffff</color>
<color name="nord_onSurfaceVariant">#2E3440</color>
<color name="nord_outline">#4C566A</color>
<color name="nord_inverseOnSurface">#ECEFF4</color>
<color name="nord_inverseSurface">#3B4252</color>
<color name="nord_primaryInverse">#8CA8CD</color>
<color name="nord_elevationOverlay">#D8DEE9</color>
<color name="nord_onError">#ECEFF4</color>
<color name="nord_errorContainer">#BF616A</color>
<color name="nord_onErrorContainer">#000000</color>
</resources>