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.compose.materialmotion)
implementation(libs.compose.simpleicons)
implementation(libs.swipe)
// Logging
implementation(libs.logcat)

View file

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

View file

@ -1,21 +1,14 @@
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.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
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.filled.Bookmark
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.FileDownloadOff
import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@ -38,14 +30,18 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds
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.LocalHapticFeedback
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
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.tachiyomi.R
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.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
import tachiyomi.presentation.core.util.selectedBackground
import kotlin.math.absoluteValue
@Composable
fun AnimeEpisodeListItem(
@ -73,13 +72,19 @@ fun AnimeEpisodeListItem(
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> AnimeDownload.State,
downloadProgressProvider: () -> Int,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeStartAction: LibraryPreferences.EpisodeSwipeAction,
episodeSwipeEndAction: LibraryPreferences.EpisodeSwipeAction,
onLongClick: () -> Unit,
onClick: () -> Unit,
onDownloadClick: ((EpisodeDownloadAction) -> 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
val configuration = LocalViewConfiguration.current
CompositionLocalProvider(
@ -87,248 +92,166 @@ fun AnimeEpisodeListItem(
override val touchSlop: Float = configuration.touchSlop * 3f
},
) {
val textAlpha = if (seen) ReadItemAlpha else 1f
val textSubtitleAlpha = if (seen) ReadItemAlpha else SecondaryItemAlpha
val episodeSwipeStartEnabled = episodeSwipeStartAction != LibraryPreferences.EpisodeSwipeAction.Disabled
val episodeSwipeEndEnabled = episodeSwipeEndAction != LibraryPreferences.EpisodeSwipeAction.Disabled
val dismissState = rememberDismissState()
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 start = getSwipeAction(
action = episodeSwipeStartAction,
seen = seen,
bookmark = bookmark,
downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
onSwipe = { onEpisodeSwipe(episodeSwipeStartAction) },
)
val dismissContentAlpha = if (lastDismissDirection != null) animateDismissContentAlpha else 1f
val backgroundColor = if (episodeSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) {
MaterialTheme.colorScheme.primary
} else if (episodeSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) {
MaterialTheme.colorScheme.primary
} else {
Color.Unspecified
val end = getSwipeAction(
action = episodeSwipeEndAction,
seen = seen,
bookmark = bookmark,
downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
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) {
when (dismissState.currentValue) {
DismissValue.DismissedToEnd -> {
lastDismissDirection = DismissDirection.StartToEnd
val dismissDirectionsCopy = dismissDirections.toSet()
dismissDirections.clear()
onEpisodeSwipe(episodeSwipeEndAction)
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),
SwipeableActionsBox(
modifier = Modifier.clipToBounds(),
state = swipeableActionsState,
startActions = listOfNotNull(start),
endActions = listOfNotNull(end),
swipeThreshold = swipeActionThreshold,
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
) {
Row(
modifier = modifier
.selectedBackground(selected)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
)
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
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(
modifier = modifier
.background(
MaterialTheme.colorScheme.background.copy(dismissContentAlpha),
)
.selectedBackground(selected)
.alpha(dismissContentAlpha)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
)
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(6.dp),
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
var textHeight by remember { mutableIntStateOf(0) }
if (!seen) {
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unseen),
modifier = Modifier
.height(8.dp)
.padding(end = 4.dp),
tint = MaterialTheme.colorScheme.primary,
)
}
if (bookmark) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = stringResource(R.string.action_filter_bookmarked),
modifier = Modifier
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
var textHeight by remember { mutableIntStateOf(0) }
if (!seen) {
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unread),
modifier = Modifier
.height(8.dp)
.padding(end = 4.dp),
tint = MaterialTheme.colorScheme.primary,
)
}
if (bookmark) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = stringResource(R.string.action_filter_bookmarked),
modifier = Modifier
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
)
}
Row {
ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
),
) {
if (date != null) {
Text(
text = date,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (watchProgress != null || scanlator != null) DotSeparatorText()
}
if (watchProgress != null) {
Text(
text = watchProgress,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha),
)
if (scanlator != null) DotSeparatorText()
}
if (scanlator != null) {
Text(
text = scanlator,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Row {
ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
),
) {
if (date != null) {
Text(
text = date,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (watchProgress != null || scanlator != null) DotSeparatorText()
}
if (watchProgress != null) {
Text(
text = watchProgress,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha),
)
if (scanlator != null) DotSeparatorText()
}
if (scanlator != null) {
Text(
text = scanlator,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
if (onDownloadClick != null) {
EpisodeDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick,
)
}
}
},
)
if (onDownloadClick != null) {
EpisodeDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick,
)
}
}
}
}
}
@Composable
private fun SwipeBackgroundIcon(
modifier: Modifier = Modifier,
tint: Color,
swipeAction: LibraryPreferences.EpisodeSwipeAction,
private fun getSwipeAction(
action: LibraryPreferences.EpisodeSwipeAction,
seen: Boolean,
bookmark: Boolean,
downloadState: AnimeDownload.State,
) {
val imageVector = when (swipeAction) {
LibraryPreferences.EpisodeSwipeAction.ToggleSeen -> {
if (!seen) {
Icons.Outlined.Done
} else {
Icons.Outlined.RemoveDone
}
}
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,
background: Color,
onSwipe: () -> Unit,
): me.saket.swipe.SwipeAction? {
return when (action) {
LibraryPreferences.EpisodeSwipeAction.ToggleSeen -> swipeAction(
icon = if (!seen) Icons.Outlined.Done else Icons.Outlined.RemoveDone,
background = background,
isUndo = seen,
onSwipe = onSwipe,
)
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,
dateFormat: DateFormat,
isTabletUi: Boolean,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -152,8 +152,8 @@ fun MangaScreen(
snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
@ -187,8 +187,8 @@ fun MangaScreen(
state = state,
snackbarHostState = snackbarHostState,
dateRelativeTime = dateRelativeTime,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
dateFormat = dateFormat,
onBackClicked = onBackClicked,
onChapterClicked = onChapterClicked,
@ -227,8 +227,8 @@ private fun MangaScreenSmallImpl(
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -439,8 +439,8 @@ private fun MangaScreenSmallImpl(
chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected,
@ -458,8 +458,8 @@ fun MangaScreenLargeImpl(
snackbarHostState: SnackbarHostState,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onBackClicked: () -> Unit,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
@ -664,8 +664,8 @@ fun MangaScreenLargeImpl(
chapters = chapters,
dateRelativeTime = dateRelativeTime,
dateFormat = dateFormat,
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onChapterClicked = onChapterClicked,
onDownloadChapter = onDownloadChapter,
onChapterSelected = onChapterSelected,
@ -727,8 +727,8 @@ private fun LazyListScope.sharedChapterItems(
chapters: List<ChapterItem>,
dateRelativeTime: Int,
dateFormat: DateFormat,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onChapterClicked: (Chapter) -> Unit,
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
@ -775,8 +775,8 @@ private fun LazyListScope.sharedChapterItems(
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
downloadStateProvider = { chapterItem.downloadState },
downloadProgressProvider = { chapterItem.downloadProgress },
chapterSwipeEndAction = chapterSwipeEndAction,
chapterSwipeStartAction = chapterSwipeStartAction,
chapterSwipeEndAction = chapterSwipeEndAction,
onLongClick = {
onChapterSelected(chapterItem, !chapterItem.selected, true, true)
haptic.performHapticFeedback(HapticFeedbackType.LongPress)

View file

@ -1,20 +1,13 @@
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.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
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.filled.Bookmark
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.FileDownloadOff
import androidx.compose.material.icons.outlined.RemoveDone
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
@ -37,14 +29,18 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clipToBounds
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.LocalHapticFeedback
import androidx.compose.ui.platform.LocalViewConfiguration
import androidx.compose.ui.platform.ViewConfiguration
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.tachiyomi.R
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.presentation.core.components.material.ReadItemAlpha
import tachiyomi.presentation.core.components.material.SecondaryItemAlpha
import tachiyomi.presentation.core.util.selectedBackground
import kotlin.math.absoluteValue
@Composable
fun MangaChapterListItem(
@ -72,13 +71,19 @@ fun MangaChapterListItem(
downloadIndicatorEnabled: Boolean,
downloadStateProvider: () -> MangaDownload.State,
downloadProgressProvider: () -> Int,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
onLongClick: () -> Unit,
onClick: () -> Unit,
onDownloadClick: ((ChapterDownloadAction) -> 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
val configuration = LocalViewConfiguration.current
CompositionLocalProvider(
@ -86,254 +91,187 @@ fun MangaChapterListItem(
override val touchSlop: Float = configuration.touchSlop * 3f
},
) {
val textAlpha = if (read) ReadItemAlpha else 1f
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
val chapterSwipeStartEnabled =
chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled
val chapterSwipeEndEnabled =
chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled
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 start = getSwipeAction(
action = chapterSwipeStartAction,
read = read,
bookmark = bookmark,
downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
onSwipe = { onChapterSwipe(chapterSwipeStartAction) },
)
val end = getSwipeAction(
action = chapterSwipeEndAction,
read = read,
bookmark = bookmark,
downloadState = downloadStateProvider(),
background = MaterialTheme.colorScheme.primaryContainer,
onSwipe = { onChapterSwipe(chapterSwipeEndAction) },
)
val dismissContentAlpha =
if (lastDismissDirection != null) animateDismissContentAlpha else 1f
val backgroundColor =
if (chapterSwipeEndEnabled && (dismissState.dismissDirection == DismissDirection.StartToEnd || lastDismissDirection == DismissDirection.StartToEnd)) {
MaterialTheme.colorScheme.primary
} else if (chapterSwipeStartEnabled && (dismissState.dismissDirection == DismissDirection.EndToStart || lastDismissDirection == DismissDirection.EndToStart)) {
MaterialTheme.colorScheme.primary
} else {
Color.Unspecified
}
LaunchedEffect(dismissState.currentValue) {
when (dismissState.currentValue) {
DismissValue.DismissedToEnd -> {
lastDismissDirection = DismissDirection.StartToEnd
val dismissDirectionsCopy = dismissDirections.toSet()
dismissDirections.clear()
onChapterSwipe(chapterSwipeEndAction)
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 -> {}
}
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) }
}
SwipeToDismiss(
state = dismissState,
directions = dismissDirections,
background = {
Box(
modifier = Modifier
.fillMaxSize()
.background(backgroundColor),
SwipeableActionsBox(
modifier = Modifier.clipToBounds(),
state = swipeableActionsState,
startActions = listOfNotNull(start),
endActions = listOfNotNull(end),
swipeThreshold = swipeActionThreshold,
backgroundUntilSwipeThreshold = MaterialTheme.colorScheme.surfaceContainerLowest,
) {
Row(
modifier = modifier
.selectedBackground(selected)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
)
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(6.dp),
) {
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(
modifier = modifier
.background(
MaterialTheme.colorScheme.background.copy(dismissContentAlpha),
)
.selectedBackground(selected)
.alpha(dismissContentAlpha)
.combinedClickable(
onClick = onClick,
onLongClick = onLongClick,
)
.padding(start = 16.dp, top = 12.dp, end = 8.dp, bottom = 12.dp),
) {
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(6.dp),
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Row(
horizontalArrangement = Arrangement.spacedBy(2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
var textHeight by remember { mutableIntStateOf(0) }
if (!read) {
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unread),
modifier = Modifier
.height(8.dp)
.padding(end = 4.dp),
tint = MaterialTheme.colorScheme.primary,
)
}
if (bookmark) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = stringResource(R.string.action_filter_bookmarked),
modifier = Modifier
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
var textHeight by remember { mutableIntStateOf(0) }
if (!read) {
Icon(
imageVector = Icons.Filled.Circle,
contentDescription = stringResource(R.string.unread),
modifier = Modifier
.height(8.dp)
.padding(end = 4.dp),
tint = MaterialTheme.colorScheme.primary,
)
}
if (bookmark) {
Icon(
imageVector = Icons.Filled.Bookmark,
contentDescription = stringResource(R.string.action_filter_bookmarked),
modifier = Modifier
.sizeIn(maxHeight = with(LocalDensity.current) { textHeight.toDp() - 2.dp }),
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = LocalContentColor.current.copy(alpha = textAlpha),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
onTextLayout = { textHeight = it.size.height },
)
}
Row {
ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
),
) {
if (date != null) {
Text(
text = date,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (readProgress != null || scanlator != null) DotSeparatorText()
}
if (readProgress != null) {
Text(
text = readProgress,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha),
)
if (scanlator != null) DotSeparatorText()
}
if (scanlator != null) {
Text(
text = scanlator,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Row {
ProvideTextStyle(
value = MaterialTheme.typography.bodyMedium.copy(
fontSize = 12.sp,
color = LocalContentColor.current.copy(alpha = textSubtitleAlpha),
),
) {
if (date != null) {
Text(
text = date,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (readProgress != null || scanlator != null) DotSeparatorText()
}
if (readProgress != null) {
Text(
text = readProgress,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.alpha(ReadItemAlpha),
)
}
if (scanlator != null) {
Text(
text = scanlator,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
if (onDownloadClick != null) {
ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick,
)
}
}
},
)
if (onDownloadClick != null) {
ChapterDownloadIndicator(
enabled = downloadIndicatorEnabled,
modifier = Modifier.padding(start = 4.dp),
downloadStateProvider = downloadStateProvider,
downloadProgressProvider = downloadProgressProvider,
onClick = onDownloadClick,
)
}
}
}
}
}
@Composable
private fun SwipeBackgroundIcon(
modifier: Modifier = Modifier,
tint: Color,
swipeAction: LibraryPreferences.ChapterSwipeAction,
private fun getSwipeAction(
action: LibraryPreferences.ChapterSwipeAction,
read: Boolean,
bookmark: Boolean,
downloadState: MangaDownload.State,
) {
val imageVector = when (swipeAction) {
LibraryPreferences.ChapterSwipeAction.ToggleRead -> {
if (!read) {
Icons.Outlined.Done
} else {
Icons.Outlined.RemoveDone
}
}
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> {
if (!bookmark) {
Icons.Outlined.BookmarkAdd
} else {
Icons.Outlined.BookmarkRemove
}
}
LibraryPreferences.ChapterSwipeAction.Download -> {
when (downloadState) {
MangaDownload.State.NOT_DOWNLOADED,
MangaDownload.State.ERROR,
-> { Icons.Outlined.Download }
MangaDownload.State.QUEUE,
MangaDownload.State.DOWNLOADING,
-> { Icons.Outlined.FileDownloadOff }
MangaDownload.State.DOWNLOADED -> { Icons.Outlined.Delete }
}
}
background: Color,
onSwipe: () -> Unit,
): me.saket.swipe.SwipeAction? {
return when (action) {
LibraryPreferences.ChapterSwipeAction.ToggleRead -> swipeAction(
icon = if (!read) Icons.Outlined.Done else Icons.Outlined.RemoveDone,
background = background,
isUndo = read,
onSwipe = onSwipe,
)
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> swipeAction(
icon = if (!bookmark) Icons.Outlined.BookmarkAdd else Icons.Outlined.BookmarkRemove,
background = background,
isUndo = bookmark,
onSwipe = onSwipe,
)
LibraryPreferences.ChapterSwipeAction.Download -> swipeAction(
icon = when (downloadState) {
MangaDownload.State.NOT_DOWNLOADED, MangaDownload.State.ERROR -> Icons.Outlined.Download
MangaDownload.State.QUEUE, MangaDownload.State.DOWNLOADING -> Icons.Outlined.FileDownloadOff
MangaDownload.State.DOWNLOADED -> Icons.Outlined.Delete
},
background = background,
onSwipe = onSwipe,
)
LibraryPreferences.ChapterSwipeAction.Disabled -> null
}
imageVector?.let {
Icon(
modifier = modifier,
imageVector = imageVector,
tint = tint,
contentDescription = null,
)
}
}
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

@ -30,6 +30,8 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.source.service.SourcePreferences.DataSaver
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.util.collectAsState
import eu.kanade.tachiyomi.R

View file

@ -394,7 +394,7 @@ object SettingsLibraryScreen : SearchableSettings {
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeChapterStartAction(),
title = stringResource(R.string.pref_chapter_swipe_start),
title = stringResource(R.string.pref_chapter_swipe_end),
entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
@ -404,7 +404,7 @@ object SettingsLibraryScreen : SearchableSettings {
),
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeChapterEndAction(),
title = stringResource(R.string.pref_chapter_swipe_end),
title = stringResource(R.string.pref_chapter_swipe_start),
entries = mapOf(
LibraryPreferences.ChapterSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.ChapterSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark),
@ -425,7 +425,7 @@ object SettingsLibraryScreen : SearchableSettings {
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeEpisodeStartAction(),
title = stringResource(R.string.pref_episode_swipe_start),
title = stringResource(R.string.pref_episode_swipe_end),
entries = mapOf(
LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable),
LibraryPreferences.EpisodeSwipeAction.ToggleBookmark to stringResource(R.string.action_bookmark_episode),
@ -435,7 +435,7 @@ object SettingsLibraryScreen : SearchableSettings {
),
Preference.PreferenceItem.ListPreference(
pref = libraryPreferences.swipeEpisodeEndAction(),
title = stringResource(R.string.pref_episode_swipe_end),
title = stringResource(R.string.pref_episode_swipe_start),
entries = mapOf(
LibraryPreferences.EpisodeSwipeAction.Disabled to stringResource(R.string.action_disable),
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.AppBarActions
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.util.LocalBackPress
import eu.kanade.presentation.util.Screen

View file

@ -1,6 +1,5 @@
package eu.kanade.presentation.more.settings.screen
import android.content.res.Resources
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.clickable
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.graphics.SolidColor
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.navigator.LocalNavigator
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.util.Screen
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.Scaffold
import tachiyomi.presentation.core.screens.EmptyScreen
@ -164,6 +164,8 @@ private fun SearchResult(
) {
if (searchKey.isEmpty()) return
val isLtr = LocalLayoutDirection.current == LayoutDirection.Ltr
val index = getIndex()
val result by produceState<List<SearchResultItem>?>(initialValue = null, searchKey) {
value = index.asSequence()
@ -199,7 +201,7 @@ private fun SearchResult(
SearchResultItem(
route = settingsData.route,
title = p.title,
breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle),
breadcrumbs = getLocalizedBreadcrumb(path = settingsData.title, node = categoryTitle, isLtr = isLtr),
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) {
path
} else {
if (Resources.getSystem().isLTR) {
if (isLtr) {
// This locale reads left to right.
"$path > $node"
} 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 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.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.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.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.layout.Column

View file

@ -12,7 +12,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import eu.kanade.presentation.more.settings.Preference
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.tachiyomi.R
import eu.kanade.tachiyomi.util.system.DeviceUtil

View file

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

View file

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

View file

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

View file

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

View file

@ -109,6 +109,7 @@ class PlayerViewModel(
val eventFlow = eventChannel.receiveAsFlow()
private val incognitoMode = basePreferences.incognitoMode().get()
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileWatching().get()
internal val relativeTime = uiPreferences.relativeTime().get()
internal val dateFormat = UiPreferences.dateFormat(uiPreferences.dateFormat().get())
@ -349,9 +350,9 @@ class PlayerViewModel(
}
private fun downloadNextEpisodes() {
if (downloadAheadAmount == 0) 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
if (getCurrentEpisodeIndex() == this.currentPlaylist.lastIndex) return
val currentEpisode = currentEpisode ?: return
@ -366,7 +367,7 @@ class PlayerViewModel(
return@launchIO
}
val episodesToDownload = getNextEpisodes.await(anime.id, nextEpisode.id!!)
.take(amount)
.take(downloadAheadAmount)
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.online.HttpSource
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.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@ -204,6 +205,7 @@ class ReaderViewModel(
}
private val incognitoMode = preferences.incognitoMode().get()
private val downloadAheadAmount = downloadPreferences.autoDownloadWhileReading().get()
init {
// To save state
@ -444,12 +446,11 @@ class ReaderViewModel(
}
private fun downloadNextChapters() {
if (downloadAheadAmount == 0) 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
if (getCurrentChapter()?.pageLoader?.isLocal == true) return
if (getCurrentChapter()?.pageLoader !is DownloadPageLoader) return
val nextChapter = state.value.viewerChapters?.nextChapter?.chapter ?: return
viewModelScope.launchIO {
@ -467,7 +468,7 @@ class ReaderViewModel(
} else {
this
}
}.take(amount)
}.take(downloadAheadAmount)
downloadManager.downloadChapters(
manga,
chaptersToDownload,

View file

@ -12,10 +12,10 @@ import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
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.SettingsBackupScreen
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.LocalBackPress
import eu.kanade.presentation.util.Screen

View file

@ -261,22 +261,22 @@ class LibraryPreferences(
// region Swipe Actions
fun swipeEpisodeStartAction() =
preferenceStore.getEnum("pref_episode_swipe_end_action", EpisodeSwipeAction.ToggleSeen)
fun swipeEpisodeEndAction() = preferenceStore.getEnum(
"pref_episode_swipe_start_action",
EpisodeSwipeAction.ToggleBookmark,
)
fun swipeEpisodeStartAction() =
preferenceStore.getEnum("pref_episode_swipe_end_action", EpisodeSwipeAction.ToggleSeen)
fun swipeChapterStartAction() =
preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleRead)
fun swipeChapterEndAction() = preferenceStore.getEnum(
"pref_chapter_swipe_start_action",
ChapterSwipeAction.ToggleBookmark,
)
fun swipeChapterStartAction() =
preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleRead)
// endregion
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-logging = { module = "com.squareup.okhttp3:logging-interceptor", 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"
@ -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-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"
acra-http = "ch.acra:acra-http:5.10.1"

View file

@ -463,7 +463,7 @@
<item quantity="one">Next unread chapter</item>
<item quantity="other">Next %d unread chapters</item>
</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="split_tall_images">Split tall images</string>
<string name="split_tall_images_summary">Improves reader performance</string>