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 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 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 relativeTime() = preferenceStore.getBoolean("relative_time_v2", true)
fun dateFormat() = preferenceStore.getString("app_date_format", "") 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.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
@ -48,10 +49,15 @@ fun HistoryItem(
.padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
val mangaCover = history.coverData
val bgColor = mangaCover.dominantCoverColors?.first?.let { Color(it) }
val onBgColor = mangaCover.dominantCoverColors?.second
MangaCover.Book( MangaCover.Book(
modifier = Modifier.fillMaxHeight(), modifier = Modifier.fillMaxHeight(),
data = history.coverData, data = mangaCover,
onClick = onClickCover, onClick = onClickCover,
bgColor = bgColor,
tint = onBgColor,
) )
Column( Column(
modifier = Modifier modifier = Modifier

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,11 +3,16 @@ package eu.kanade.presentation.manga.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding 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.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -25,7 +30,7 @@ fun ChapterHeader(
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Column( Row(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.clickable( .clickable(
@ -33,19 +38,30 @@ fun ChapterHeader(
onClick = onClick, onClick = onClick,
) )
.padding(horizontal = 16.dp, vertical = 4.dp), .padding(horizontal = 16.dp, vertical = 4.dp),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) { ) {
Text( Column(
text = if (chapterCount == null) { verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
stringResource(MR.strings.chapters) ) {
} else { Text(
pluralStringResource(MR.plurals.manga_num_chapters, count = chapterCount, chapterCount) text = if (chapterCount == null) {
}, stringResource(MR.strings.chapters)
style = MaterialTheme.typography.titleMedium, } else {
color = MaterialTheme.colorScheme.onBackground, 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.animatedVectorResource
import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter
import androidx.compose.animation.graphics.vector.AnimatedImageVector import androidx.compose.animation.graphics.vector.AnimatedImageVector
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -16,6 +17,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
@ -23,6 +25,9 @@ import coil3.compose.AsyncImage
import eu.kanade.presentation.util.rememberResourceBitmapPainter import eu.kanade.presentation.util.rememberResourceBitmapPainter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import kotlinx.coroutines.delay 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) { enum class MangaCover(val ratio: Float) {
Square(1f / 1f), Square(1f / 1f),
@ -36,8 +41,10 @@ enum class MangaCover(val ratio: Float) {
contentDescription: String = "", contentDescription: String = "",
shape: Shape = MaterialTheme.shapes.extraSmall, shape: Shape = MaterialTheme.shapes.extraSmall,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
@ColorInt tint: Int = CoverPlaceholderColor,
alpha: Float = 1f, alpha: Float = 1f,
bgColor: Color? = null,
@ColorInt tint: Int? = null,
onCoverLoaded: ((DomainMangaCover) -> Unit)? = null,
) { ) {
val animatedImageVector = AnimatedImageVector.animatedVectorResource(R.drawable.anim_waiting) val animatedImageVector = AnimatedImageVector.animatedVectorResource(R.drawable.anim_waiting)
var atEnd by remember { mutableStateOf(false) } var atEnd by remember { mutableStateOf(false) }
@ -60,12 +67,21 @@ enum class MangaCover(val ratio: Float) {
placeholder = rememberAnimatedVectorPainter(animatedImageVector = animatedImageVector, atEnd = atEnd), placeholder = rememberAnimatedVectorPainter(animatedImageVector = animatedImageVector, atEnd = atEnd),
error = rememberResourceBitmapPainter(id = R.drawable.cover_error, tint), error = rememberResourceBitmapPainter(id = R.drawable.cover_error, tint),
fallback = 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, contentDescription = contentDescription,
modifier = modifier modifier = modifier
.aspectRatio(ratio) .aspectRatio(ratio)
.clip(shape) .clip(shape)
.alpha(if (succeed) alpha else 1f) .alpha(if (succeed) alpha else 1f)
.background(bgColor ?: Color.Transparent)
.then( .then(
if (onClick != null) { if (onClick != null) {
Modifier.clickable( Modifier.clickable(
@ -81,6 +97,4 @@ enum class MangaCover(val ratio: Float) {
} }
} }
@ColorInt
internal val CoverPlaceholderColor = 0x7F888888
const val LoadingAnimatedDuration = 600L 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.filled.Warning
import androidx.compose.material.icons.outlined.AttachMoney import androidx.compose.material.icons.outlined.AttachMoney
import androidx.compose.material.icons.outlined.Block 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.Close
import androidx.compose.material.icons.outlined.Done import androidx.compose.material.icons.outlined.Done
import androidx.compose.material.icons.outlined.DoneAll 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.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.materialkolor.ktx.blend import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.DropdownMenu import eu.kanade.presentation.components.DropdownMenu
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.model.SManga 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.i18n.stringResource
import tachiyomi.presentation.core.util.clickableNoIndication import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.secondaryItemAlpha import tachiyomi.presentation.core.util.secondaryItemAlpha
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
import kotlin.math.roundToInt import kotlin.math.roundToInt
import tachiyomi.domain.manga.model.MangaCover as DomainMangaCover
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE)) private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
@ -110,6 +112,7 @@ fun MangaInfoBox(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
// KMK --> // KMK -->
onSourceClick: () -> Unit, onSourceClick: () -> Unit,
onCoverLoaded: (DomainMangaCover) -> Unit,
// KMK <-- // KMK <--
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
@ -127,11 +130,14 @@ fun MangaInfoBox(
.drawWithContent { .drawWithContent {
drawContent() drawContent()
drawRect( drawRect(
brush = Brush.verticalGradient(colors = backdropGradientColors), brush = Brush.verticalGradient(
colors = backdropGradientColors,
startY = size.height / 2,
),
) )
} }
.background(MaterialTheme.colorScheme.inversePrimary.copy(alpha = 0.2f)) .background(MaterialTheme.colorScheme.surfaceTint.copy(alpha = 0.4f))
.blur(4.dp) .blur(7.dp)
.alpha(0.2f), .alpha(0.2f),
) )
@ -151,6 +157,7 @@ fun MangaInfoBox(
isStubSource = isStubSource, isStubSource = isStubSource,
// KMK --> // KMK -->
onSourceClick = onSourceClick, onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
// KMK <-- // KMK <--
) )
} else { } else {
@ -167,6 +174,7 @@ fun MangaInfoBox(
isStubSource = isStubSource, isStubSource = isStubSource,
// KMK --> // KMK -->
onSourceClick = onSourceClick, onSourceClick = onSourceClick,
onCoverLoaded = onCoverLoaded,
// KMK <-- // KMK <--
) )
} }
@ -226,7 +234,11 @@ fun MangaActionRow(
) )
}, },
icon = Icons.Default.HourglassEmpty, 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() }, onClick = { onEditIntervalClicked?.invoke() },
) )
MangaActionButton( MangaActionButton(
@ -274,6 +286,8 @@ fun ExpandableMangaDescription(
// SY <-- // SY <--
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val uiPreferences = Injekt.get<UiPreferences>()
val pureDarkMode = uiPreferences.themeDarkAmoled().get()
Column(modifier = modifier) { Column(modifier = modifier) {
val (expanded, onExpanded) = rememberSaveable { val (expanded, onExpanded) = rememberSaveable {
mutableStateOf(defaultExpandState) mutableStateOf(defaultExpandState)
@ -341,6 +355,7 @@ fun ExpandableMangaDescription(
tagSelected = it tagSelected = it
showMenu = true showMenu = true
}, },
pureDarkMode = pureDarkMode,
) )
} else { } else {
// SY <-- // SY <--
@ -356,6 +371,7 @@ fun ExpandableMangaDescription(
tagSelected = it tagSelected = it
showMenu = true showMenu = true
}, },
pureDarkMode = pureDarkMode,
) )
} }
} }
@ -373,6 +389,7 @@ fun ExpandableMangaDescription(
tagSelected = it tagSelected = it
showMenu = true showMenu = true
}, },
pureDarkMode = pureDarkMode,
) )
} }
} }
@ -396,6 +413,7 @@ private fun MangaAndSourceTitlesLarge(
isStubSource: Boolean, isStubSource: Boolean,
// KMK --> // KMK -->
onSourceClick: () -> Unit, onSourceClick: () -> Unit,
onCoverLoaded: (DomainMangaCover) -> Unit,
// KMK <-- // KMK <--
) { ) {
Column( Column(
@ -409,6 +427,7 @@ private fun MangaAndSourceTitlesLarge(
data = coverDataProvider(), data = coverDataProvider(),
contentDescription = stringResource(MR.strings.manga_cover), contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick, onClick = onCoverClick,
onCoverLoaded = { mangaCover -> onCoverLoaded(mangaCover) },
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
MangaContentInfo( MangaContentInfo(
@ -441,6 +460,7 @@ private fun MangaAndSourceTitlesSmall(
isStubSource: Boolean, isStubSource: Boolean,
// KMK --> // KMK -->
onSourceClick: () -> Unit, onSourceClick: () -> Unit,
onCoverLoaded: (DomainMangaCover) -> Unit,
// KMK <-- // KMK <--
) { ) {
Row( Row(
@ -457,6 +477,7 @@ private fun MangaAndSourceTitlesSmall(
data = coverDataProvider(), data = coverDataProvider(),
contentDescription = stringResource(MR.strings.manga_cover), contentDescription = stringResource(MR.strings.manga_cover),
onClick = onCoverClick, onClick = onCoverClick,
onCoverLoaded = { mangaCover -> onCoverLoaded(mangaCover) },
) )
Column( Column(
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
@ -674,7 +695,7 @@ private fun MangaSummary(
contentDescription = stringResource( contentDescription = stringResource(
if (expanded) MR.strings.manga_info_collapse else MR.strings.manga_info_expand, 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())), 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.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import dev.icerock.moko.graphics.BuildConfig
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.components.AppBarActions import eu.kanade.presentation.components.AppBarActions
import eu.kanade.presentation.components.DownloadDropdownMenu import eu.kanade.presentation.components.DownloadDropdownMenu
@ -59,6 +60,7 @@ fun MangaToolbar(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
backgroundAlphaProvider: () -> Float = titleAlphaProvider, backgroundAlphaProvider: () -> Float = titleAlphaProvider,
onPaletteScreenClick: () -> Unit,
) { ) {
Column( Column(
modifier = modifier, modifier = modifier,
@ -191,6 +193,14 @@ fun MangaToolbar(
} }
// SY <-- // SY <--
// KMK --> // KMK -->
if (BuildConfig.DEBUG) {
add(
AppBar.OverflowAction(
title = "Colors Palette",
onClick = onPaletteScreenClick,
),
)
}
// add( // add(
// AppBar.OverflowAction( // AppBar.OverflowAction(
// title = stringResource(MR.strings.pref_invalidate_download_cache), // title = stringResource(MR.strings.pref_invalidate_download_cache),
@ -204,7 +214,6 @@ fun MangaToolbar(
// ), // ),
// ) // )
// KMK <-- // KMK <--
} }
.build(), .build(),
) )

View file

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

View file

@ -112,14 +112,14 @@ object SettingsAppearanceScreen : SearchableSettings {
): Preference.PreferenceGroup { ): Preference.PreferenceGroup {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val detailsPageThemeCoverBased by remember { val detailsPageThemeCoverBased by remember {
Injekt.get<UiPreferences>().detailsPageThemeCoverBased().asState(scope) Injekt.get<UiPreferences>().themeCoverBased().asState(scope)
} }
return Preference.PreferenceGroup( return Preference.PreferenceGroup(
title = stringResource(KMR.strings.pref_details_page_theme), title = stringResource(KMR.strings.pref_details_page_theme),
preferenceItems = persistentListOf( preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
pref = uiPreferences.detailsPageThemeCoverBased(), pref = uiPreferences.themeCoverBased(),
title = stringResource(KMR.strings.pref_details_page_theme_cover_based), title = stringResource(KMR.strings.pref_theme_cover_based),
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = uiPreferences.themeCoverBasedStyle(), pref = uiPreferences.themeCoverBasedStyle(),
@ -129,11 +129,6 @@ object SettingsAppearanceScreen : SearchableSettings {
.associateWith { it.name } .associateWith { it.name }
.toImmutableMap(), .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.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.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
@ -168,12 +169,17 @@ private fun UpdatesUiItem(
.padding(horizontal = MaterialTheme.padding.medium), .padding(horizontal = MaterialTheme.padding.medium),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
val mangaCover = update.coverData
val bgColor = mangaCover.dominantCoverColors?.first?.let { Color(it) }
val onBgColor = mangaCover.dominantCoverColors?.second
MangaCover.Square( MangaCover.Square(
modifier = Modifier modifier = Modifier
.padding(vertical = 6.dp) .padding(vertical = 6.dp)
.fillMaxHeight(), .fillMaxHeight(),
data = update.coverData, data = mangaCover,
onClick = onClickCover, onClick = onClickCover,
bgColor = bgColor,
tint = onBgColor,
) )
Column( 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.BufferedSourceFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer 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.MangaKeyer
import eu.kanade.tachiyomi.data.coil.PagePreviewFetcher import eu.kanade.tachiyomi.data.coil.PagePreviewFetcher
import eu.kanade.tachiyomi.data.coil.PagePreviewKeyer 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.NetworkHelper
import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate 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.DeviceUtil
import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.system.animatorDurationScale

View file

@ -68,8 +68,8 @@ class MangaCoverFetcher(
private val imageLoader: ImageLoader, private val imageLoader: ImageLoader,
) : Fetcher { ) : Fetcher {
private val uiPreferences: UiPreferences = Injekt.get() private val fileScope by lazy { CoroutineScope(Job() + Dispatchers.IO) }
private val fileScope = CoroutineScope(Job() + Dispatchers.IO) private val uiPreferences = Injekt.get<UiPreferences>()
private val diskCacheKey: String private val diskCacheKey: String
get() = diskCacheKeyLazy.value 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 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 * @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] * 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. * @param force if true 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.
*/ */
private fun setRatioAndColorsInScope( private fun setRatioAndColorsInScope(
mangaCover: MangaCover, mangaCover: MangaCover,
bufferedSource: BufferedSource? = null, bufferedSource: BufferedSource? = null,
ogFile: File? = null, ogFile: File? = null,
force: Boolean = true onlyFavorite: Boolean = !uiPreferences.themeCoverBased().get(),
force: Boolean = false,
) { ) {
if (!uiPreferences.detailsPageThemeCoverBased().get()) return
fileScope.launch { 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 android.graphics.BitmapFactory
import androidx.palette.graphics.Palette import androidx.palette.graphics.Palette
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.model.MangaCover 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 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 * @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] * 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. * @param force if true 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.
* *
* @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( fun setRatioAndColors(
mangaCover: MangaCover, mangaCover: MangaCover,
bufferedSource: BufferedSource? = null, bufferedSource: BufferedSource? = null,
ogFile: File? = null, ogFile: File? = null,
force: Boolean = true, onlyDominantColor: Boolean = true,
force: Boolean = false,
) { ) {
if (!mangaCover.isMangaFavorite) { if (!mangaCover.isMangaFavorite) {
mangaCover.remove() 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 val file = ogFile
?: coverCache.getCustomCoverFile(mangaCover.mangaId).takeIf { it.exists() } ?: coverCache.getCustomCoverFile(mangaCover.mangaId).takeIf { it.exists() }
?: coverCache.getCoverFile(mangaCover.url) ?: 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 { val bitmap = when {
bufferedSource != null -> BitmapFactory.decodeStream(bufferedSource.inputStream(), null, options) bufferedSource != null -> BitmapFactory.decodeStream(bufferedSource.inputStream(), null, options)
// if the file exists and the there was still an error then the file is corrupted // 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.TextView import android.widget.TextView
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
@ -82,7 +83,6 @@ fun EditMangaDialog(
textHighlightColor = MaterialTheme.colorScheme.outline.toArgb(), textHighlightColor = MaterialTheme.colorScheme.outline.toArgb(),
iconColor = MaterialTheme.colorScheme.primary.toArgb(), iconColor = MaterialTheme.colorScheme.primary.toArgb(),
tagColor = MaterialTheme.colorScheme.outlineVariant.toArgb(), tagColor = MaterialTheme.colorScheme.outlineVariant.toArgb(),
tagFocusColor = MaterialTheme.colorScheme.outline.toArgb(),
tagTextColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb(), tagTextColor = MaterialTheme.colorScheme.onSurfaceVariant.toArgb(),
btnTextColor = MaterialTheme.colorScheme.onPrimary.toArgb(), btnTextColor = MaterialTheme.colorScheme.onPrimary.toArgb(),
btnBgColor = MaterialTheme.colorScheme.surfaceTint.toArgb(), btnBgColor = MaterialTheme.colorScheme.surfaceTint.toArgb(),
@ -159,7 +159,6 @@ class EditMangaDialogColors(
@ColorInt val textHighlightColor: Int, @ColorInt val textHighlightColor: Int,
@ColorInt val iconColor: Int, @ColorInt val iconColor: Int,
@ColorInt val tagColor: Int, @ColorInt val tagColor: Int,
@ColorInt val tagFocusColor: Int,
@ColorInt val tagTextColor: Int, @ColorInt val tagTextColor: Int,
@ColorInt val btnTextColor: Int, @ColorInt val btnTextColor: Int,
@ColorInt val btnBgColor: 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 // Set Spinner's dropdown caret color
binding.status.backgroundTintList = ColorStateList.valueOf(colors.iconColor) binding.status.backgroundTintList = ColorStateList.valueOf(colors.iconColor)
@ -346,18 +353,7 @@ private fun ChipGroup.setChips(
) { ) {
removeAllViews() removeAllViews()
val colorStateList = ColorStateList( val colorStateList = ColorStateList.valueOf(colors.tagColor)
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,
),
)
items.asSequence().map { item -> items.asSequence().map { item ->
Chip(context).apply { Chip(context).apply {

View file

@ -29,7 +29,6 @@ import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.materialkolor.DynamicMaterialTheme import com.materialkolor.DynamicMaterialTheme
import com.materialkolor.rememberDynamicColorScheme
import eu.kanade.domain.manga.model.hasCustomCover import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.manga.model.toSManga import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
@ -140,24 +139,17 @@ class MangaScreen(
val seedColorState = rememberUpdatedState(newValue = successState.seedColor) val seedColorState = rememberUpdatedState(newValue = successState.seedColor)
val uiPreferences = remember { Injekt.get<UiPreferences>() } val uiPreferences = remember { Injekt.get<UiPreferences>() }
if (uiPreferences.themeCoverBasedAnimate().get()) { if (uiPreferences.themeCoverBased().get()) {
DynamicMaterialTheme( DynamicMaterialTheme(
seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary, seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary,
useDarkTheme = isSystemInDarkTheme(), useDarkTheme = isSystemInDarkTheme(),
withAmoled = uiPreferences.themeDarkAmoled().get(),
style = uiPreferences.themeCoverBasedStyle().get(), style = uiPreferences.themeCoverBasedStyle().get(),
animate = uiPreferences.themeCoverBasedAnimate().get(), animate = true,
content = { MaterialThemeContent(context, screenModel, successState) }, content = { MaterialThemeContent(context, screenModel, successState) },
) )
} else { } else {
val colorScheme = rememberDynamicColorScheme( MaterialThemeContent(context, screenModel, successState)
seedColor = seedColorState.value ?: MaterialTheme.colorScheme.primary,
isDark = isSystemInDarkTheme(),
style = uiPreferences.themeCoverBasedStyle().get(),
)
MaterialTheme(
colorScheme = colorScheme,
content = { MaterialThemeContent(context, screenModel, successState) },
)
} }
} }
@ -262,7 +254,7 @@ class MangaScreen(
previewsRowCount = successState.previewsRowCount, previewsRowCount = successState.previewsRowCount,
// SY --> // SY -->
onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite }, onMigrateClicked = { migrateManga(navigator, screenModel.manga!!) }.takeIf { successState.manga.favorite },
onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga) }, onMetadataViewerClicked = { openMetadataViewer(navigator, successState.manga, successState.seedColor) },
onEditInfoClicked = screenModel::showEditMangaInfoDialog, onEditInfoClicked = screenModel::showEditMangaInfoDialog,
onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) }, onRecommendClicked = { openRecommends(context, navigator, screenModel.source?.getMainSource(), successState.manga) },
onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog, onMergedSettingsClicked = screenModel::showEditMergedSettingsDialog,
@ -313,6 +305,8 @@ class MangaScreen(
navigator.push(ExtensionsScreen(searchSource = successState.source.name)) navigator.push(ExtensionsScreen(searchSource = successState.source.name))
} }
}, },
onCoverLoaded = screenModel::setPaletteColor,
onPaletteScreenClick = { navigator.push(PaletteScreen(successState.seedColor)) }
// KMK <-- // 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.StateScreenModel
import cafe.adriel.voyager.core.model.screenModelScope import cafe.adriel.voyager.core.model.screenModelScope
import coil3.Image import coil3.Image
import coil3.executeBlocking
import coil3.imageLoader import coil3.imageLoader
import coil3.request.ImageRequest import coil3.request.ImageRequest
import coil3.request.allowHardware 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.DownloadAction
import eu.kanade.presentation.manga.components.ChapterDownloadAction import eu.kanade.presentation.manga.components.ChapterDownloadAction
import eu.kanade.presentation.util.formattedMessage 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.DownloadCache
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download 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.manga.track.TrackItem
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.util.chapter.getNextUnread 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.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import eu.kanade.tachiyomi.util.system.getBitmapOrNull 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. * 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 if (model is ImageRequest && model.defined.sizeResolver != null) return
val imageRequestBuilder = if (model is ImageRequest) { val imageRequestBuilder = if (model is ImageRequest) {
@ -494,58 +493,41 @@ class MangaScreenModel(
val generatePalette: (Image) -> Unit = { image -> val generatePalette: (Image) -> Unit = { image ->
val bitmap = image.asDrawable(context.resources).getBitmapOrNull() val bitmap = image.asDrawable(context.resources).getBitmapOrNull()
if (bitmap != null) { if (bitmap != null) {
Palette.from(bitmap).generate { palette -> Palette.from(bitmap).generate {
screenModelScope.launchUI { screenModelScope.launchUI {
palette?.getBestColor()?.let { vibrantColor -> if (it == null) return@launchUI
when (model) { val mangaCover = when (model) {
is Manga -> model.asMangaCover().vibrantCoverColor = vibrantColor is Manga -> model.asMangaCover()
is MangaCover -> model.vibrantCoverColor = vibrantColor 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) { context.imageLoader.enqueue(
ImageRequestType.Enqueue -> { imageRequestBuilder
context.imageLoader.enqueue( .target(
imageRequestBuilder onSuccess = generatePalette,
.target( onError = {
onSuccess = generatePalette, // TODO: handle error
onError = { // val file = coverCache.getCoverFile(manga!!)
// TODO: handle error // if (file.exists()) {
// val file = coverCache.getCoverFile(manga!!) // file.delete()
// if (file.exists()) { // setPaletteColor()
// file.delete() // }
// setPaletteColor() },
// }
},
)
.build()
) )
} .build()
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)
}
}
}
}
} }
// KMK --> // KMK -->
@ -2079,10 +2061,3 @@ sealed interface RelatedManga {
} }
} }
// KMK <-- // 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.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import com.materialkolor.DynamicMaterialTheme import com.materialkolor.DynamicMaterialTheme
import com.materialkolor.PaletteStyle import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.util.system.copyToClipboard 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.screens.LoadingScreen
import tachiyomi.presentation.core.util.clickableNoIndication import tachiyomi.presentation.core.util.clickableNoIndication
import tachiyomi.presentation.core.util.plus import tachiyomi.presentation.core.util.plus
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MetadataViewScreen( class MetadataViewScreen(
private val mangaId: Long, private val mangaId: Long,
@ -107,12 +109,19 @@ class MetadataViewScreen(
} }
} }
DynamicMaterialTheme( val uiPreferences = remember { Injekt.get<UiPreferences>() }
seedColor = seedColor ?: MaterialTheme.colorScheme.primary,
useDarkTheme = isSystemInDarkTheme(), if (uiPreferences.themeCoverBased().get()) {
style = PaletteStyle.Vibrant, DynamicMaterialTheme(
animate = true, seedColor = seedColor ?: MaterialTheme.colorScheme.primary,
content = { content() } 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, search: (String) -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val textColor = MaterialTheme.colorScheme.onBackground.toArgb() val textColor = MaterialTheme.colorScheme.secondary.toArgb()
val iconColor = MaterialTheme.colorScheme.secondary.toArgb() val iconColor = MaterialTheme.colorScheme.primary.toArgb()
val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb() val ratingBarSecondaryColor = MaterialTheme.colorScheme.outlineVariant.toArgb()
AndroidView( AndroidView(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,6 +11,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -38,9 +39,14 @@ fun UpcomingItem(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.large), 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( MangaCover.Book(
modifier = Modifier.fillMaxHeight(), modifier = Modifier.fillMaxHeight(),
data = upcoming.asMangaCover(), data = mangaCover,
bgColor = bgColor,
tint = onBgColor,
) )
Text( Text(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),

View file

@ -35,9 +35,15 @@ class LibraryPreferences(
fun lastUpdatedTimestamp() = preferenceStore.getLong(Preference.appStateKey("library_update_last_timestamp"), 0L) fun lastUpdatedTimestamp() = preferenceStore.getLong(Preference.appStateKey("library_update_last_timestamp"), 0L)
fun autoUpdateInterval() = preferenceStore.getInt("pref_library_update_interval_key", 0) 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( fun autoUpdateDeviceRestrictions() = preferenceStore.getStringSet(
"library_update_restriction", "library_update_restriction",

View file

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

View file

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

View file

@ -9,9 +9,8 @@
<!-- Preferences --> <!-- Preferences -->
<!-- General section --> <!-- General section -->
<string name="pref_details_page_theme">Details page</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_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">Expand Related titles</string>
<string name="pref_expand_related_titles_summary">If turned off, related titles won\'t automatically load &amp; show expanded</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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Režim hromadného výběru</string>
<string name="action_faq_and_guides">FAQ a návody</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">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_style">Styl motivu</string>
<string name="pref_theme_cover_based_animate">Animace motivu</string>
<string name="ext_unofficial">Neoficiální</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="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> <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_bulk_select">Masse valg tilstand</string>
<string name="action_faq_and_guides">Ofte stillede spørgsmål og vejledninger</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">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_style">Tema stil</string>
<string name="pref_theme_cover_based_animate">Tema animere</string>
<string name="ext_unofficial">Uofficiel</string> <string name="ext_unofficial">Uofficiel</string>
<string name="unofficial_extension_message">Denne udvidelse er ikke fra den officielle repo.</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> <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_bulk_select">Massenauswahlmodus</string>
<string name="action_faq_and_guides">FAQ und Anleitungen</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">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_style">Design-Stil</string>
<string name="pref_theme_cover_based_animate">Theme animieren</string>
<string name="ext_unofficial">Ungültig</string> <string name="ext_unofficial">Ungültig</string>
<string name="unofficial_extension_message">Diese Erweiterung ist nicht vom offiziellen Repo.</string> <string name="unofficial_extension_message">Diese Erweiterung ist nicht vom offiziellen Repo.</string>
<string name="download_cache_renew_interval">Download-Cache Erneuerungsintervall</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_bulk_select">Λειτουργία μαζικής επιλογής</string>
<string name="action_faq_and_guides">Συχνές ερωτήσεις και οδηγοί</string> <string name="action_faq_and_guides">Συχνές ερωτήσεις και οδηγοί</string>
<string name="pref_details_page_theme">Σελίδα λεπτομερειών</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_style">Στυλ θέματος</string>
<string name="pref_theme_cover_based_animate">Εφέ κίνησης θέματος</string>
<string name="ext_unofficial">Ανεπίσημη</string> <string name="ext_unofficial">Ανεπίσημη</string>
<string name="unofficial_extension_message">Η επέκταση αυτή δεν προέρχεται από την επίσημη αποχώρηση.</string> <string name="unofficial_extension_message">Η επέκταση αυτή δεν προέρχεται από την επίσημη αποχώρηση.</string>
<string name="download_cache_renew_interval">Χρονικό διάστημα ανανέωσης προσωρινής μνήμης</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_bulk_select">Modo de selección masiva</string>
<string name="action_faq_and_guides">Preguntas frecuentes y guías</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">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_style">Estilo del tema</string>
<string name="pref_theme_cover_based_animate">Animar tema</string>
<string name="ext_unofficial">No oficial</string> <string name="ext_unofficial">No oficial</string>
<string name="unofficial_extension_message">Esta extensión no es del repositorio 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> <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_bulk_select">Bulk valintatila</string>
<string name="action_faq_and_guides">UKK ja oppaat</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">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_style">Teeman tyyli</string>
<string name="pref_theme_cover_based_animate">Teema animoitu</string>
<string name="ext_unofficial">Epävirallinen</string> <string name="ext_unofficial">Epävirallinen</string>
<string name="unofficial_extension_message">Tämä laajennus ei ole virallisesta reposta.</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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Mode de sélection en bloc</string>
<string name="action_faq_and_guides">FAQ et tutoriels</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">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_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="ext_unofficial">Non officiel</string>
<string name="unofficial_extension_message">Cette extension ne provient pas du dépôt 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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Modalità selezione Bulk</string>
<string name="action_faq_and_guides">FAQ e guide</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">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_style">Stile tema</string>
<string name="pref_theme_cover_based_animate">Animazione tema</string>
<string name="ext_unofficial">Non Ufficiale</string> <string name="ext_unofficial">Non Ufficiale</string>
<string name="unofficial_extension_message">Questa estensione non proviene dal repo 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> <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_bulk_select">一括選択モード</string>
<string name="action_faq_and_guides">FAQ とガイド</string> <string name="action_faq_and_guides">FAQ とガイド</string>
<string name="pref_details_page_theme">詳細ページ</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_style">テーマのスタイル</string>
<string name="pref_theme_cover_based_animate">テーマのアニメーション</string> <string name="ext_unofficial">Unofficial</string>
<string name="ext_unofficial">&lt;unk&gt;</string>
<string name="unofficial_extension_message">この拡張機能は公式リポジトリからのものではありません。</string> <string name="unofficial_extension_message">この拡張機能は公式リポジトリからのものではありません。</string>
<string name="download_cache_renew_interval">キャッシュの更新間隔をダウンロード</string> <string name="download_cache_renew_interval">キャッシュの更新間隔をダウンロード</string>
<string name="download_cache_renew_interval_manual">マニュアル</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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Bulk selectiemodus</string>
<string name="action_faq_and_guides">FAQ en handleidingen</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">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_style">Thema stijl</string>
<string name="pref_theme_cover_based_animate">Thema animeren</string>
<string name="ext_unofficial">Onofficiële</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="unofficial_extension_message">Deze uitbreiding komt niet uit de officiële repo.</string>
<string name="download_cache_renew_interval">Download de cache vernieuwingsinterval</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_bulk_select">Tryb masowego zaznaczenia</string>
<string name="action_faq_and_guides">FAQ i przewodniki</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">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_style">Styl motywu</string>
<string name="pref_theme_cover_based_animate">Animacja motywu</string>
<string name="ext_unofficial">Nieoficjalne</string> <string name="ext_unofficial">Nieoficjalne</string>
<string name="unofficial_extension_message">To rozszerzenie nie pochodzi z oficjalnego repozytorium.</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> <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_bulk_select">Modo de seleção em massa</string>
<string name="action_faq_and_guides">FAQ e Guias</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">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_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="ext_unofficial">Não-oficial</string>
<string name="unofficial_extension_message">Essa extensão não é do repositório 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> <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_bulk_select">Modo de seleção em massa</string>
<string name="action_faq_and_guides">FAQ e Guias</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">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_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="ext_unofficial">Não-oficial</string>
<string name="unofficial_extension_message">Essa extensão não é do repositório 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> <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_bulk_select">Mod selecţie în masă</string>
<string name="action_faq_and_guides">Întrebări frecvente și ghiduri</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">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_style">Stil temă</string>
<string name="pref_theme_cover_based_animate">Animare temă</string>
<string name="ext_unofficial">Neoficial.</string> <string name="ext_unofficial">Neoficial.</string>
<string name="unofficial_extension_message">Această extensie nu este din repo-ul oficial.</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> <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_bulk_select">Режим массового выбора</string>
<string name="action_faq_and_guides">FAQ и руководства</string> <string name="action_faq_and_guides">FAQ и руководства</string>
<string name="pref_details_page_theme">Детали</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_style">Стиль темы</string>
<string name="pref_theme_cover_based_animate">Тема анимации</string>
<string name="ext_unofficial">Неофициальный</string> <string name="ext_unofficial">Неофициальный</string>
<string name="unofficial_extension_message">Это расширение не из официального репозитория.</string> <string name="unofficial_extension_message">Это расширение не из официального репозитория.</string>
<string name="download_cache_renew_interval">Загрузить интервал обновления кэша</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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Bulk markeringsläge</string>
<string name="action_faq_and_guides">Vanliga frågor och guider</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">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_style">Tema stil</string>
<string name="pref_theme_cover_based_animate">Tema animerat</string>
<string name="ext_unofficial">Inofficiellt</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="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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">Масовий режим вибору</string>
<string name="action_faq_and_guides">ЧаПи та керівництва</string> <string name="action_faq_and_guides">ЧаПи та керівництва</string>
<string name="pref_details_page_theme">Сторінка подробиць</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_style">Стиль теми</string>
<string name="pref_theme_cover_based_animate">Анімація теми</string>
<string name="ext_unofficial">Неофіційна</string> <string name="ext_unofficial">Неофіційна</string>
<string name="unofficial_extension_message">Це розширення не з офіційного репозиторію.</string> <string name="unofficial_extension_message">Це розширення не з офіційного репозиторію.</string>
<string name="download_cache_renew_interval">Завантажити інтервал оновлення кешу</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_bulk_select">Bulk selection mode</string>
<string name="action_faq_and_guides">FAQ and Guides</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">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_style">Theme style</string>
<string name="pref_theme_cover_based_animate">Theme animate</string>
<string name="ext_unofficial">Unofficial</string> <string name="ext_unofficial">Unofficial</string>
<string name="unofficial_extension_message">This extension is not from the official repo.</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> <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_bulk_select">批量选择模式</string>
<string name="action_faq_and_guides">常见问题和指南</string> <string name="action_faq_and_guides">常见问题和指南</string>
<string name="pref_details_page_theme">详细信息页面</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_style">主题样式</string>
<string name="pref_theme_cover_based_animate">主题动画</string>
<string name="ext_unofficial">非官方的</string> <string name="ext_unofficial">非官方的</string>
<string name="unofficial_extension_message">这个扩展不是官方仓库的扩展。</string> <string name="unofficial_extension_message">这个扩展不是官方仓库的扩展。</string>
<string name="download_cache_renew_interval">下载缓存续间隔</string> <string name="download_cache_renew_interval">下载缓存续间隔</string>

View file

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