Improve Theme cover based (#81)

* Support Pure dark mode
* Tag using elevated style (non pure-dark-mode) and more vibrant outline color (pure-dark-mode)
* More color for various icons
* Color theme for Metadata screen
* Always animated color change
* Always/Only use dominant color for library/update/upcoming cover's background
* Don't always generate color each time cover loaded (library/browse), only update color when new cover changed on detail page
* Fix the spinner background in Edit Manga Info
* Improve manga's backdrop color

Detail:
* Change Cover's color & ratio preference key to AppState

* using ElevatedSuggestionChip with color

* more icon color

* color theme for MetadataViewScreen

* enable animation by default

* Fix: can't turn off theme cover based

* Won't check pref for theme enabled everytime, instead always generate cover's color to run faster later

* Skip generate cover's vibrant color for browsing mangas if already generated, also won't even trying to read ratio for favorite mangas

* Only generate new seed color when cover loaded in detail page

* only use dominantColor for cover when cover is unable to load

* Don't use elevatedSuggestionChip for darkmode and change border color

* EditMangaInfo: Set Spinner's selected item's background color to transparent

* update chip color

* Colors Palette screen

* Support Amoled Dark Mode by upgrading Material-Kolor to 1.7.0

* improve manga's backdrop color

* only generate color while browsing if option is enabled, aslo fixed the case won't update dominant color if cover changed

* always using theme animation

* update translation
This commit is contained in:
Cuong M. Tran 2024-06-12 18:22:44 +07:00 committed by GitHub
parent 95fef0d019
commit 753285275f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 558 additions and 275 deletions

View file

@ -25,12 +25,10 @@ class UiPreferences(
fun themeDarkAmoled() = preferenceStore.getBoolean("pref_theme_dark_amoled_key", false)
fun detailsPageThemeCoverBased() = preferenceStore.getBoolean("pref_details_page_theme_cover_based_key", true)
fun themeCoverBased() = preferenceStore.getBoolean("pref_theme_cover_based_key", true)
fun themeCoverBasedStyle() = preferenceStore.getEnum("pref_theme_cover_based_style_key", PaletteStyle.Vibrant)
fun themeCoverBasedAnimate() = preferenceStore.getBoolean("pref_theme_cover_based_animate_key", false)
fun relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
fun dateFormat() = preferenceStore.getString("app_date_format", "")

View file

@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark
@ -48,10 +49,15 @@ fun HistoryItem(
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
verticalAlignment = Alignment.CenterVertically,
) {
val mangaCover = history.coverData
val bgColor = mangaCover.dominantCoverColors?.first?.let { Color(it) }
val onBgColor = mangaCover.dominantCoverColors?.second
MangaCover.Book(
modifier = Modifier.fillMaxHeight(),
data = history.coverData,
data = mangaCover,
onClick = onClickCover,
bgColor = bgColor,
tint = onBgColor,
)
Column(
modifier = Modifier

View file

@ -27,18 +27,15 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shadow
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import eu.kanade.presentation.manga.components.CoverPlaceholderColor
import eu.kanade.presentation.manga.components.MangaCover
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.BadgeGroup
@ -74,7 +71,10 @@ fun MangaCompactGridItem(
coverAlpha: Float = 1f,
coverBadgeStart: @Composable (RowScope.() -> Unit)? = null,
coverBadgeEnd: @Composable (RowScope.() -> Unit)? = null,
libraryColored: Boolean? = null,
) {
val bgColor = libraryColored?.let { coverData.dominantCoverColors?.first?.let { Color(it) } }
val onBgColor = libraryColored?.let { coverData.dominantCoverColors?.second }
GridItemSelectable(
isSelected = isSelected,
onClick = onClick,
@ -84,15 +84,11 @@ fun MangaCompactGridItem(
cover = {
MangaCover.Book(
modifier = Modifier
.fillMaxWidth()
.background(
color = DomainMangaCover.coverColorMap[coverData.mangaId]?.first?.let { Color(it) }
?: MaterialTheme.colorScheme.surface
),
.fillMaxWidth(),
alpha = if (isSelected) GridSelectedCoverAlpha else coverAlpha,
data = coverData,
tint = DomainMangaCover.coverColorMap[coverData.mangaId]?.second
?: MaterialTheme.colorScheme.onSurface.toArgb(),
bgColor = bgColor,
tint = onBgColor,
)
},
badgesStart = coverBadgeStart,
@ -182,7 +178,10 @@ fun MangaComfortableGridItem(
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
onClickContinueReading: (() -> Unit)? = null,
libraryColored: Boolean? = null,
) {
val bgColor = libraryColored?.let { coverData.dominantCoverColors?.first?.let { Color(it) } }
val onBgColor = libraryColored?.let { coverData.dominantCoverColors?.second }
GridItemSelectable(
isSelected = isSelected,
onClick = onClick,
@ -193,15 +192,11 @@ fun MangaComfortableGridItem(
cover = {
MangaCover.Book(
modifier = Modifier
.fillMaxWidth()
.background(
color = DomainMangaCover.coverColorMap[coverData.mangaId]?.first?.let { Color(it) }
?: MaterialTheme.colorScheme.surface
),
.fillMaxWidth(),
alpha = if (isSelected) GridSelectedCoverAlpha else coverAlpha,
data = coverData,
tint = DomainMangaCover.coverColorMap[coverData.mangaId]?.second
?: MaterialTheme.colorScheme.onSurface.toArgb(),
bgColor = bgColor,
tint = onBgColor,
)
},
badgesStart = coverBadgeStart,
@ -339,7 +334,10 @@ fun MangaListItem(
isSelected: Boolean = false,
coverAlpha: Float = 1f,
onClickContinueReading: (() -> Unit)? = null,
libraryColored: Boolean? = null,
) {
val bgColor = libraryColored?.let { coverData.dominantCoverColors?.first?.let { Color(it) } }
val onBgColor = libraryColored?.let { coverData.dominantCoverColors?.second }
Row(
modifier = Modifier
.selectedBackground(isSelected)
@ -353,15 +351,11 @@ fun MangaListItem(
) {
MangaCover.Square(
modifier = Modifier
.fillMaxHeight()
.background(
color = DomainMangaCover.coverColorMap[coverData.mangaId]?.first?.let { Color(it) }
?: MaterialTheme.colorScheme.surface
),
.fillMaxHeight(),
alpha = coverAlpha,
data = coverData,
tint = DomainMangaCover.coverColorMap[coverData.mangaId]?.second
?: MaterialTheme.colorScheme.onSurface.toArgb(),
bgColor = bgColor,
tint = onBgColor,
)
Text(
text = title,

View file

@ -61,6 +61,7 @@ internal fun LibraryComfortableGrid(
} else {
null
},
libraryColored = true,
)
}
}

View file

@ -62,6 +62,7 @@ internal fun LibraryCompactGrid(
} else {
null
},
libraryColored = true,
)
}
}

View file

@ -69,6 +69,7 @@ internal fun LibraryList(
} else {
null
},
libraryColored = true,
)
}
}

View file

@ -101,6 +101,7 @@ import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.service.missingChaptersCount
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.source.model.StubSource
import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR
@ -183,6 +184,8 @@ fun MangaScreen(
onRelatedMangaClick: (Manga) -> Unit,
onRelatedMangaLongClick: (Manga) -> Unit,
onSourceClick: () -> Unit,
onCoverLoaded: (MangaCover) -> Unit,
onPaletteScreenClick: () -> Unit,
// KMK <--
) {
val context = LocalContext.current
@ -243,6 +246,8 @@ fun MangaScreen(
onRelatedMangaClick = onRelatedMangaClick,
onRelatedMangaLongClick = onRelatedMangaLongClick,
onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
onPaletteScreenClick = onPaletteScreenClick,
// KMK <--
)
} else {
@ -296,6 +301,8 @@ fun MangaScreen(
onRelatedMangaClick = onRelatedMangaClick,
onRelatedMangaLongClick = onRelatedMangaLongClick,
onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
onPaletteScreenClick = onPaletteScreenClick,
// KMK <--
)
}
@ -366,6 +373,8 @@ private fun MangaScreenSmallImpl(
onRelatedMangaClick: (Manga) -> Unit,
onRelatedMangaLongClick: (Manga) -> Unit,
onSourceClick: () -> Unit,
onCoverLoaded: (MangaCover) -> Unit,
onPaletteScreenClick: () -> Unit,
// KMK <--
) {
val chapterListState = rememberLazyListState()
@ -437,6 +446,7 @@ private fun MangaScreenSmallImpl(
actionModeCounter = selectedChapterCount,
onSelectAll = { onAllChapterSelected(true) },
onInvertSelection = { onInvertSelection() },
onPaletteScreenClick = onPaletteScreenClick,
)
},
bottomBar = {
@ -520,6 +530,7 @@ private fun MangaScreenSmallImpl(
doSearch = onSearch,
// KMK -->
onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
// KMK <--
)
}
@ -747,6 +758,8 @@ private fun MangaScreenLargeImpl(
onRelatedMangaClick: (Manga) -> Unit,
onRelatedMangaLongClick: (Manga) -> Unit,
onSourceClick: () -> Unit,
onCoverLoaded: (MangaCover) -> Unit,
onPaletteScreenClick: () -> Unit,
// KMK <--
) {
val layoutDirection = LocalLayoutDirection.current
@ -809,6 +822,7 @@ private fun MangaScreenLargeImpl(
actionModeCounter = selectedChapterCount,
onSelectAll = { onAllChapterSelected(true) },
onInvertSelection = { onInvertSelection() },
onPaletteScreenClick = onPaletteScreenClick,
)
},
bottomBar = {
@ -893,6 +907,7 @@ private fun MangaScreenLargeImpl(
doSearch = onSearch,
// KMK -->
onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
// KMK <--
)
MangaActionRow(

View file

@ -102,7 +102,7 @@ private fun NotDownloadedIndicator(
painter = painterResource(R.drawable.ic_download_chapter_24dp),
contentDescription = stringResource(MR.strings.manga_download),
modifier = Modifier.size(IndicatorSize),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
tint = MaterialTheme.colorScheme.primary,
)
}
}
@ -128,7 +128,7 @@ private fun DownloadingIndicator(
contentAlignment = Alignment.Center,
) {
val arrowColor: Color
val strokeColor = MaterialTheme.colorScheme.onSurfaceVariant
val strokeColor = MaterialTheme.colorScheme.primary
val downloadProgress = downloadProgressProvider()
val indeterminate = downloadState == Download.State.QUEUE ||
(downloadState == Download.State.DOWNLOADING && downloadProgress == 0)
@ -208,7 +208,7 @@ private fun DownloadedIndicator(
imageVector = Icons.Filled.CheckCircle,
contentDescription = null,
modifier = Modifier.size(IndicatorSize),
tint = MaterialTheme.colorScheme.onSurfaceVariant,
tint = MaterialTheme.colorScheme.primary,
)
DropdownMenu(expanded = isMenuExpanded, onDismissRequest = { isMenuExpanded = false }) {
DropdownMenuItem(

View file

@ -3,11 +3,16 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.FilterList
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@ -25,7 +30,7 @@ fun ChapterHeader(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Column(
Row(
modifier = modifier
.fillMaxWidth()
.clickable(
@ -33,19 +38,30 @@ fun ChapterHeader(
onClick = onClick,
)
.padding(horizontal = 16.dp, vertical = 4.dp),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = if (chapterCount == null) {
stringResource(MR.strings.chapters)
} else {
pluralStringResource(MR.plurals.manga_num_chapters, count = chapterCount, chapterCount)
},
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
)
Column(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
) {
Text(
text = if (chapterCount == null) {
stringResource(MR.strings.chapters)
} else {
pluralStringResource(MR.plurals.manga_num_chapters, count = chapterCount, chapterCount)
},
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onBackground,
)
MissingChaptersWarning(missingChapterCount)
MissingChaptersWarning(missingChapterCount)
}
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
}

View file

@ -4,6 +4,7 @@ import androidx.annotation.ColorInt
import androidx.compose.animation.graphics.res.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.material3.MaterialTheme
@ -16,6 +17,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.semantics.Role
@ -23,6 +25,9 @@ import coil3.compose.AsyncImage
import eu.kanade.presentation.util.rememberResourceBitmapPainter
import eu.kanade.tachiyomi.R
import kotlinx.coroutines.delay
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.asMangaCover
import tachiyomi.domain.manga.model.MangaCover as DomainMangaCover
enum class MangaCover(val ratio: Float) {
Square(1f / 1f),
@ -36,8 +41,10 @@ enum class MangaCover(val ratio: Float) {
contentDescription: String = "",
shape: Shape = MaterialTheme.shapes.extraSmall,
onClick: (() -> Unit)? = null,
@ColorInt tint: Int = CoverPlaceholderColor,
alpha: Float = 1f,
bgColor: Color? = null,
@ColorInt tint: Int? = null,
onCoverLoaded: ((DomainMangaCover) -> Unit)? = null,
) {
val animatedImageVector = AnimatedImageVector.animatedVectorResource(R.drawable.anim_waiting)
var atEnd by remember { mutableStateOf(false) }
@ -60,12 +67,21 @@ enum class MangaCover(val ratio: Float) {
placeholder = rememberAnimatedVectorPainter(animatedImageVector = animatedImageVector, atEnd = atEnd),
error = rememberResourceBitmapPainter(id = R.drawable.cover_error, tint),
fallback = rememberResourceBitmapPainter(id = R.drawable.cover_error, tint),
onSuccess = { succeed = true },
onSuccess = {
succeed = true
if (onCoverLoaded != null) {
when (data) {
is Manga -> onCoverLoaded(data.asMangaCover())
is DomainMangaCover -> onCoverLoaded(data)
}
}
},
contentDescription = contentDescription,
modifier = modifier
.aspectRatio(ratio)
.clip(shape)
.alpha(if (succeed) alpha else 1f)
.background(bgColor ?: Color.Transparent)
.then(
if (onClick != null) {
Modifier.clickable(
@ -81,6 +97,4 @@ enum class MangaCover(val ratio: Float) {
}
}
@ColorInt
internal val CoverPlaceholderColor = 0x7F888888
const val LoadingAnimatedDuration = 600L

View file

@ -32,7 +32,6 @@ import androidx.compose.material.icons.filled.PersonOutline
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.outlined.Block
import androidx.compose.material.icons.outlined.CallMerge
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Done
import androidx.compose.material.icons.outlined.DoneAll
@ -74,7 +73,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import com.materialkolor.ktx.blend
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga
@ -88,9 +87,12 @@ import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.Instant
import java.time.temporal.ChronoUnit
import kotlin.math.roundToInt
import tachiyomi.domain.manga.model.MangaCover as DomainMangaCover
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
@ -110,6 +112,7 @@ fun MangaInfoBox(
modifier: Modifier = Modifier,
// KMK -->
onSourceClick: () -> Unit,
onCoverLoaded: (DomainMangaCover) -> Unit,
// KMK <--
) {
Box(modifier = modifier) {
@ -127,11 +130,14 @@ fun MangaInfoBox(
.drawWithContent {
drawContent()
drawRect(
brush = Brush.verticalGradient(colors = backdropGradientColors),
brush = Brush.verticalGradient(
colors = backdropGradientColors,
startY = size.height / 2,
),
)
}
.background(MaterialTheme.colorScheme.inversePrimary.copy(alpha = 0.2f))
.blur(4.dp)
.background(MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.4f))
.blur(7.dp)
.alpha(0.2f),
)
@ -151,6 +157,7 @@ fun MangaInfoBox(
isStubSource = isStubSource,
// KMK -->
onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
// KMK <--
)
} else {
@ -167,6 +174,7 @@ fun MangaInfoBox(
isStubSource = isStubSource,
// KMK -->
onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
// KMK <--
)
}
@ -226,7 +234,11 @@ fun MangaActionRow(
)
},
icon = Icons.Default.HourglassEmpty,
color = if (isUserIntervalMode) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
color = if (isUserIntervalMode || nextUpdateDays?.let { it <= 1 } == true) {
MaterialTheme.colorScheme.primary
} else {
defaultActionButtonColor
},
onClick = { onEditIntervalClicked?.invoke() },
)
MangaActionButton(
@ -274,6 +286,8 @@ fun ExpandableMangaDescription(
// SY <--
modifier: Modifier = Modifier,
) {
val uiPreferences = Injekt.get<UiPreferences>()
val pureDarkMode = uiPreferences.themeDarkAmoled().get()
Column(modifier = modifier) {
val (expanded, onExpanded) = rememberSaveable {
mutableStateOf(defaultExpandState)
@ -341,6 +355,7 @@ fun ExpandableMangaDescription(
tagSelected = it
showMenu = true
},
pureDarkMode = pureDarkMode,
)
} else {
// SY <--
@ -356,6 +371,7 @@ fun ExpandableMangaDescription(
tagSelected = it
showMenu = true
},
pureDarkMode = pureDarkMode,
)
}
}
@ -373,6 +389,7 @@ fun ExpandableMangaDescription(
tagSelected = it
showMenu = true
},
pureDarkMode = pureDarkMode,
)
}
}
@ -396,6 +413,7 @@ private fun MangaAndSourceTitlesLarge(
isStubSource: Boolean,
// KMK -->
onSourceClick: () -> Unit,
onCoverLoaded: (DomainMangaCover) -> Unit,
// KMK <--
) {
Column(
@ -409,6 +427,7 @@ private fun MangaAndSourceTitlesLarge(
data = coverDataProvider(),
contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick,
onCoverLoaded = { mangaCover -> onCoverLoaded(mangaCover) },
)
Spacer(modifier = Modifier.height(16.dp))
MangaContentInfo(
@ -441,6 +460,7 @@ private fun MangaAndSourceTitlesSmall(
isStubSource: Boolean,
// KMK -->
onSourceClick: () -> Unit,
onCoverLoaded: (DomainMangaCover) -> Unit,
// KMK <--
) {
Row(
@ -457,6 +477,7 @@ private fun MangaAndSourceTitlesSmall(
data = coverDataProvider(),
contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick,
onCoverLoaded = { mangaCover -> onCoverLoaded(mangaCover) },
)
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
@ -674,7 +695,7 @@ private fun MangaSummary(
contentDescription = stringResource(
if (expanded) MR.strings.manga_info_collapse else MR.strings.manga_info_expand,
),
tint = MaterialTheme.colorScheme.onBackground,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.background(Brush.radialGradient(colors = colors.asReversed())),
)
}

View file

@ -22,6 +22,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.icerock.moko.graphics.BuildConfig
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DownloadDropdownMenu
@ -59,6 +60,7 @@ fun MangaToolbar(
modifier: Modifier = Modifier,
backgroundAlphaProvider: () -> Float = titleAlphaProvider,
onPaletteScreenClick: () -> Unit,
) {
Column(
modifier = modifier,
@ -191,6 +193,14 @@ fun MangaToolbar(
}
// SY <--
// KMK -->
if (BuildConfig.DEBUG) {
add(
AppBar.OverflowAction(
title = "Colors Palette",
onClick = onPaletteScreenClick,
),
)
}
// add(
// AppBar.OverflowAction(
// title = stringResource(MR.strings.pref_invalidate_download_cache),
@ -204,7 +214,6 @@ fun MangaToolbar(
// ),
// )
// KMK <--
}
.build(),
)

View file

@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ElevatedSuggestionChip
import androidx.compose.material3.LocalMinimumInteractiveComponentSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SuggestionChip
@ -98,6 +99,7 @@ value class SearchMetadataChips(
fun NamespaceTags(
tags: SearchMetadataChips,
onClick: (item: String) -> Unit,
pureDarkMode: Boolean = false,
) {
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp)) {
tags.tags.forEach { (namespace, tags) ->
@ -107,6 +109,7 @@ fun NamespaceTags(
modifier = Modifier.padding(top = 4.dp),
text = namespace,
onClick = null,
pureDarkMode = pureDarkMode,
)
}
FlowRow(
@ -120,11 +123,23 @@ fun NamespaceTags(
text = text,
onClick = { onClick(search) },
border = borderDp?.let {
SuggestionChipDefaults.suggestionChipBorder(borderWidth = it)
} ?: SuggestionChipDefaults.suggestionChipBorder(),
SuggestionChipDefaults.suggestionChipBorder(
borderWidth = it,
borderColor = MaterialTheme.colorScheme.primary,
)
} ?: SuggestionChipDefaults.suggestionChipBorder(
borderColor = MaterialTheme.colorScheme.primary,
),
borderM3 = borderDp?.let {
SuggestionChipDefaultsM3.suggestionChipBorder(enabled = true, borderWidth = it)
} ?: SuggestionChipDefaultsM3.suggestionChipBorder(enabled = true),
SuggestionChipDefaultsM3.suggestionChipBorder(
enabled = true,
borderWidth = it,
borderColor = MaterialTheme.colorScheme.primary,
)
} ?: SuggestionChipDefaultsM3.suggestionChipBorder(
enabled = true,
borderColor = MaterialTheme.colorScheme.primary,
),
)
}
}
@ -139,23 +154,45 @@ fun TagsChip(
onClick: (() -> Unit)?,
modifier: Modifier = Modifier,
border: ChipBorder? = SuggestionChipDefaults.suggestionChipBorder(),
borderM3: BorderStroke? = SuggestionChipDefaultsM3.suggestionChipBorder(enabled = true),
borderM3: BorderStroke? = null,
pureDarkMode: Boolean = false,
) {
CompositionLocalProvider(LocalMinimumInteractiveComponentSize provides 0.dp) {
if (onClick != null) {
SuggestionChip(
modifier = modifier,
onClick = onClick,
label = {
Text(
text = text,
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
border = borderM3,
)
if (borderM3 != null || pureDarkMode) {
SuggestionChip(
modifier = modifier,
onClick = onClick,
label = {
Text(
text = text,
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
border = borderM3 ?: SuggestionChipDefaultsM3.suggestionChipBorder(
enabled = true,
borderColor = MaterialTheme.colorScheme.primary,
),
)
} else {
ElevatedSuggestionChip(
modifier = modifier,
onClick = onClick,
label = {
Text(
text = text,
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
},
colors = SuggestionChipDefaultsM3.elevatedSuggestionChipColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
),
)
}
} else {
SuggestionChip(
modifier = modifier,

View file

@ -112,14 +112,14 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup {
val scope = rememberCoroutineScope()
val detailsPageThemeCoverBased by remember {
Injekt.get<UiPreferences>().detailsPageThemeCoverBased().asState(scope)
Injekt.get<UiPreferences>().themeCoverBased().asState(scope)
}
return Preference.PreferenceGroup(
title = stringResource(KMR.strings.pref_details_page_theme),
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = uiPreferences.detailsPageThemeCoverBased(),
title = stringResource(KMR.strings.pref_details_page_theme_cover_based),
pref = uiPreferences.themeCoverBased(),
title = stringResource(KMR.strings.pref_theme_cover_based),
),
Preference.PreferenceItem.ListPreference(
pref = uiPreferences.themeCoverBasedStyle(),
@ -129,11 +129,6 @@ object SettingsAppearanceScreen : SearchableSettings {
.associateWith { it.name }
.toImmutableMap(),
),
Preference.PreferenceItem.SwitchPreference(
pref = uiPreferences.themeCoverBasedAnimate(),
title = stringResource(KMR.strings.pref_theme_cover_based_animate),
enabled = detailsPageThemeCoverBased,
),
),
)
}

View file

@ -26,6 +26,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback
@ -168,12 +169,17 @@ private fun UpdatesUiItem(
.padding(horizontal = MaterialTheme.padding.medium),
verticalAlignment = Alignment.CenterVertically,
) {
val mangaCover = update.coverData
val bgColor = mangaCover.dominantCoverColors?.first?.let { Color(it) }
val onBgColor = mangaCover.dominantCoverColors?.second
MangaCover.Square(
modifier = Modifier
.padding(vertical = 6.dp)
.fillMaxHeight(),
data = update.coverData,
data = mangaCover,
onClick = onClickCover,
bgColor = bgColor,
tint = onBgColor,
)
Column(

View file

@ -42,6 +42,7 @@ import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.MangaCoverMetadata
import eu.kanade.tachiyomi.data.coil.MangaKeyer
import eu.kanade.tachiyomi.data.coil.PagePreviewFetcher
import eu.kanade.tachiyomi.data.coil.PagePreviewKeyer
@ -54,7 +55,6 @@ import eu.kanade.tachiyomi.di.SYPreferenceModule
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.data.coil.MangaCoverMetadata
import eu.kanade.tachiyomi.util.system.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale

View file

@ -68,8 +68,8 @@ class MangaCoverFetcher(
private val imageLoader: ImageLoader,
) : Fetcher {
private val uiPreferences: UiPreferences = Injekt.get()
private val fileScope = CoroutineScope(Job() + Dispatchers.IO)
private val fileScope by lazy { CoroutineScope(Job() + Dispatchers.IO) }
private val uiPreferences = Injekt.get<UiPreferences>()
private val diskCacheKey: String
get() = diskCacheKeyLazy.value
@ -325,18 +325,17 @@ class MangaCoverFetcher(
* @param bufferedSource if not null then it will load bitmap from [BufferedSource], regardless of [ogFile]
* @param ogFile if not null then it will load bitmap from [File]. If it's null then it will try to load bitmap
* from [CoverCache] using either [CoverCache.customCoverCacheDir] or [CoverCache.cacheDir]
* @param force if true (default) then it will always re-calculate ratio & color for favorite mangas.
* This is useful when a favorite manga updates/changes its cover. If false then it will only update ratio.
* @param force if true then it will always re-calculate ratio & color for favorite mangas.
*/
private fun setRatioAndColorsInScope(
mangaCover: MangaCover,
bufferedSource: BufferedSource? = null,
ogFile: File? = null,
force: Boolean = true
onlyFavorite: Boolean = !uiPreferences.themeCoverBased().get(),
force: Boolean = false,
) {
if (!uiPreferences.detailsPageThemeCoverBased().get()) return
fileScope.launch {
MangaCoverMetadata.setRatioAndColors(mangaCover, bufferedSource, ogFile, force)
MangaCoverMetadata.setRatioAndColors(mangaCover, bufferedSource, ogFile, onlyFavorite, force)
}
}

View file

@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.data.coil
import android.graphics.BitmapFactory
import androidx.palette.graphics.Palette
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import okio.BufferedSource
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.MangaCover
@ -65,37 +66,57 @@ object MangaCoverMetadata {
* @param bufferedSource if not null then it will load bitmap from [BufferedSource], regardless of [ogFile]
* @param ogFile if not null then it will load bitmap from [File]. If it's null then it will try to load bitmap
* from [CoverCache] using either [CoverCache.customCoverCacheDir] or [CoverCache.cacheDir]
* @param force if true (default) then it will always re-calculate ratio & color for favorite mangas.
* This is useful when a favorite manga updates/changes its cover. If false then it will only update ratio.
* @param force if true then it will always re-calculate ratio & color for favorite mangas.
*
* @author Jays2Kings
* This is only for loading color first time it appears on Library/Browse. Any new colors caused by loading new
* cover when open a manga detail or change cover will be updated separately on [MangaScreenModel.setPaletteColor].
*/
fun setRatioAndColors(
mangaCover: MangaCover,
bufferedSource: BufferedSource? = null,
ogFile: File? = null,
force: Boolean = true,
onlyDominantColor: Boolean = true,
force: Boolean = false,
) {
if (!mangaCover.isMangaFavorite) {
mangaCover.remove()
if (mangaCover.vibrantCoverColor != null) return
}
if (mangaCover.isMangaFavorite && onlyDominantColor && mangaCover.dominantCoverColors != null) return
val options = BitmapFactory.Options()
val updateColors = mangaCover.isMangaFavorite && mangaCover.dominantCoverColors == null ||
!onlyDominantColor && mangaCover.vibrantCoverColor == null || force
if (updateColors) {
/**
* + Manga is Favorite & doesn't have dominant color
* For non-favorite, it doesn't care if dominant is there or not, if it has vibrant color then it will
* already be returned from beginning.
* + [onlyDominantColor] = false
* - Manga doesn't have vibrant color
*/
options.inSampleSize = 4
} else {
/**
* + [onlyDominantColor] = true
* - Manga is Favorite & already have dominant color
* + [onlyDominantColor] = false
* - Manga is Favorite & already have dominant color & vibrant color
* - Manga is not Favorite & already have vibrant color (already skip at beginning)
*/
// Just trying to update ratio without actual reading bitmap (bitmap will be null)
options.inJustDecodeBounds = true
// Don't even need to update ratio because we don't use it yet.
return
}
val file = ogFile
?: coverCache.getCustomCoverFile(mangaCover.mangaId).takeIf { it.exists() }
?: coverCache.getCoverFile(mangaCover.url)
val options = BitmapFactory.Options()
val hasVibrantColor = if (mangaCover.isMangaFavorite) mangaCover.vibrantCoverColor != null else true
// If dominantCoverColors is not null, it means that color is restored from Prefs
// and also has vibrantCoverColor (e.g. new color caused by updated cover)
val updateRatioOnly = mangaCover.dominantCoverColors != null && hasVibrantColor && !force
if (updateRatioOnly) {
// Just trying to update ratio without actual reading bitmap (bitmap will be null)
// This is often when open a favorite manga
options.inJustDecodeBounds = true
} else {
options.inSampleSize = 4
}
val bitmap = when {
bufferedSource != null -> BitmapFactory.decodeStream(bufferedSource.inputStream(), null, options)
// if the file exists and the there was still an error then the file is corrupted

View file

@ -6,6 +6,7 @@ import android.os.Build
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.annotation.ColorInt
@ -82,7 +83,6 @@ fun EditMangaDialog(
textHighlightColor = MaterialTheme.colorScheme.outline.toArgb(),
iconColor = MaterialTheme.colorScheme.primary.toArgb(),
tagColor = MaterialTheme.colorScheme.outlineVariant.toArgb(),
tagFocusColor = MaterialTheme.colorScheme.outline.toArgb(),
tagTextColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb(),
btnTextColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
btnBgColor = MaterialTheme.colorScheme.surfaceTint.toArgb(),
@ -159,7 +159,6 @@ class EditMangaDialogColors(
@ColorInt val textHighlightColor: Int,
@ColorInt val iconColor: Int,
@ColorInt val tagColor: Int,
@ColorInt val tagFocusColor: Int,
@ColorInt val tagTextColor: Int,
@ColorInt val btnTextColor: Int,
@ColorInt val btnBgColor: Int,
@ -207,6 +206,14 @@ private fun onViewCreated(
)
}
// Set Spinner's selected item's background color to transparent
binding.status.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) {
if (view != null) (view as TextView).setBackgroundColor(0x00000000)
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
}
// Set Spinner's dropdown caret color
binding.status.backgroundTintList = ColorStateList.valueOf(colors.iconColor)
@ -346,18 +353,7 @@ private fun ChipGroup.setChips(
) {
removeAllViews()
val colorStateList = ColorStateList(
arrayOf(
intArrayOf(android.R.attr.state_focused),
intArrayOf(android.R.attr.state_pressed),
intArrayOf(-android.R.attr.state_active),
),
intArrayOf(
colors.tagFocusColor,
colors.tagFocusColor,
colors.tagColor,
),
)
val colorStateList = ColorStateList.valueOf(colors.tagColor)
items.asSequence().map { item ->
Chip(context).apply {

View file

@ -29,7 +29,6 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.materialkolor.DynamicMaterialTheme
import com.materialkolor.rememberDynamicColorScheme
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.ui.UiPreferences
@ -140,24 +139,17 @@ class MangaScreen(
val seedColorState = rememberUpdatedState(newValue = successState.seedColor)
val uiPreferences = remember { Injekt.get<UiPreferences>() }
if (uiPreferences.themeCoverBasedAnimate().get()) {
if (uiPreferences.themeCoverBased().get()) {
DynamicMaterialTheme(
seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary,
useDarkTheme = isSystemInDarkTheme(),
withAmoled = uiPreferences.themeDarkAmoled().get(),
style = uiPreferences.themeCoverBasedStyle().get(),
animate = uiPreferences.themeCoverBasedAnimate().get(),
animate = true,
content = { MaterialThemeContent(context, screenModel, successState) },
)
} else {
val colorScheme = rememberDynamicColorScheme(
seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary,
isDark = isSystemInDarkTheme(),
style = uiPreferences.themeCoverBasedStyle().get(),
)
MaterialTheme(
colorScheme = colorScheme,
content = { MaterialThemeContent(context, screenModel, successState) },
)
MaterialThemeContent(context, screenModel, successState)
}
}
@ -262,7 +254,7 @@ class MangaScreen(
previewsRowCount = successState.previewsRowCount,
// SY -->
onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite },
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) },
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga, successState.seedColor) },
onEditInfoClicked = screenModel::showEditMangaInfoDialog,
onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) },
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
@ -313,6 +305,8 @@ class MangaScreen(
navigator.push(ExtensionsScreen(searchSource = successState.source.name))
}
},
onCoverLoaded = screenModel::setPaletteColor,
onPaletteScreenClick = { navigator.push(PaletteScreen(successState.seedColor)) }
// KMK <--
)

View file

@ -15,7 +15,6 @@ import androidx.palette.graphics.Palette
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import coil3.Image
import coil3.executeBlocking
import coil3.imageLoader
import coil3.request.ImageRequest
import coil3.request.allowHardware
@ -45,6 +44,7 @@ import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.manga.DownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.data.coil.getBestColor
import eu.kanade.tachiyomi.data.download.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
@ -64,7 +64,6 @@ import eu.kanade.tachiyomi.ui.manga.RelatedManga.Companion.sorted
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.data.coil.getBestColor
import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
@ -481,7 +480,7 @@ class MangaScreenModel(
/**
* Get the color of the manga cover by loading cover with ImageRequest directly from network.
*/
private suspend fun setPaletteColor(model: Any, method: ImageRequestType = ImageRequestType.Enqueue) {
fun setPaletteColor(model: Any) {
if (model is ImageRequest && model.defined.sizeResolver != null) return
val imageRequestBuilder = if (model is ImageRequest) {
@ -494,58 +493,41 @@ class MangaScreenModel(
val generatePalette: (Image) -> Unit = { image ->
val bitmap = image.asDrawable(context.resources).getBitmapOrNull()
if (bitmap != null) {
Palette.from(bitmap).generate { palette ->
Palette.from(bitmap).generate {
screenModelScope.launchUI {
palette?.getBestColor()?.let { vibrantColor ->
when (model) {
is Manga -> model.asMangaCover().vibrantCoverColor = vibrantColor
is MangaCover -> model.vibrantCoverColor = vibrantColor
if (it == null) return@launchUI
val mangaCover = when (model) {
is Manga -> model.asMangaCover()
is MangaCover -> model
else -> return@launchUI
}
if (mangaCover.isMangaFavorite) {
it.dominantSwatch?.let { swatch ->
mangaCover.dominantCoverColors = swatch.rgb to swatch.titleTextColor
}
}
val vibrantColor = it.getBestColor() ?: return@launchUI
mangaCover.vibrantCoverColor = vibrantColor
}
}
}
}
when (method) {
ImageRequestType.Enqueue -> {
context.imageLoader.enqueue(
imageRequestBuilder
.target(
onSuccess = generatePalette,
onError = {
// TODO: handle error
// val file = coverCache.getCoverFile(manga!!)
// if (file.exists()) {
// file.delete()
// setPaletteColor()
// }
},
)
.build()
context.imageLoader.enqueue(
imageRequestBuilder
.target(
onSuccess = generatePalette,
onError = {
// TODO: handle error
// val file = coverCache.getCoverFile(manga!!)
// if (file.exists()) {
// file.delete()
// setPaletteColor()
// }
},
)
}
ImageRequestType.Execute -> {
context.imageLoader.execute(imageRequestBuilder.build())
.image?.let { image ->
generatePalette(image)
}
}
ImageRequestType.ExecuteBlocking -> {
context.imageLoader.executeBlocking(imageRequestBuilder.build())
.image?.let { image ->
generatePalette(image)
}
}
ImageRequestType.IOContext -> {
withIOContext {
context.imageLoader.execute(imageRequestBuilder.build())
.image?.let { image ->
generatePalette(image)
}
}
}
}
.build()
)
}
// KMK -->
@ -2079,10 +2061,3 @@ sealed interface RelatedManga {
}
}
// KMK <--
enum class ImageRequestType {
IOContext,
Enqueue,
Execute,
ExecuteBlocking,
}

View file

@ -0,0 +1,197 @@
package eu.kanade.tachiyomi.ui.manga
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.materialkolor.DynamicMaterialTheme
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import tachiyomi.presentation.core.components.material.Button
import tachiyomi.presentation.core.components.material.ButtonDefaults
import tachiyomi.presentation.core.components.material.Scaffold
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/**
* A screen that displays a colors palette of current theme.
*/
class PaletteScreen(
private val seedColor: Color?,
) : Screen() {
@Composable
override fun Content() {
val uiPreferences = remember { Injekt.get<UiPreferences>() }
if (uiPreferences.themeCoverBased().get()) {
DynamicMaterialTheme(
seedColor = seedColor ?: MaterialTheme.colorScheme.primary,
useDarkTheme = isSystemInDarkTheme(),
withAmoled = uiPreferences.themeDarkAmoled().get(),
style = uiPreferences.themeCoverBasedStyle().get(),
animate = true,
content = { MaterialThemeContent() },
)
} else {
MaterialThemeContent()
}
}
@Composable
fun MaterialThemeContent() {
val navigator = LocalNavigator.currentOrThrow
Scaffold(
topBar = { scrollBehavior ->
AppBar(
title = "Colors Palette",
navigateUp = navigator::pop,
scrollBehavior = scrollBehavior,
)
},
) { contentPadding ->
Column(
modifier = Modifier.padding(contentPadding),
) {
ButtonsColor(
"accent & onPrimary",
seedColor ?: MaterialTheme.colorScheme.primary,
"accent & contentColor",
seedColor ?: MaterialTheme.colorScheme.primary,
MaterialTheme.colorScheme.onPrimary,
)
ButtonsColor(
"primary",
MaterialTheme.colorScheme.primary,
"primaryContainer",
MaterialTheme.colorScheme.primaryContainer
)
ButtonsColor(
"secondary",
MaterialTheme.colorScheme.secondary,
"secondaryContainer",
MaterialTheme.colorScheme.secondaryContainer
)
ButtonsColor(
"tertiary",
MaterialTheme.colorScheme.tertiary,
"tertiaryContainer",
MaterialTheme.colorScheme.tertiaryContainer
)
ButtonsColor(
"surface",
MaterialTheme.colorScheme.surface,
"surfaceVariant",
MaterialTheme.colorScheme.surfaceVariant
)
ButtonsColor(
"inverseSurface",
MaterialTheme.colorScheme.inverseSurface,
"surfaceTint",
MaterialTheme.colorScheme.surfaceTint
)
ButtonsColor(
"inversePrimary",
MaterialTheme.colorScheme.inversePrimary,
"background",
MaterialTheme.colorScheme.background
)
ButtonsColor(
"error",
MaterialTheme.colorScheme.error,
"errorContainer",
MaterialTheme.colorScheme.errorContainer
)
ButtonsColor(
"outline",
MaterialTheme.colorScheme.outline,
"outlineVariant",
MaterialTheme.colorScheme.outlineVariant
)
ButtonsColor(
"scrim",
MaterialTheme.colorScheme.scrim,
"surfaceBright",
MaterialTheme.colorScheme.surfaceBright
)
ButtonsColor(
"surfaceDim",
MaterialTheme.colorScheme.surfaceDim,
"surfaceContainer",
MaterialTheme.colorScheme.surfaceContainer
)
ButtonsColor(
"surfaceContainerHigh",
MaterialTheme.colorScheme.surfaceContainerHigh,
"surfaceContainerHighest",
MaterialTheme.colorScheme.surfaceContainerHighest
)
ButtonsColor(
"surfaceContainerLow",
MaterialTheme.colorScheme.surfaceContainerLow,
"surfaceContainerLowest",
MaterialTheme.colorScheme.surfaceContainerLowest
)
}
}
}
}
@Composable
fun ButtonsColor(
name1: String,
color1: Color,
name2: String,
color2: Color,
contentColor1: Color = contentColorFor(backgroundColor = color1),
contentColor2: Color = contentColorFor(backgroundColor = color2),
) {
Row(
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
) {
Button(
onClick = { },
Modifier
.weight(1f)
.padding(horizontal = 16.dp, vertical = 4.dp),
colors = ButtonDefaults.buttonColors(
containerColor = color1,
contentColor = contentColor1,
)
) {
Text(
text = name1,
)
}
Button(
onClick = { },
Modifier
.weight(1f)
.padding(horizontal = 16.dp, vertical = 4.dp),
colors = ButtonDefaults.buttonColors(
containerColor = color2,
contentColor = contentColor2,
),
) {
Text(
text = name2
)
}
}
}

View file

@ -24,7 +24,7 @@ import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import com.materialkolor.DynamicMaterialTheme
import com.materialkolor.PaletteStyle
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.copyToClipboard
@ -36,6 +36,8 @@ import tachiyomi.presentation.core.screens.EmptyScreen
import tachiyomi.presentation.core.screens.LoadingScreen
import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.plus
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MetadataViewScreen(
private val mangaId: Long,
@ -107,12 +109,19 @@ class MetadataViewScreen(
}
}
DynamicMaterialTheme(
seedColor = seedColor ?: MaterialTheme.colorScheme.primary,
useDarkTheme = isSystemInDarkTheme(),
style = PaletteStyle.Vibrant,
animate = true,
content = { content() }
)
val uiPreferences = remember { Injekt.get<UiPreferences>() }
if (uiPreferences.themeCoverBased().get()) {
DynamicMaterialTheme(
seedColor = seedColor ?: MaterialTheme.colorScheme.primary,
useDarkTheme = isSystemInDarkTheme(),
withAmoled = uiPreferences.themeDarkAmoled().get(),
style = uiPreferences.themeCoverBasedStyle().get(),
animate = true,
content = { content() },
)
} else {
content()
}
}
}

View file

@ -29,8 +29,8 @@ fun EHentaiDescription(
search: (String) -> Unit,
) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val iconColor = MaterialTheme.colorScheme.secondary.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
AndroidView(
modifier = Modifier.fillMaxWidth(),

View file

@ -20,7 +20,7 @@ import tachiyomi.i18n.MR
@Composable
fun EightMusesDescription(state: State.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
AndroidView(
modifier = Modifier.fillMaxWidth(),

View file

@ -20,7 +20,7 @@ import tachiyomi.i18n.sy.SYMR
@Composable
fun HBrowseDescription(state: State.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
AndroidView(
modifier = Modifier.fillMaxWidth(),

View file

@ -23,7 +23,7 @@ import kotlin.math.round
@Composable
fun MangaDexDescription(state: State.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
AndroidView(

View file

@ -27,7 +27,7 @@ import java.time.ZonedDateTime
@Composable
fun NHentaiDescription(state: State.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
AndroidView(
modifier = Modifier.fillMaxWidth(),

View file

@ -25,7 +25,7 @@ import kotlin.math.round
@Composable
fun PururinDescription(state: State.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
AndroidView(

View file

@ -26,7 +26,7 @@ import kotlin.math.round
@Composable
fun TsuminoDescription(state: State.Success, openMetadataViewer: () -> Unit) {
val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb()
val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.primary.toArgb()
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
AndroidView(

View file

@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@ -38,9 +39,14 @@ fun UpcomingItem(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large),
) {
val mangaCover = upcoming.asMangaCover()
val bgColor = mangaCover.dominantCoverColors?.first?.let { Color(it) }
val onBgColor = mangaCover.dominantCoverColors?.second
MangaCover.Book(
modifier = Modifier.fillMaxHeight(),
data = upcoming.asMangaCover(),
data = mangaCover,
bgColor = bgColor,
tint = onBgColor,
)
Text(
modifier = Modifier.weight(1f),

View file

@ -35,9 +35,15 @@ class LibraryPreferences(
fun lastUpdatedTimestamp() = preferenceStore.getLong(Preference.appStateKey("library_update_last_timestamp"), 0L)
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0)
fun coverRatios() = preferenceStore.getStringSet("pref_library_cover_ratios_key", emptySet())
fun coverRatios() = preferenceStore.getStringSet(
Preference.appStateKey("pref_library_cover_ratios_key"),
emptySet(),
)
fun coverColors() = preferenceStore.getStringSet("pref_library_cover_colors_key", emptySet())
fun coverColors() = preferenceStore.getStringSet(
Preference.appStateKey("pref_library_cover_colors_key"),
emptySet(),
)
fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
"library_update_restriction",

View file

@ -10,8 +10,6 @@ sqlite = "2.4.0"
voyager = "1.0.0"
detekt = "1.23.6"
detektCompose = "0.3.12"
paletteKtxVersion = "1.0.0"
materialKolorVersion = "1.6.1"
[libraries]
desugar = "com.android.tools:desugar_jdk_libs:2.0.4"
@ -70,8 +68,8 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:2.0.0"
compose-webview = "io.github.kevinnzou:compose-webview:0.33.6"
compose-grid = "io.woong.compose.grid:grid:1.2.2"
palette-ktx = { group = "androidx.palette", name = "palette-ktx", version.ref = "paletteKtxVersion" }
material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolorVersion" }
palette-ktx = "androidx.palette:palette-ktx:1.0.0"
material-kolor = "com.materialkolor:material-kolor:1.7.0"
swipe = "me.saket.swipe:swipe:1.3.0"

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">وضع التحديد بالجملة</string>
<string name="action_faq_and_guides">الأسئلة الشائعة والأدلة</string>
<string name="pref_details_page_theme">صفحة التفاصيل</string>
<string name="pref_details_page_theme_cover_based">تفاصيل سمة الصفحة استنادًا إلى الغلاف</string>
<string name="pref_theme_cover_based">السمة المبنية على الغلاف</string>
<string name="pref_theme_cover_based_style">نمط السمة</string>
<string name="pref_theme_cover_based_animate">الرسوم المتحركة للسمة</string>
<string name="ext_unofficial">غير رسمي</string>
<string name="unofficial_extension_message">وهذا التمديد ليس من المستودع الرسمي.</string>
<string name="download_cache_renew_interval">الفاصل الزمني لتجديد التحميل</string>

View file

@ -9,9 +9,8 @@
<!-- Preferences -->
<!-- General section -->
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="pref_expand_related_titles">Expand Related titles</string>
<string name="pref_expand_related_titles_summary">If turned off, related titles won\'t automatically load &amp; show expanded</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Režim hromadného výběru</string>
<string name="action_faq_and_guides">FAQ a návody</string>
<string name="pref_details_page_theme">Stránka s podrobnostmi</string>
<string name="pref_details_page_theme_cover_based">Šablona stránky s podrobnostmi na základě obalu</string>
<string name="pref_theme_cover_based">Šablona založená na obálce</string>
<string name="pref_theme_cover_based_style">Styl motivu</string>
<string name="pref_theme_cover_based_animate">Animace motivu</string>
<string name="ext_unofficial">Neoficiální</string>
<string name="unofficial_extension_message">Toto rozšíření není z oficiálního repozitáře.</string>
<string name="download_cache_renew_interval">Interval obnovení mezipaměti</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Masse valg tilstand</string>
<string name="action_faq_and_guides">Ofte stillede spørgsmål og vejledninger</string>
<string name="pref_details_page_theme">Detaljer side</string>
<string name="pref_details_page_theme_cover_based">Detaljer sidetema baseret på omslag</string>
<string name="pref_theme_cover_based">Tema baseret på omslag</string>
<string name="pref_theme_cover_based_style">Tema stil</string>
<string name="pref_theme_cover_based_animate">Tema animere</string>
<string name="ext_unofficial">Uofficiel</string>
<string name="unofficial_extension_message">Denne udvidelse er ikke fra den officielle repo.</string>
<string name="download_cache_renew_interval">Download cache forny interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Massenauswahlmodus</string>
<string name="action_faq_and_guides">FAQ und Anleitungen</string>
<string name="pref_details_page_theme">Detailseite</string>
<string name="pref_details_page_theme_cover_based">Detailseitenthema basierend auf Cover</string>
<string name="pref_theme_cover_based">Theme basierend auf Cover</string>
<string name="pref_theme_cover_based_style">Design-Stil</string>
<string name="pref_theme_cover_based_animate">Theme animieren</string>
<string name="ext_unofficial">Ungültig</string>
<string name="unofficial_extension_message">Diese Erweiterung ist nicht vom offiziellen Repo.</string>
<string name="download_cache_renew_interval">Download-Cache Erneuerungsintervall</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Λειτουργία μαζικής επιλογής</string>
<string name="action_faq_and_guides">Συχνές ερωτήσεις και οδηγοί</string>
<string name="pref_details_page_theme">Σελίδα λεπτομερειών</string>
<string name="pref_details_page_theme_cover_based">Θέμα λεπτομερειών σελίδας με βάση το εξώφυλλο</string>
<string name="pref_theme_cover_based">Θέμα βασισμένο στο εξώφυλλο</string>
<string name="pref_theme_cover_based_style">Στυλ θέματος</string>
<string name="pref_theme_cover_based_animate">Εφέ κίνησης θέματος</string>
<string name="ext_unofficial">Ανεπίσημη</string>
<string name="unofficial_extension_message">Η επέκταση αυτή δεν προέρχεται από την επίσημη αποχώρηση.</string>
<string name="download_cache_renew_interval">Χρονικό διάστημα ανανέωσης προσωρινής μνήμης</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Modo de selección masiva</string>
<string name="action_faq_and_guides">Preguntas frecuentes y guías</string>
<string name="pref_details_page_theme">Página de detalles</string>
<string name="pref_details_page_theme_cover_based">Tema de página de detalles basado en portada</string>
<string name="pref_theme_cover_based">Tema basado en portada</string>
<string name="pref_theme_cover_based_style">Estilo del tema</string>
<string name="pref_theme_cover_based_animate">Animar tema</string>
<string name="ext_unofficial">No oficial</string>
<string name="unofficial_extension_message">Esta extensión no es del repositorio oficial.</string>
<string name="download_cache_renew_interval">Descargar el intervalo de renovación de caché</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk valintatila</string>
<string name="action_faq_and_guides">UKK ja oppaat</string>
<string name="pref_details_page_theme">Yksityiskohdat sivu</string>
<string name="pref_details_page_theme_cover_based">Yksityiskohtaiset tiedot sivun teema kannen perusteella</string>
<string name="pref_theme_cover_based">Teema perustuu kanteen</string>
<string name="pref_theme_cover_based_style">Teeman tyyli</string>
<string name="pref_theme_cover_based_animate">Teema animoitu</string>
<string name="ext_unofficial">Epävirallinen</string>
<string name="unofficial_extension_message">Tämä laajennus ei ole virallisesta reposta.</string>
<string name="download_cache_renew_interval">Lataa välimuistin uusimisväli</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Mode de sélection en bloc</string>
<string name="action_faq_and_guides">FAQ et tutoriels</string>
<string name="pref_details_page_theme">Page de détails</string>
<string name="pref_details_page_theme_cover_based">Thème de la page de détails basé sur la couverture</string>
<string name="pref_theme_cover_based">Thème basé sur la couverture</string>
<string name="pref_theme_cover_based_style">Style du thème</string>
<string name="pref_theme_cover_based_animate">Animation du thème</string>
<string name="ext_unofficial">Non officiel</string>
<string name="unofficial_extension_message">Cette extension ne provient pas du dépôt officiel.</string>
<string name="download_cache_renew_interval">Intervalle de renouvellement du cache de téléchargement</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Modalità selezione Bulk</string>
<string name="action_faq_and_guides">FAQ e guide</string>
<string name="pref_details_page_theme">Pagina dettagli</string>
<string name="pref_details_page_theme_cover_based">Dettagli tema pagina basato sulla copertina</string>
<string name="pref_theme_cover_based">Tema basato sulla copertina</string>
<string name="pref_theme_cover_based_style">Stile tema</string>
<string name="pref_theme_cover_based_animate">Animazione tema</string>
<string name="ext_unofficial">Non Ufficiale</string>
<string name="unofficial_extension_message">Questa estensione non proviene dal repo ufficiale.</string>
<string name="download_cache_renew_interval">Intervallo di rinnovo download cache</string>

View file

@ -5,10 +5,9 @@
<string name="action_bulk_select">一括選択モード</string>
<string name="action_faq_and_guides">FAQ とガイド</string>
<string name="pref_details_page_theme">詳細ページ</string>
<string name="pref_details_page_theme_cover_based">カバーに基づく詳細ページのテーマ</string>
<string name="pref_theme_cover_based">カバーに基づくテーマ</string>
<string name="pref_theme_cover_based_style">テーマのスタイル</string>
<string name="pref_theme_cover_based_animate">テーマのアニメーション</string>
<string name="ext_unofficial">&lt;unk&gt;</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">この拡張機能は公式リポジトリからのものではありません。</string>
<string name="download_cache_renew_interval">キャッシュの更新間隔をダウンロード</string>
<string name="download_cache_renew_interval_manual">マニュアル</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selectiemodus</string>
<string name="action_faq_and_guides">FAQ en handleidingen</string>
<string name="pref_details_page_theme">Details pagina</string>
<string name="pref_details_page_theme_cover_based">Details pagina thema gebaseerd op cover</string>
<string name="pref_theme_cover_based">Thema gebaseerd op cover</string>
<string name="pref_theme_cover_based_style">Thema stijl</string>
<string name="pref_theme_cover_based_animate">Thema animeren</string>
<string name="ext_unofficial">Onofficiële</string>
<string name="unofficial_extension_message">Deze uitbreiding komt niet uit de officiële repo.</string>
<string name="download_cache_renew_interval">Download de cache vernieuwingsinterval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Tryb masowego zaznaczenia</string>
<string name="action_faq_and_guides">FAQ i przewodniki</string>
<string name="pref_details_page_theme">Strona szczegółów</string>
<string name="pref_details_page_theme_cover_based">Szczegółowy motyw strony oparty na okładce</string>
<string name="pref_theme_cover_based">Motyw oparty na okładce</string>
<string name="pref_theme_cover_based_style">Styl motywu</string>
<string name="pref_theme_cover_based_animate">Animacja motywu</string>
<string name="ext_unofficial">Nieoficjalne</string>
<string name="unofficial_extension_message">To rozszerzenie nie pochodzi z oficjalnego repozytorium.</string>
<string name="download_cache_renew_interval">Częstotliwość odnawiania pamięci podręcznej pobierania</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Modo de seleção em massa</string>
<string name="action_faq_and_guides">FAQ e Guias</string>
<string name="pref_details_page_theme">Página de detalhes</string>
<string name="pref_details_page_theme_cover_based">Detalhe o tema da página baseado na capa</string>
<string name="pref_theme_cover_based">Tema baseado na capa</string>
<string name="pref_theme_cover_based_style">Estilo do tema</string>
<string name="pref_theme_cover_based_animate">Animação do tema</string>
<string name="ext_unofficial">Não-oficial</string>
<string name="unofficial_extension_message">Essa extensão não é do repositório oficial.</string>
<string name="download_cache_renew_interval">Baixar intervalo de renovação do cache</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Modo de seleção em massa</string>
<string name="action_faq_and_guides">FAQ e Guias</string>
<string name="pref_details_page_theme">Página de detalhes</string>
<string name="pref_details_page_theme_cover_based">Detalhe o tema da página baseado na capa</string>
<string name="pref_theme_cover_based">Tema baseado na capa</string>
<string name="pref_theme_cover_based_style">Estilo do tema</string>
<string name="pref_theme_cover_based_animate">Animação do tema</string>
<string name="ext_unofficial">Não-oficial</string>
<string name="unofficial_extension_message">Essa extensão não é do repositório oficial.</string>
<string name="download_cache_renew_interval">Baixar intervalo de renovação do cache</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Mod selecţie în masă</string>
<string name="action_faq_and_guides">Întrebări frecvente și ghiduri</string>
<string name="pref_details_page_theme">Pagina de detalii</string>
<string name="pref_details_page_theme_cover_based">Tema paginii cu detalii bazate pe copertă</string>
<string name="pref_theme_cover_based">Temă bazată pe copertă</string>
<string name="pref_theme_cover_based_style">Stil temă</string>
<string name="pref_theme_cover_based_animate">Animare temă</string>
<string name="ext_unofficial">Neoficial.</string>
<string name="unofficial_extension_message">Această extensie nu este din repo-ul oficial.</string>
<string name="download_cache_renew_interval">Interval reînnoire descărcare geocutie</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Режим массового выбора</string>
<string name="action_faq_and_guides">FAQ и руководства</string>
<string name="pref_details_page_theme">Детали</string>
<string name="pref_details_page_theme_cover_based">Детали темы на основе обложки</string>
<string name="pref_theme_cover_based">Тема на основе обложки</string>
<string name="pref_theme_cover_based_style">Стиль темы</string>
<string name="pref_theme_cover_based_animate">Тема анимации</string>
<string name="ext_unofficial">Неофициальный</string>
<string name="unofficial_extension_message">Это расширение не из официального репозитория.</string>
<string name="download_cache_renew_interval">Загрузить интервал обновления кэша</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk markeringsläge</string>
<string name="action_faq_and_guides">Vanliga frågor och guider</string>
<string name="pref_details_page_theme">Detaljer sida</string>
<string name="pref_details_page_theme_cover_based">Detaljer sida tema baserat på omslag</string>
<string name="pref_theme_cover_based">Tema baserat på omslag</string>
<string name="pref_theme_cover_based_style">Tema stil</string>
<string name="pref_theme_cover_based_animate">Tema animerat</string>
<string name="ext_unofficial">Inofficiellt</string>
<string name="unofficial_extension_message">Denna förlängning kommer inte från den officiella rapo.</string>
<string name="download_cache_renew_interval">Hämta cache förnyelseintervall</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Масовий режим вибору</string>
<string name="action_faq_and_guides">ЧаПи та керівництва</string>
<string name="pref_details_page_theme">Сторінка подробиць</string>
<string name="pref_details_page_theme_cover_based">Тема сторінки подробиць на основі обкладинки</string>
<string name="pref_theme_cover_based">Тему на основі обкладинки</string>
<string name="pref_theme_cover_based_style">Стиль теми</string>
<string name="pref_theme_cover_based_animate">Анімація теми</string>
<string name="ext_unofficial">Неофіційна</string>
<string name="unofficial_extension_message">Це розширення не з офіційного репозиторію.</string>
<string name="download_cache_renew_interval">Завантажити інтервал оновлення кешу</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</string>
<string name="pref_details_page_theme">Details page</string>
<string name="pref_details_page_theme_cover_based">Details page theme based on cover</string>
<string name="pref_theme_cover_based">Theme based on cover</string>
<string name="pref_theme_cover_based_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</string>
<string name="download_cache_renew_interval">Download cache renew interval</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">批量选择模式</string>
<string name="action_faq_and_guides">常见问题和指南</string>
<string name="pref_details_page_theme">详细信息页面</string>
<string name="pref_details_page_theme_cover_based">基于封面的详细页面主题</string>
<string name="pref_theme_cover_based">基于封面的主题</string>
<string name="pref_theme_cover_based_style">主题样式</string>
<string name="pref_theme_cover_based_animate">主题动画</string>
<string name="ext_unofficial">非官方的</string>
<string name="unofficial_extension_message">这个扩展不是官方仓库的扩展。</string>
<string name="download_cache_renew_interval">下载缓存续间隔</string>

View file

@ -5,9 +5,8 @@
<string name="action_bulk_select">批量選擇模式</string>
<string name="action_faq_and_guides">常見問題和指南</string>
<string name="pref_details_page_theme">詳情頁面</string>
<string name="pref_details_page_theme_cover_based">基於封面的詳細資訊頁面主題</string>
<string name="pref_theme_cover_based">基於封面的主題</string>
<string name="pref_theme_cover_based_style">主題風格</string>
<string name="pref_theme_cover_based_animate">主題動畫</string>
<string name="ext_unofficial">非官方版本</string>
<string name="unofficial_extension_message">此擴充功能不是來自官方儲存庫</string>
<string name="download_cache_renew_interval">下載快取更新間隔</string>