Last commit merged: 16cbcecd99
This commit is contained in:
LuftVerbot 2023-11-04 22:10:05 +01:00
parent 184804f62e
commit 59b4404c11
27 changed files with 442 additions and 539 deletions

13
.github/renovate.json vendored
View file

@ -1,13 +0,0 @@
{
"extends": [
"config:base"
],
"schedule": ["every sunday"],
"packageRules": [
{
"managers": ["maven"],
"packageNames": ["com.google.guava:guava"],
"versionScheme": "docker"
}
]
}

22
.github/renovate.json5 vendored Normal file
View file

@ -0,0 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"schedule": ["every sunday"],
"packageRules": [
{
"managers": ["maven"],
"packageNames": ["com.google.guava:guava"],
"versionScheme": "docker"
},
{
// Compiler plugins are tightly coupled to Kotlin version
"groupName": "Kotlin",
"matchPackagePrefixes": [
"androidx.compose.compiler",
"org.jetbrains.kotlin",
],
}
]
}

View file

@ -247,6 +247,7 @@ dependencies {
implementation(libs.bundles.voyager) implementation(libs.bundles.voyager)
implementation(libs.compose.materialmotion) implementation(libs.compose.materialmotion)
implementation(libs.compose.simpleicons) implementation(libs.compose.simpleicons)
implementation(libs.swipe)
// Logging // Logging
implementation(libs.logcat) implementation(libs.logcat)

View file

@ -100,8 +100,8 @@ fun AnimeScreen(
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
showNextEpisodeAirTime: Boolean, showNextEpisodeAirTime: Boolean,
alwaysUseExternalPlayer: Boolean, alwaysUseExternalPlayer: Boolean,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -162,8 +162,8 @@ fun AnimeScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
episodeSwipeEndAction = episodeSwipeEndAction,
episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeStartAction = episodeSwipeStartAction,
episodeSwipeEndAction = episodeSwipeEndAction,
showNextEpisodeAirTime = showNextEpisodeAirTime, showNextEpisodeAirTime = showNextEpisodeAirTime,
alwaysUseExternalPlayer = alwaysUseExternalPlayer, alwaysUseExternalPlayer = alwaysUseExternalPlayer,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
@ -200,8 +200,8 @@ fun AnimeScreen(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
episodeSwipeEndAction = episodeSwipeEndAction,
episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeStartAction = episodeSwipeStartAction,
episodeSwipeEndAction = episodeSwipeEndAction,
showNextEpisodeAirTime = showNextEpisodeAirTime, showNextEpisodeAirTime = showNextEpisodeAirTime,
alwaysUseExternalPlayer = alwaysUseExternalPlayer, alwaysUseExternalPlayer = alwaysUseExternalPlayer,
dateFormat = dateFormat, dateFormat = dateFormat,
@ -244,8 +244,8 @@ private fun AnimeScreenSmallImpl(
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
showNextEpisodeAirTime: Boolean, showNextEpisodeAirTime: Boolean,
alwaysUseExternalPlayer: Boolean, alwaysUseExternalPlayer: Boolean,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -490,8 +490,8 @@ private fun AnimeScreenSmallImpl(
episodes = episodes, episodes = episodes,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
episodeSwipeEndAction = episodeSwipeEndAction,
episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeStartAction = episodeSwipeStartAction,
episodeSwipeEndAction = episodeSwipeEndAction,
onEpisodeClicked = onEpisodeClicked, onEpisodeClicked = onEpisodeClicked,
onDownloadEpisode = onDownloadEpisode, onDownloadEpisode = onDownloadEpisode,
onEpisodeSelected = onEpisodeSelected, onEpisodeSelected = onEpisodeSelected,
@ -510,8 +510,8 @@ fun AnimeScreenLargeImpl(
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
showNextEpisodeAirTime: Boolean, showNextEpisodeAirTime: Boolean,
alwaysUseExternalPlayer: Boolean, alwaysUseExternalPlayer: Boolean,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
@ -747,8 +747,8 @@ fun AnimeScreenLargeImpl(
episodes = episodes, episodes = episodes,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
episodeSwipeEndAction = episodeSwipeEndAction,
episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeStartAction = episodeSwipeStartAction,
episodeSwipeEndAction = episodeSwipeEndAction,
onEpisodeClicked = onEpisodeClicked, onEpisodeClicked = onEpisodeClicked,
onDownloadEpisode = onDownloadEpisode, onDownloadEpisode = onDownloadEpisode,
onEpisodeSelected = onEpisodeSelected, onEpisodeSelected = onEpisodeSelected,
@ -818,8 +818,8 @@ private fun LazyListScope.sharedEpisodeItems(
episodes: List<EpisodeItem>, episodes: List<EpisodeItem>,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
onEpisodeClicked: (Episode, Boolean) -> Unit, onEpisodeClicked: (Episode, Boolean) -> Unit,
onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?, onDownloadEpisode: ((List<EpisodeItem>, EpisodeDownloadAction) -> Unit)?,
onEpisodeSelected: (EpisodeItem, Boolean, Boolean, Boolean) -> Unit, onEpisodeSelected: (EpisodeItem, Boolean, Boolean, Boolean) -> Unit,
@ -867,8 +867,8 @@ private fun LazyListScope.sharedEpisodeItems(
downloadIndicatorEnabled = episodes.fastAll { !it.selected }, downloadIndicatorEnabled = episodes.fastAll { !it.selected },
downloadStateProvider = { episodeItem.downloadState }, downloadStateProvider = { episodeItem.downloadState },
downloadProgressProvider = { episodeItem.downloadProgress }, downloadProgressProvider = { episodeItem.downloadProgress },
episodeSwipeEndAction = episodeSwipeEndAction,
episodeSwipeStartAction = episodeSwipeStartAction, episodeSwipeStartAction = episodeSwipeStartAction,
episodeSwipeEndAction = episodeSwipeEndAction,
onLongClick = { onLongClick = {
onEpisodeSelected(episodeItem, !episodeItem.selected, true, true) onEpisodeSelected(episodeItem, !episodeItem.selected, true, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)

View file

@ -1,21 +1,14 @@
package eu.kanade.presentation.entries.anime.components package eu.kanade.presentation.entries.anime.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissValue
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Circle import androidx.compose.material.icons.filled.Circle
@ -26,7 +19,6 @@ import androidx.compose.material.icons.outlined.Done
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FileDownloadOff import androidx.compose.material.icons.outlined.FileDownloadOff
import androidx.compose.material.icons.outlined.RemoveDone import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -38,14 +30,18 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
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.alpha
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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.LocalViewConfiguration import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -55,10 +51,13 @@ import androidx.compose.ui.unit.sp
import eu.kanade.presentation.entries.DotSeparatorText import eu.kanade.presentation.entries.DotSeparatorText
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload import eu.kanade.tachiyomi.data.download.anime.model.AnimeDownload
import me.saket.swipe.SwipeableActionsBox
import me.saket.swipe.rememberSwipeableActionsState
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
import kotlin.math.absoluteValue
@Composable @Composable
fun AnimeEpisodeListItem( fun AnimeEpisodeListItem(
@ -73,13 +72,19 @@ fun AnimeEpisodeListItem(
downloadIndicatorEnabled: Boolean, downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> AnimeDownload.State, downloadStateProvider: () -> AnimeDownload.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction, episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onDownloadClick: ((EpisodeDownloadAction) -> Unit)?, onDownloadClick: ((EpisodeDownloadAction) -> Unit)?,
onEpisodeSwipe: (LibraryPreferences.EpisodeSwipeAction) -> Unit, onEpisodeSwipe: (LibraryPreferences.EpisodeSwipeAction) -> Unit,
) { ) {
val haptic = LocalHapticFeedback.current
val density = LocalDensity.current
val textAlpha = if (seen) ReadItemAlpha else 1f
val textSubtitleAlpha = if (seen) ReadItemAlpha else SecondaryItemAlpha
// Increase touch slop of swipe action to reduce accidental trigger // Increase touch slop of swipe action to reduce accidental trigger
val configuration = LocalViewConfiguration.current val configuration = LocalViewConfiguration.current
CompositionLocalProvider( CompositionLocalProvider(
@ -87,110 +92,42 @@ fun AnimeEpisodeListItem(
override val touchSlop: Float = configuration.touchSlop * 3f override val touchSlop: Float = configuration.touchSlop * 3f
}, },
) { ) {
val textAlpha = if (seen) ReadItemAlpha else 1f val start = getSwipeAction(
val textSubtitleAlpha = if (seen) ReadItemAlpha else SecondaryItemAlpha action = episodeSwipeStartAction,
seen = seen,
val episodeSwipeStartEnabled = episodeSwipeStartAction != LibraryPreferences.EpisodeSwipeAction.Disabled bookmark = bookmark,
val episodeSwipeEndEnabled = episodeSwipeEndAction != LibraryPreferences.EpisodeSwipeAction.Disabled downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
val dismissState = rememberDismissState() onSwipe = { onEpisodeSwipe(episodeSwipeStartAction) },
val dismissDirections = remember { mutableSetOf<DismissDirection>() }
var lastDismissDirection: DismissDirection? by remember { mutableStateOf(null) }
if (lastDismissDirection == null) {
if (episodeSwipeStartEnabled) {
dismissDirections.add(DismissDirection.EndToStart)
}
if (episodeSwipeEndEnabled) {
dismissDirections.add(DismissDirection.StartToEnd)
}
}
val animateDismissContentAlpha by animateFloatAsState(
label = "animateDismissContentAlpha",
targetValue = if (lastDismissDirection != null) 1f else 0f,
animationSpec = tween(durationMillis = if (lastDismissDirection != null) 500 else 0),
finishedListener = {
lastDismissDirection = null
},
) )
val dismissContentAlpha = if (lastDismissDirection != null) animateDismissContentAlpha else 1f val end = getSwipeAction(
val backgroundColor = if (episodeSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) { action = episodeSwipeEndAction,
MaterialTheme.colorScheme.primary seen = seen,
} else if (episodeSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) { bookmark = bookmark,
MaterialTheme.colorScheme.primary downloadState = downloadStateProvider(),
} else { background = MaterialTheme.colorScheme.primaryContainer,
Color.Unspecified onSwipe = { onEpisodeSwipe(episodeSwipeEndAction) },
)
val swipeableActionsState = rememberSwipeableActionsState()
LaunchedEffect(Unit) {
// Haptic effect when swipe over threshold
val swipeActionThresholdPx = with(density) { swipeActionThreshold.toPx() }
snapshotFlow { swipeableActionsState.offset.value.absoluteValue > swipeActionThresholdPx }
.collect { if (it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) }
} }
LaunchedEffect(dismissState.currentValue) { SwipeableActionsBox(
when (dismissState.currentValue) { modifier = Modifier.clipToBounds(),
DismissValue.DismissedToEnd -> { state = swipeableActionsState,
lastDismissDirection = DismissDirection.StartToEnd startActions = listOfNotNull(start),
val dismissDirectionsCopy = dismissDirections.toSet() endActions = listOfNotNull(end),
dismissDirections.clear() swipeThreshold = swipeActionThreshold,
onEpisodeSwipe(episodeSwipeEndAction) backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
dismissState.snapTo(DismissValue.Default)
dismissDirections.addAll(dismissDirectionsCopy)
}
DismissValue.DismissedToStart -> {
lastDismissDirection = DismissDirection.EndToStart
val dismissDirectionsCopy = dismissDirections.toSet()
dismissDirections.clear()
onEpisodeSwipe(episodeSwipeStartAction)
dismissState.snapTo(DismissValue.Default)
dismissDirections.addAll(dismissDirectionsCopy)
}
DismissValue.Default -> { }
}
}
SwipeToDismiss(
state = dismissState,
directions = dismissDirections,
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor),
) { ) {
if (dismissState.dismissDirection in dismissDirections) {
val downloadState = downloadStateProvider()
SwipeBackgroundIcon(
modifier = Modifier
.padding(start = 16.dp)
.align(Alignment.CenterStart)
.alpha(
if (dismissState.dismissDirection == DismissDirection.StartToEnd) 1f else 0f,
),
tint = contentColorFor(backgroundColor),
swipeAction = episodeSwipeEndAction,
seen = seen,
bookmark = bookmark,
downloadState = downloadState,
)
SwipeBackgroundIcon(
modifier = Modifier
.padding(end = 16.dp)
.align(Alignment.CenterEnd)
.alpha(
if (dismissState.dismissDirection == DismissDirection.EndToStart) 1f else 0f,
),
tint = contentColorFor(backgroundColor),
swipeAction = episodeSwipeStartAction,
seen = seen,
bookmark = bookmark,
downloadState = downloadState,
)
}
}
},
dismissContent = {
Row( Row(
modifier = modifier modifier = modifier
.background(
MaterialTheme.colorScheme.background.copy(dismissContentAlpha),
)
.selectedBackground(selected) .selectedBackground(selected)
.alpha(dismissContentAlpha)
.combinedClickable( .combinedClickable(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
@ -209,7 +146,7 @@ fun AnimeEpisodeListItem(
if (!seen) { if (!seen) {
Icon( Icon(
imageVector = Icons.Filled.Circle, imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unseen), contentDescription = stringResource(R.string.unread),
modifier = Modifier modifier = Modifier
.height(8.dp) .height(8.dp)
.padding(end = 4.dp), .padding(end = 4.dp),
@ -280,55 +217,41 @@ fun AnimeEpisodeListItem(
) )
} }
} }
}, }
)
} }
} }
@Composable private fun getSwipeAction(
private fun SwipeBackgroundIcon( action: LibraryPreferences.EpisodeSwipeAction,
modifier: Modifier = Modifier,
tint: Color,
swipeAction: LibraryPreferences.EpisodeSwipeAction,
seen: Boolean, seen: Boolean,
bookmark: Boolean, bookmark: Boolean,
downloadState: AnimeDownload.State, downloadState: AnimeDownload.State,
) { background: Color,
val imageVector = when (swipeAction) { onSwipe: () -> Unit,
LibraryPreferences.EpisodeSwipeAction.ToggleSeen -> { ): me.saket.swipe.SwipeAction? {
if (!seen) { return when (action) {
Icons.Outlined.Done LibraryPreferences.EpisodeSwipeAction.ToggleSeen -> swipeAction(
} else { icon = if (!seen) Icons.Outlined.Done else Icons.Outlined.RemoveDone,
Icons.Outlined.RemoveDone background = background,
} isUndo = seen,
} onSwipe = onSwipe,
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark -> {
if (!bookmark) {
Icons.Outlined.BookmarkAdd
} else {
Icons.Outlined.BookmarkRemove
}
}
LibraryPreferences.EpisodeSwipeAction.Download -> {
when (downloadState) {
AnimeDownload.State.NOT_DOWNLOADED,
AnimeDownload.State.ERROR,
-> { Icons.Outlined.Download }
AnimeDownload.State.QUEUE,
AnimeDownload.State.DOWNLOADING,
-> { Icons.Outlined.FileDownloadOff }
AnimeDownload.State.DOWNLOADED -> { Icons.Outlined.Delete }
}
}
LibraryPreferences.EpisodeSwipeAction.Disabled -> null
}
imageVector?.let {
Icon(
modifier = modifier,
imageVector = imageVector,
tint = tint,
contentDescription = null,
) )
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark -> swipeAction(
icon = if (!bookmark) Icons.Outlined.BookmarkAdd else Icons.Outlined.BookmarkRemove,
background = background,
isUndo = bookmark,
onSwipe = onSwipe,
)
LibraryPreferences.EpisodeSwipeAction.Download -> swipeAction(
icon = when (downloadState) {
AnimeDownload.State.NOT_DOWNLOADED, AnimeDownload.State.ERROR -> Icons.Outlined.Download
AnimeDownload.State.QUEUE, AnimeDownload.State.DOWNLOADING -> Icons.Outlined.FileDownloadOff
AnimeDownload.State.DOWNLOADED -> Icons.Outlined.Delete
},
background = background,
onSwipe = onSwipe,
)
LibraryPreferences.EpisodeSwipeAction.Disabled -> null
} }
} }
@ -370,3 +293,26 @@ fun NextEpisodeAiringListItem(
} }
} }
} }
private fun swipeAction(
onSwipe: () -> Unit,
icon: ImageVector,
background: Color,
isUndo: Boolean = false,
): me.saket.swipe.SwipeAction {
return me.saket.swipe.SwipeAction(
icon = {
Icon(
modifier = Modifier.padding(16.dp),
imageVector = icon,
tint = contentColorFor(background),
contentDescription = null,
)
},
background = background,
onSwipe = onSwipe,
isUndo = isUndo,
)
}
private val swipeActionThreshold = 56.dp

View file

@ -93,8 +93,8 @@ fun MangaScreen(
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
isTabletUi: Boolean, isTabletUi: Boolean,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -152,8 +152,8 @@ fun MangaScreen(
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
@ -187,8 +187,8 @@ fun MangaScreen(
state = state, state = state,
snackbarHostState = snackbarHostState, snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat, dateFormat = dateFormat,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
@ -227,8 +227,8 @@ private fun MangaScreenSmallImpl(
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -439,8 +439,8 @@ private fun MangaScreenSmallImpl(
chapters = chapters, chapters = chapters,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected, onChapterSelected = onChapterSelected,
@ -458,8 +458,8 @@ fun MangaScreenLargeImpl(
snackbarHostState: SnackbarHostState, snackbarHostState: SnackbarHostState,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -664,8 +664,8 @@ fun MangaScreenLargeImpl(
chapters = chapters, chapters = chapters,
dateRelativeTime = dateRelativeTime, dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat, dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked, onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter, onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected, onChapterSelected = onChapterSelected,
@ -727,8 +727,8 @@ private fun LazyListScope.sharedChapterItems(
chapters: List<ChapterItem>, chapters: List<ChapterItem>,
dateRelativeTime: Int, dateRelativeTime: Int,
dateFormat: DateFormat, dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onChapterClicked: (Chapter) -> Unit, onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?, onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
@ -775,8 +775,8 @@ private fun LazyListScope.sharedChapterItems(
downloadIndicatorEnabled = chapters.fastAll { !it.selected }, downloadIndicatorEnabled = chapters.fastAll { !it.selected },
downloadStateProvider = { chapterItem.downloadState }, downloadStateProvider = { chapterItem.downloadState },
downloadProgressProvider = { chapterItem.downloadProgress }, downloadProgressProvider = { chapterItem.downloadProgress },
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = { onLongClick = {
onChapterSelected(chapterItem, !chapterItem.selected, true, true) onChapterSelected(chapterItem, !chapterItem.selected, true, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress) haptic.performHapticFeedback(HapticFeedbackType.LongPress)

View file

@ -1,20 +1,13 @@
package eu.kanade.presentation.entries.manga.components package eu.kanade.presentation.entries.manga.components
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.layout.sizeIn
import androidx.compose.material.DismissDirection
import androidx.compose.material.DismissValue
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Bookmark import androidx.compose.material.icons.filled.Bookmark
import androidx.compose.material.icons.filled.Circle import androidx.compose.material.icons.filled.Circle
@ -25,7 +18,6 @@ import androidx.compose.material.icons.outlined.Done
import androidx.compose.material.icons.outlined.Download import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.FileDownloadOff import androidx.compose.material.icons.outlined.FileDownloadOff
import androidx.compose.material.icons.outlined.RemoveDone import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
@ -37,14 +29,18 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
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.alpha
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
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.LocalViewConfiguration import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration import androidx.compose.ui.platform.ViewConfiguration
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -54,10 +50,13 @@ import androidx.compose.ui.unit.sp
import eu.kanade.presentation.entries.DotSeparatorText import eu.kanade.presentation.entries.DotSeparatorText
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload import eu.kanade.tachiyomi.data.download.manga.model.MangaDownload
import me.saket.swipe.SwipeableActionsBox
import me.saket.swipe.rememberSwipeableActionsState
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.core.components.material.ReadItemAlpha import tachiyomi.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
import tachiyomi.presentation.core.util.selectedBackground import tachiyomi.presentation.core.util.selectedBackground
import kotlin.math.absoluteValue
@Composable @Composable
fun MangaChapterListItem( fun MangaChapterListItem(
@ -72,13 +71,19 @@ fun MangaChapterListItem(
downloadIndicatorEnabled: Boolean, downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> MangaDownload.State, downloadStateProvider: () -> MangaDownload.State,
downloadProgressProvider: () -> Int, downloadProgressProvider: () -> Int,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onDownloadClick: ((ChapterDownloadAction) -> Unit)?, onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit, onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
) { ) {
val haptic = LocalHapticFeedback.current
val density = LocalDensity.current
val textAlpha = if (read) ReadItemAlpha else 1f
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
// Increase touch slop of swipe action to reduce accidental trigger // Increase touch slop of swipe action to reduce accidental trigger
val configuration = LocalViewConfiguration.current val configuration = LocalViewConfiguration.current
CompositionLocalProvider( CompositionLocalProvider(
@ -86,117 +91,42 @@ fun MangaChapterListItem(
override val touchSlop: Float = configuration.touchSlop * 3f override val touchSlop: Float = configuration.touchSlop * 3f
}, },
) { ) {
val textAlpha = if (read) ReadItemAlpha else 1f val start = getSwipeAction(
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha action = chapterSwipeStartAction,
read = read,
val chapterSwipeStartEnabled = bookmark = bookmark,
chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled downloadState = downloadStateProvider(),
val chapterSwipeEndEnabled = background = MaterialTheme.colorScheme.primaryContainer,
chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
val dismissState = rememberDismissState()
val dismissDirections = remember { mutableSetOf<DismissDirection>() }
var lastDismissDirection: DismissDirection? by remember { mutableStateOf(null) }
if (lastDismissDirection == null) {
if (chapterSwipeStartEnabled) {
dismissDirections.add(DismissDirection.EndToStart)
}
if (chapterSwipeEndEnabled) {
dismissDirections.add(DismissDirection.StartToEnd)
}
}
val animateDismissContentAlpha by animateFloatAsState(
label = "animateDismissContentAlpha",
targetValue = if (lastDismissDirection != null) 1f else 0f,
animationSpec = tween(durationMillis = if (lastDismissDirection != null) 500 else 0),
finishedListener = {
lastDismissDirection = null
},
) )
val dismissContentAlpha = val end = getSwipeAction(
if (lastDismissDirection != null) animateDismissContentAlpha else 1f action = chapterSwipeEndAction,
val backgroundColor = read = read,
if (chapterSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) { bookmark = bookmark,
MaterialTheme.colorScheme.primary downloadState = downloadStateProvider(),
} else if (chapterSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) { background = MaterialTheme.colorScheme.primaryContainer,
MaterialTheme.colorScheme.primary onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
} else { )
Color.Unspecified
val swipeableActionsState = rememberSwipeableActionsState()
LaunchedEffect(Unit) {
// Haptic effect when swipe over threshold
val swipeActionThresholdPx = with(density) { swipeActionThreshold.toPx() }
snapshotFlow { swipeableActionsState.offset.value.absoluteValue > swipeActionThresholdPx }
.collect { if (it) haptic.performHapticFeedback(HapticFeedbackType.LongPress) }
} }
LaunchedEffect(dismissState.currentValue) { SwipeableActionsBox(
when (dismissState.currentValue) { modifier = Modifier.clipToBounds(),
DismissValue.DismissedToEnd -> { state = swipeableActionsState,
lastDismissDirection = DismissDirection.StartToEnd startActions = listOfNotNull(start),
val dismissDirectionsCopy = dismissDirections.toSet() endActions = listOfNotNull(end),
dismissDirections.clear() swipeThreshold = swipeActionThreshold,
onChapterSwipe(chapterSwipeEndAction) backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
dismissState.snapTo(DismissValue.Default)
dismissDirections.addAll(dismissDirectionsCopy)
}
DismissValue.DismissedToStart -> {
lastDismissDirection = DismissDirection.EndToStart
val dismissDirectionsCopy = dismissDirections.toSet()
dismissDirections.clear()
onChapterSwipe(chapterSwipeStartAction)
dismissState.snapTo(DismissValue.Default)
dismissDirections.addAll(dismissDirectionsCopy)
}
DismissValue.Default -> {}
}
}
SwipeToDismiss(
state = dismissState,
directions = dismissDirections,
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor),
) { ) {
if (dismissState.dismissDirection in dismissDirections) {
val downloadState = downloadStateProvider()
SwipeBackgroundIcon(
modifier = Modifier
.padding(start = 16.dp)
.align(Alignment.CenterStart)
.alpha(
if (dismissState.dismissDirection == DismissDirection.StartToEnd) 1f else 0f,
),
tint = contentColorFor(backgroundColor),
swipeAction = chapterSwipeEndAction,
read = read,
bookmark = bookmark,
downloadState = downloadState,
)
SwipeBackgroundIcon(
modifier = Modifier
.padding(end = 16.dp)
.align(Alignment.CenterEnd)
.alpha(
if (dismissState.dismissDirection == DismissDirection.EndToStart) 1f else 0f,
),
tint = contentColorFor(backgroundColor),
swipeAction = chapterSwipeStartAction,
read = read,
bookmark = bookmark,
downloadState = downloadState,
)
}
}
},
dismissContent = {
Row( Row(
modifier = modifier modifier = modifier
.background(
MaterialTheme.colorScheme.background.copy(dismissContentAlpha),
)
.selectedBackground(selected) .selectedBackground(selected)
.alpha(dismissContentAlpha)
.combinedClickable( .combinedClickable(
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
@ -263,7 +193,6 @@ fun MangaChapterListItem(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha), modifier = Modifier.alpha(ReadItemAlpha),
) )
if (scanlator != null) DotSeparatorText()
} }
if (scanlator != null) { if (scanlator != null) {
Text( Text(
@ -286,54 +215,63 @@ fun MangaChapterListItem(
) )
} }
} }
}, }
)
} }
} }
@Composable private fun getSwipeAction(
private fun SwipeBackgroundIcon( action: LibraryPreferences.ChapterSwipeAction,
modifier: Modifier = Modifier,
tint: Color,
swipeAction: LibraryPreferences.ChapterSwipeAction,
read: Boolean, read: Boolean,
bookmark: Boolean, bookmark: Boolean,
downloadState: MangaDownload.State, downloadState: MangaDownload.State,
) { background: Color,
val imageVector = when (swipeAction) { onSwipe: () -> Unit,
LibraryPreferences.ChapterSwipeAction.ToggleRead -> { ): me.saket.swipe.SwipeAction? {
if (!read) { return when (action) {
Icons.Outlined.Done LibraryPreferences.ChapterSwipeAction.ToggleRead -> swipeAction(
} else { icon = if (!read) Icons.Outlined.Done else Icons.Outlined.RemoveDone,
Icons.Outlined.RemoveDone background = background,
} isUndo = read,
} onSwipe = onSwipe,
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> { )
if (!bookmark) { LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> swipeAction(
Icons.Outlined.BookmarkAdd icon = if (!bookmark) Icons.Outlined.BookmarkAdd else Icons.Outlined.BookmarkRemove,
} else { background = background,
Icons.Outlined.BookmarkRemove isUndo = bookmark,
} onSwipe = onSwipe,
} )
LibraryPreferences.ChapterSwipeAction.Download -> { LibraryPreferences.ChapterSwipeAction.Download -> swipeAction(
when (downloadState) { icon = when (downloadState) {
MangaDownload.State.NOT_DOWNLOADED, MangaDownload.State.NOT_DOWNLOADED, MangaDownload.State.ERROR -> Icons.Outlined.Download
MangaDownload.State.ERROR, MangaDownload.State.QUEUE, MangaDownload.State.DOWNLOADING -> Icons.Outlined.FileDownloadOff
-> { Icons.Outlined.Download } MangaDownload.State.DOWNLOADED -> Icons.Outlined.Delete
MangaDownload.State.QUEUE, },
MangaDownload.State.DOWNLOADING, background = background,
-> { Icons.Outlined.FileDownloadOff } onSwipe = onSwipe,
MangaDownload.State.DOWNLOADED -> { Icons.Outlined.Delete } )
}
}
LibraryPreferences.ChapterSwipeAction.Disabled -> null LibraryPreferences.ChapterSwipeAction.Disabled -> null
} }
imageVector?.let { }
private fun swipeAction(
onSwipe: () -> Unit,
icon: ImageVector,
background: Color,
isUndo: Boolean = false,
): me.saket.swipe.SwipeAction {
return me.saket.swipe.SwipeAction(
icon = {
Icon( Icon(
modifier = modifier, modifier = Modifier.padding(16.dp),
imageVector = imageVector, imageVector = icon,
tint = tint, tint = contentColorFor(background),
contentDescription = null, contentDescription = null,
) )
} },
background = background,
onSwipe = onSwipe,
isUndo = isUndo,
)
} }
private val swipeActionThreshold = 56.dp

View file

@ -30,6 +30,8 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.source.service.SourcePreferences.DataSaver import eu.kanade.domain.source.service.SourcePreferences.DataSaver
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.screen.advanced.ClearAnimeDatabaseScreen
import eu.kanade.presentation.more.settings.screen.advanced.ClearDatabaseScreen
import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen import eu.kanade.presentation.more.settings.screen.debug.DebugInfoScreen
import eu.kanade.presentation.util.collectAsState import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R

View file

@ -394,7 +394,7 @@ object SettingsLibraryScreen : SearchableSettings {
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeChapterStartAction(), pref = libraryPreferences.swipeChapterStartAction(),
title = stringResource(R.string.pref_chapter_swipe_start), title = stringResource(R.string.pref_chapter_swipe_end),
entries = mapOf( entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable), LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark), LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
@ -404,7 +404,7 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeChapterEndAction(), pref = libraryPreferences.swipeChapterEndAction(),
title = stringResource(R.string.pref_chapter_swipe_end), title = stringResource(R.string.pref_chapter_swipe_start),
entries = mapOf( entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable), LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark), LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
@ -425,7 +425,7 @@ object SettingsLibraryScreen : SearchableSettings {
preferenceItems = listOf( preferenceItems = listOf(
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeEpisodeStartAction(), pref = libraryPreferences.swipeEpisodeStartAction(),
title = stringResource(R.string.pref_episode_swipe_start), title = stringResource(R.string.pref_episode_swipe_end),
entries = mapOf( entries = mapOf(
LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable), LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode), LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode),
@ -435,7 +435,7 @@ object SettingsLibraryScreen : SearchableSettings {
), ),
Preference.PreferenceItem.ListPreference( Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeEpisodeEndAction(), pref = libraryPreferences.swipeEpisodeEndAction(),
title = stringResource(R.string.pref_episode_swipe_end), title = stringResource(R.string.pref_episode_swipe_start),
entries = mapOf( entries = mapOf(
LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable), LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode), LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode),

View file

@ -45,6 +45,7 @@ import cafe.adriel.voyager.navigator.currentOrThrow
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.UpIcon import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget import eu.kanade.presentation.more.settings.widget.TextPreferenceWidget
import eu.kanade.presentation.util.LocalBackPress import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen

View file

@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen
import android.content.res.Resources
import androidx.compose.animation.Crossfade import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -39,12 +38,14 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@ -52,7 +53,6 @@ import eu.kanade.presentation.components.UpIcon
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.isLTR
import tachiyomi.presentation.core.components.material.Divider import tachiyomi.presentation.core.components.material.Divider
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen import tachiyomi.presentation.core.screens.EmptyScreen
@ -164,6 +164,8 @@ private fun SearchResult(
) { ) {
if (searchKey.isEmpty()) return if (searchKey.isEmpty()) return
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val index = getIndex() val index = getIndex()
val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) { val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) {
value = index.asSequence() value = index.asSequence()
@ -199,7 +201,7 @@ private fun SearchResult(
SearchResultItem( SearchResultItem(
route = settingsData.route, route = settingsData.route,
title = p.title, title = p.title,
breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle), breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle, isLtr = isLtr),
highlightKey = p.title, highlightKey = p.title,
) )
} }
@ -264,11 +266,11 @@ private fun getIndex() = settingScreens
) )
} }
private fun getLocalizedBreadcrumb(path: String, node: String?): String { private fun getLocalizedBreadcrumb(path: String, node: String?, isLtr: Boolean): String {
return if (node == null) { return if (node == null) {
path path
} else { } else {
if (Resources.getSystem().isLTR) { if (isLtr) {
// This locale reads left to right. // This locale reads left to right.
"$path > $node" "$path > $node"
} else { } else {

View file

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.about
import android.content.Context import android.content.Context
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility

View file

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.about
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding

View file

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.about
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme

View file

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.advanced
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View file

@ -1,4 +1,4 @@
package eu.kanade.presentation.more.settings.screen package eu.kanade.presentation.more.settings.screen.advanced
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View file

@ -12,7 +12,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.Preference import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.PreferenceScaffold import eu.kanade.presentation.more.settings.PreferenceScaffold
import eu.kanade.presentation.more.settings.screen.AboutScreen import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.DeviceUtil

View file

@ -106,8 +106,8 @@ class AnimeScreen(
dateRelativeTime = screenModel.relativeTime, dateRelativeTime = screenModel.relativeTime,
dateFormat = screenModel.dateFormat, dateFormat = screenModel.dateFormat,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
episodeSwipeEndAction = screenModel.episodeSwipeEndAction,
episodeSwipeStartAction = screenModel.episodeSwipeStartAction, episodeSwipeStartAction = screenModel.episodeSwipeStartAction,
episodeSwipeEndAction = screenModel.episodeSwipeEndAction,
showNextEpisodeAirTime = screenModel.showNextEpisodeAirTime, showNextEpisodeAirTime = screenModel.showNextEpisodeAirTime,
alwaysUseExternalPlayer = screenModel.alwaysUseExternalPlayer, alwaysUseExternalPlayer = screenModel.alwaysUseExternalPlayer,
onBackClicked = navigator::pop, onBackClicked = navigator::pop,

View file

@ -126,8 +126,8 @@ class AnimeInfoScreenModel(
private val processedEpisodes: List<EpisodeItem>? private val processedEpisodes: List<EpisodeItem>?
get() = successState?.processedEpisodes get() = successState?.processedEpisodes
val episodeSwipeEndAction = libraryPreferences.swipeEpisodeEndAction().get() val episodeSwipeStartAction = libraryPreferences.swipeEpisodeEndAction().get()
val episodeSwipeStartAction = libraryPreferences.swipeEpisodeStartAction().get() val episodeSwipeEndAction = libraryPreferences.swipeEpisodeStartAction().get()
val showNextEpisodeAirTime = trackPreferences.showNextEpisodeAiringTime().get() val showNextEpisodeAirTime = trackPreferences.showNextEpisodeAiringTime().get()
val alwaysUseExternalPlayer = playerPreferences.alwaysUseExternalPlayer().get() val alwaysUseExternalPlayer = playerPreferences.alwaysUseExternalPlayer().get()

View file

@ -102,8 +102,8 @@ class MangaScreen(
dateRelativeTime = screenModel.relativeTime, dateRelativeTime = screenModel.relativeTime,
dateFormat = screenModel.dateFormat, dateFormat = screenModel.dateFormat,
isTabletUi = isTabletUi(), isTabletUi = isTabletUi(),
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
chapterSwipeStartAction = screenModel.chapterSwipeStartAction, chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
onBackClicked = navigator::pop, onBackClicked = navigator::pop,
onChapterClicked = { openChapter(context, it) }, onChapterClicked = { openChapter(context, it) },
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() }, onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },

View file

@ -124,8 +124,8 @@ class MangaInfoScreenModel(
private val filteredChapters: List<ChapterItem>? private val filteredChapters: List<ChapterItem>?
get() = successState?.processedChapters get() = successState?.processedChapters
val chapterSwipeEndAction = libraryPreferences.swipeChapterEndAction().get() val chapterSwipeStartAction = libraryPreferences.swipeChapterEndAction().get()
val chapterSwipeStartAction = libraryPreferences.swipeChapterStartAction().get() val chapterSwipeEndAction = libraryPreferences.swipeChapterStartAction().get()
val relativeTime by uiPreferences.relativeTime().asState(coroutineScope) val relativeTime by uiPreferences.relativeTime().asState(coroutineScope)
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get())) val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))

View file

@ -109,6 +109,7 @@ class PlayerViewModel(
val eventFlow = eventChannel.receiveAsFlow() val eventFlow = eventChannel.receiveAsFlow()
private val incognitoMode = basePreferences.incognitoMode().get() private val incognitoMode = basePreferences.incognitoMode().get()
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileWatching().get()
internal val relativeTime = uiPreferences.relativeTime().get() internal val relativeTime = uiPreferences.relativeTime().get()
internal val dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get()) internal val dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get())
@ -349,9 +350,9 @@ class PlayerViewModel(
} }
private fun downloadNextEpisodes() { private fun downloadNextEpisodes() {
if (downloadAheadAmount == 0) return
val anime = currentAnime ?: return val anime = currentAnime ?: return
val amount = downloadPreferences.autoDownloadWhileWatching().get()
if (amount == 0 || !anime.favorite) return
// Only download ahead if current + next episode is already downloaded too to avoid jank // Only download ahead if current + next episode is already downloaded too to avoid jank
if (getCurrentEpisodeIndex() == this.currentPlaylist.lastIndex) return if (getCurrentEpisodeIndex() == this.currentPlaylist.lastIndex) return
val currentEpisode = currentEpisode ?: return val currentEpisode = currentEpisode ?: return
@ -366,7 +367,7 @@ class PlayerViewModel(
return@launchIO return@launchIO
} }
val episodesToDownload = getNextEpisodes.await(anime.id, nextEpisode.id!!) val episodesToDownload = getNextEpisodes.await(anime.id, nextEpisode.id!!)
.take(amount) .take(downloadAheadAmount)
downloadManager.downloadEpisodes(anime, episodesToDownload) downloadManager.downloadEpisodes(anime, episodesToDownload)
} }
} }

View file

@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader import eu.kanade.tachiyomi.ui.reader.loader.ChapterLoader
import eu.kanade.tachiyomi.ui.reader.loader.DownloadPageLoader
import eu.kanade.tachiyomi.ui.reader.model.InsertPage import eu.kanade.tachiyomi.ui.reader.model.InsertPage
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@ -204,6 +205,7 @@ class ReaderViewModel(
} }
private val incognitoMode = preferences.incognitoMode().get() private val incognitoMode = preferences.incognitoMode().get()
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
init { init {
// To save state // To save state
@ -444,12 +446,11 @@ class ReaderViewModel(
} }
private fun downloadNextChapters() { private fun downloadNextChapters() {
if (downloadAheadAmount == 0) return
val manga = manga ?: return val manga = manga ?: return
val amount = downloadPreferences.autoDownloadWhileReading().get()
if (amount == 0 || !manga.favorite) return
// Only download ahead if current + next chapter is already downloaded too to avoid jank // Only download ahead if current + next chapter is already downloaded too to avoid jank
if (getCurrentChapter()?.pageLoader?.isLocal == true) return if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return
viewModelScope.launchIO { viewModelScope.launchIO {
@ -467,7 +468,7 @@ class ReaderViewModel(
} else { } else {
this this
} }
}.take(amount) }.take(downloadAheadAmount)
downloadManager.downloadChapters( downloadManager.downloadChapters(
manga, manga,
chaptersToDownload, chaptersToDownload,

View file

@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.screen.AboutScreen
import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen import eu.kanade.presentation.more.settings.screen.SettingsAppearanceScreen
import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen import eu.kanade.presentation.more.settings.screen.SettingsBackupScreen
import eu.kanade.presentation.more.settings.screen.SettingsMainScreen import eu.kanade.presentation.more.settings.screen.SettingsMainScreen
import eu.kanade.presentation.more.settings.screen.about.AboutScreen
import eu.kanade.presentation.util.DefaultNavigatorScreenTransition import eu.kanade.presentation.util.DefaultNavigatorScreenTransition
import eu.kanade.presentation.util.LocalBackPress import eu.kanade.presentation.util.LocalBackPress
import eu.kanade.presentation.util.Screen import eu.kanade.presentation.util.Screen

View file

@ -261,22 +261,22 @@ class LibraryPreferences(
// region Swipe Actions // region Swipe Actions
fun swipeEpisodeStartAction() =
preferenceStore.getEnum("pref_episode_swipe_end_action", EpisodeSwipeAction.ToggleSeen)
fun swipeEpisodeEndAction() = preferenceStore.getEnum( fun swipeEpisodeEndAction() = preferenceStore.getEnum(
"pref_episode_swipe_start_action", "pref_episode_swipe_start_action",
EpisodeSwipeAction.ToggleBookmark, EpisodeSwipeAction.ToggleBookmark,
) )
fun swipeEpisodeStartAction() = fun swipeChapterStartAction() =
preferenceStore.getEnum("pref_episode_swipe_end_action", EpisodeSwipeAction.ToggleSeen) preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleRead)
fun swipeChapterEndAction() = preferenceStore.getEnum( fun swipeChapterEndAction() = preferenceStore.getEnum(
"pref_chapter_swipe_start_action", "pref_chapter_swipe_start_action",
ChapterSwipeAction.ToggleBookmark, ChapterSwipeAction.ToggleBookmark,
) )
fun swipeChapterStartAction() =
preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleRead)
// endregion // endregion
enum class EpisodeSwipeAction { enum class EpisodeSwipeAction {

View file

@ -19,7 +19,7 @@ flowreactivenetwork = "ru.beryukhov:flowreactivenetwork:1.0.4"
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" } okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp_version" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp_version" }
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp_version" }
okio = "com.squareup.okio:okio:3.3.0" okio = "com.squareup.okio:okio:3.4.0"
conscrypt-android = "org.conscrypt:conscrypt-android:2.5.2" conscrypt-android = "org.conscrypt:conscrypt-android:2.5.2"
@ -60,6 +60,8 @@ insetter = "dev.chrisbanes.insetter:insetter:0.6.1"
compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.0.3" compose-materialmotion = "io.github.fornewid:material-motion-compose-core:1.0.3"
compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0" compose-simpleicons = "br.com.devsrsouza.compose.icons.android:simple-icons:1.0.0"
swipe = "me.saket.swipe:swipe:1.2.0"
logcat = "com.squareup.logcat:logcat:0.1" logcat = "com.squareup.logcat:logcat:0.1"
acra-http = "ch.acra:acra-http:5.10.1" acra-http = "ch.acra:acra-http:5.10.1"

View file

@ -463,7 +463,7 @@
<item quantity="one">Next unread chapter</item> <item quantity="one">Next unread chapter</item>
<item quantity="other">Next %d unread chapters</item> <item quantity="other">Next %d unread chapters</item>
</plurals> </plurals>
<string name="download_ahead_info">Only works on entries in library and if the current chapter plus the next one are already downloaded</string> <string name="download_ahead_info">Only works if the current chapter/episode + the next one are already downloaded.</string>
<string name="save_chapter_as_cbz">Save as CBZ archive</string> <string name="save_chapter_as_cbz">Save as CBZ archive</string>
<string name="split_tall_images">Split tall images</string> <string name="split_tall_images">Split tall images</string>
<string name="split_tall_images_summary">Improves reader performance</string> <string name="split_tall_images_summary">Improves reader performance</string>