mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-15 15:02:49 +01:00
Merge branch 'sync-part-final' into feat/add-sync-triggers-experimental
This commit is contained in:
commit
9859a3d129
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
@ -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/"),
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
)
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,20 +339,22 @@ 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(
|
) {
|
||||||
downloadBadge = it[0] as Boolean,
|
ItemPreferences(
|
||||||
localBadge = it[1] as Boolean,
|
downloadBadge = it[0] as Boolean,
|
||||||
languageBadge = it[2] as Boolean,
|
localBadge = it[1] as Boolean,
|
||||||
globalFilterDownloaded = it[3] as Boolean,
|
languageBadge = it[2] as Boolean,
|
||||||
filterDownloaded = it[4] as TriState,
|
skipOutsideReleasePeriod = LibraryPreferences.MANGA_OUTSIDE_RELEASE_PERIOD in (it[3] as Set<*>),
|
||||||
filterUnread = it[5] as TriState,
|
globalFilterDownloaded = it[4] as Boolean,
|
||||||
filterStarted = it[6] as TriState,
|
filterDownloaded = it[5] as TriState,
|
||||||
filterBookmarked = it[7] as TriState,
|
filterUnread = it[6] as TriState,
|
||||||
filterCompleted = it[8] 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 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
|
||||||
|
@ -378,12 +378,15 @@ class MangaScreenModel(
|
|||||||
|
|
||||||
fun setFetchInterval(manga: Manga, interval: Int) {
|
fun setFetchInterval(manga: Manga, interval: Int) {
|
||||||
screenModelScope.launchIO {
|
screenModelScope.launchIO {
|
||||||
updateManga.awaitUpdateFetchInterval(
|
if (
|
||||||
// Custom intervals are negative
|
updateManga.awaitUpdateFetchInterval(
|
||||||
manga.copy(fetchInterval = -interval),
|
// Custom intervals are negative
|
||||||
)
|
manga.copy(fetchInterval = -interval),
|
||||||
val updatedManga = mangaRepository.getMangaById(manga.id)
|
)
|
||||||
updateSuccessState { it.copy(manga = updatedManga) }
|
) {
|
||||||
|
val updatedManga = mangaRepository.getMangaById(manga.id)
|
||||||
|
updateSuccessState { it.copy(manga = updatedManga) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()!!)
|
||||||
|
@ -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)
|
||||||
|
@ -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="" />
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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,34 +92,31 @@ 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(
|
|
||||||
if (manga.lastUpdate > 0) Instant.ofEpochMilli(manga.lastUpdate) else Instant.now(),
|
|
||||||
dateTime.zone,
|
|
||||||
)
|
|
||||||
.toLocalDate()
|
|
||||||
.atStartOfDay()
|
|
||||||
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt()
|
|
||||||
val cycle = timeSinceLatest.floorDiv(
|
|
||||||
interval.absoluteValue.takeIf { interval < 0 }
|
|
||||||
?: doubleInterval(interval, timeSinceLatest, doubleWhenOver = 10),
|
|
||||||
)
|
|
||||||
latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(dateTime.offset) * 1000
|
|
||||||
} else {
|
|
||||||
manga.nextUpdate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val latestDate = ZonedDateTime.ofInstant(
|
||||||
|
if (manga.lastUpdate > 0) Instant.ofEpochMilli(manga.lastUpdate) else Instant.now(),
|
||||||
|
dateTime.zone,
|
||||||
|
)
|
||||||
|
.toLocalDate()
|
||||||
|
.atStartOfDay()
|
||||||
|
val timeSinceLatest = ChronoUnit.DAYS.between(latestDate, dateTime).toInt()
|
||||||
|
val cycle = timeSinceLatest.floorDiv(
|
||||||
|
interval.absoluteValue.takeIf { interval < 0 }
|
||||||
|
?: increaseInterval(interval, timeSinceLatest, increaseWhenOver = 10),
|
||||||
|
)
|
||||||
|
return latestDate.plusDays((cycle + 1) * interval.toLong()).toEpochSecond(dateTime.offset) * 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
@ -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" }
|
||||||
|
@ -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 & Turquoise</string>
|
<string name="theme_tealturquoise">Teal & 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>
|
||||||
|
30
presentation-core/src/main/res/values-night/colors_nord.xml
Normal file
30
presentation-core/src/main/res/values-night/colors_nord.xml
Normal 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>
|
31
presentation-core/src/main/res/values/colors_nord.xml
Normal file
31
presentation-core/src/main/res/values/colors_nord.xml
Normal 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>
|
Loading…
Reference in New Issue
Block a user