Add swipe actions for chapters (#9304)
* added chapter swipe * Rework corner animtion * Update i18n/src/main/res/values/strings.xml Co-authored-by: arkon <arkon@users.noreply.github.com> * Replace LTR/RTL with Start/End layout * Added label to the animation so the warning will go away * Getting rid of the swipe threshold setting * adding disabled option, renaming stuff, other stuff? * Getting rid of the snackbar * Getting rid of unecessary strings * changing enum names as requested * Renaming Raio to Ratio (I need a better keyboard as well -__-) * Replacing error with download icon and action * backup * minor cleanup * fixing an nasty edge case * fixing mistakes in the previous conflict * space * fixing bug fixed bug where the user could dismiss already dismissed item leading to item getting stuck * fixing lint errors * fixing lints (hopefully) * Added "swipe disabled" to the list of actions * Replacing string value and moving value as requested * replacing rest of the strings with generic ones --------- Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
parent
ef3d2c14b4
commit
a8f17a3fab
@ -66,6 +66,7 @@ import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import eu.kanade.tachiyomi.util.system.copyToClipboard
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.service.missingChaptersCount
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.source.model.StubSource
|
||||
import tachiyomi.presentation.core.components.LazyColumn
|
||||
@ -86,6 +87,8 @@ fun MangaScreen(
|
||||
dateRelativeTime: Int,
|
||||
dateFormat: DateFormat,
|
||||
isTabletUi: Boolean,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onBackClicked: () -> Unit,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
||||
@ -117,6 +120,9 @@ fun MangaScreen(
|
||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||
|
||||
// For chapter swipe
|
||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
|
||||
// Chapter selection
|
||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
||||
onAllChapterSelected: (Boolean) -> Unit,
|
||||
@ -135,6 +141,8 @@ fun MangaScreen(
|
||||
snackbarHostState = snackbarHostState,
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
dateFormat = dateFormat,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
onBackClicked = onBackClicked,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
@ -157,6 +165,7 @@ fun MangaScreen(
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
||||
onChapterSwipe = onChapterSwipe,
|
||||
onChapterSelected = onChapterSelected,
|
||||
onAllChapterSelected = onAllChapterSelected,
|
||||
onInvertSelection = onInvertSelection,
|
||||
@ -166,6 +175,8 @@ fun MangaScreen(
|
||||
state = state,
|
||||
snackbarHostState = snackbarHostState,
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
dateFormat = dateFormat,
|
||||
onBackClicked = onBackClicked,
|
||||
onChapterClicked = onChapterClicked,
|
||||
@ -189,6 +200,7 @@ fun MangaScreen(
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
||||
onChapterSwipe = onChapterSwipe,
|
||||
onChapterSelected = onChapterSelected,
|
||||
onAllChapterSelected = onAllChapterSelected,
|
||||
onInvertSelection = onInvertSelection,
|
||||
@ -202,6 +214,8 @@ private fun MangaScreenSmallImpl(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
dateRelativeTime: Int,
|
||||
dateFormat: DateFormat,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onBackClicked: () -> Unit,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
||||
@ -234,6 +248,9 @@ private fun MangaScreenSmallImpl(
|
||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||
|
||||
// For chapter swipe
|
||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
|
||||
// Chapter selection
|
||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
||||
onAllChapterSelected: (Boolean) -> Unit,
|
||||
@ -404,9 +421,12 @@ private fun MangaScreenSmallImpl(
|
||||
chapters = chapters,
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
dateFormat = dateFormat,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
onChapterSelected = onChapterSelected,
|
||||
onChapterSwipe = onChapterSwipe,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -420,6 +440,8 @@ fun MangaScreenLargeImpl(
|
||||
snackbarHostState: SnackbarHostState,
|
||||
dateRelativeTime: Int,
|
||||
dateFormat: DateFormat,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onBackClicked: () -> Unit,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
||||
@ -452,6 +474,9 @@ fun MangaScreenLargeImpl(
|
||||
onMarkPreviousAsReadClicked: (Chapter) -> Unit,
|
||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||
|
||||
// For swipe actions
|
||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
|
||||
// Chapter selection
|
||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
||||
onAllChapterSelected: (Boolean) -> Unit,
|
||||
@ -616,9 +641,12 @@ fun MangaScreenLargeImpl(
|
||||
chapters = chapters,
|
||||
dateRelativeTime = dateRelativeTime,
|
||||
dateFormat = dateFormat,
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
onChapterClicked = onChapterClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
onChapterSelected = onChapterSelected,
|
||||
onChapterSwipe = onChapterSwipe,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -675,9 +703,12 @@ private fun LazyListScope.sharedChapterItems(
|
||||
chapters: List<ChapterItem>,
|
||||
dateRelativeTime: Int,
|
||||
dateFormat: DateFormat,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onChapterClicked: (Chapter) -> Unit,
|
||||
onDownloadChapter: ((List<ChapterItem>, ChapterDownloadAction) -> Unit)?,
|
||||
onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit,
|
||||
onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
) {
|
||||
items(
|
||||
items = chapters,
|
||||
@ -720,6 +751,8 @@ private fun LazyListScope.sharedChapterItems(
|
||||
downloadIndicatorEnabled = chapters.fastAll { !it.selected },
|
||||
downloadStateProvider = { chapterItem.downloadState },
|
||||
downloadProgressProvider = { chapterItem.downloadProgress },
|
||||
chapterSwipeEndAction = chapterSwipeEndAction,
|
||||
chapterSwipeStartAction = chapterSwipeStartAction,
|
||||
onLongClick = {
|
||||
onChapterSelected(chapterItem, !chapterItem.selected, true, true)
|
||||
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
@ -737,6 +770,9 @@ private fun LazyListScope.sharedChapterItems(
|
||||
} else {
|
||||
null
|
||||
},
|
||||
onChapterSwipe = {
|
||||
onChapterSwipe(chapterItem, it)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,41 @@
|
||||
package eu.kanade.presentation.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.foundation.shape.RoundedCornerShape
|
||||
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.BookmarkRemove
|
||||
import androidx.compose.material.icons.filled.Circle
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.FileDownloadOff
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
import androidx.compose.material.rememberDismissState
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ProvideTextStyle
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.contentColorFor
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@ -23,6 +43,7 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
@ -30,9 +51,11 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
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.min
|
||||
|
||||
@Composable
|
||||
fun MangaChapterListItem(
|
||||
@ -47,103 +70,271 @@ fun MangaChapterListItem(
|
||||
downloadIndicatorEnabled: Boolean,
|
||||
downloadStateProvider: () -> Download.State,
|
||||
downloadProgressProvider: () -> Int,
|
||||
chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
|
||||
chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
|
||||
onLongClick: () -> Unit,
|
||||
onClick: () -> Unit,
|
||||
onDownloadClick: ((ChapterDownloadAction) -> Unit)?,
|
||||
onChapterSwipe: (LibraryPreferences.ChapterSwipeAction) -> Unit,
|
||||
) {
|
||||
val textAlpha = if (read) ReadItemAlpha else 1f
|
||||
val textSubtitleAlpha = if (read) ReadItemAlpha else SecondaryItemAlpha
|
||||
|
||||
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),
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
var textHeight by remember { mutableStateOf(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 },
|
||||
)
|
||||
}
|
||||
val chapterSwipeStartEnabled = chapterSwipeStartAction != LibraryPreferences.ChapterSwipeAction.Disabled
|
||||
val chapterSwipeEndEnabled = chapterSwipeEndAction != LibraryPreferences.ChapterSwipeAction.Disabled
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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 (onDownloadClick != null) {
|
||||
ChapterDownloadIndicator(
|
||||
enabled = downloadIndicatorEnabled,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
downloadStateProvider = downloadStateProvider,
|
||||
downloadProgressProvider = downloadProgressProvider,
|
||||
onClick = onDownloadClick,
|
||||
)
|
||||
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
|
||||
},
|
||||
)
|
||||
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 -> { }
|
||||
}
|
||||
}
|
||||
SwipeToDismiss(
|
||||
state = dismissState,
|
||||
directions = dismissDirections,
|
||||
background = {
|
||||
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
|
||||
}
|
||||
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 = {
|
||||
val animateCornerRatio = if (dismissState.offset.value != 0f) {
|
||||
min(
|
||||
dismissState.progress.fraction / .075f,
|
||||
1f,
|
||||
)
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val animateCornerShape = (8f * animateCornerRatio).dp
|
||||
val dismissContentAlpha =
|
||||
if (lastDismissDirection != null) animateDismissContentAlpha else 1f
|
||||
Card(
|
||||
modifier = modifier,
|
||||
colors = CardDefaults.elevatedCardColors(
|
||||
containerColor = Color.Transparent,
|
||||
),
|
||||
shape = RoundedCornerShape(animateCornerShape),
|
||||
) {
|
||||
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,
|
||||
) {
|
||||
var textHeight by remember { mutableStateOf(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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
read: Boolean,
|
||||
bookmark: Boolean,
|
||||
downloadState: Download.State,
|
||||
) {
|
||||
val imageVector = when (swipeAction) {
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleRead -> {
|
||||
if (!read) {
|
||||
Icons.Default.Visibility
|
||||
} else {
|
||||
Icons.Default.VisibilityOff
|
||||
}
|
||||
}
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> {
|
||||
if (!bookmark) {
|
||||
Icons.Default.Bookmark
|
||||
} else {
|
||||
Icons.Default.BookmarkRemove
|
||||
}
|
||||
}
|
||||
LibraryPreferences.ChapterSwipeAction.Download -> {
|
||||
when (downloadState) {
|
||||
Download.State.NOT_DOWNLOADED,
|
||||
Download.State.ERROR,
|
||||
-> { Icons.Default.Download }
|
||||
Download.State.QUEUE,
|
||||
Download.State.DOWNLOADING,
|
||||
-> { Icons.Default.FileDownloadOff }
|
||||
Download.State.DOWNLOADED -> { Icons.Default.Delete }
|
||||
}
|
||||
}
|
||||
LibraryPreferences.ChapterSwipeAction.Disabled -> {
|
||||
null
|
||||
}
|
||||
}
|
||||
imageVector?.let {
|
||||
Icon(
|
||||
modifier = modifier,
|
||||
imageVector = imageVector,
|
||||
tint = tint,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
return mutableListOf(
|
||||
getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences),
|
||||
getGlobalUpdateGroup(allCategories, libraryPreferences),
|
||||
getChapterSwipeActionsGroup(libraryPreferences),
|
||||
)
|
||||
}
|
||||
|
||||
@ -216,4 +217,38 @@ object SettingsLibraryScreen : SearchableSettings {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun getChapterSwipeActionsGroup(
|
||||
libraryPreferences: LibraryPreferences,
|
||||
): Preference.PreferenceGroup {
|
||||
val chapterSwipeEndActionPref = libraryPreferences.swipeEndAction()
|
||||
val chapterSwipeStartActionPref = libraryPreferences.swipeStartAction()
|
||||
|
||||
return Preference.PreferenceGroup(
|
||||
title = stringResource(R.string.pref_chapter_swipe),
|
||||
preferenceItems = listOf(
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = chapterSwipeEndActionPref,
|
||||
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),
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleRead to stringResource(R.string.action_mark_as_read),
|
||||
LibraryPreferences.ChapterSwipeAction.Download to stringResource(R.string.action_download),
|
||||
),
|
||||
),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = chapterSwipeStartActionPref,
|
||||
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),
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleRead to stringResource(R.string.action_mark_as_read),
|
||||
LibraryPreferences.ChapterSwipeAction.Download to stringResource(R.string.action_download),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +101,8 @@ class MangaScreen(
|
||||
dateRelativeTime = screenModel.relativeTime,
|
||||
dateFormat = screenModel.dateFormat,
|
||||
isTabletUi = isTabletUi(),
|
||||
chapterSwipeEndAction = screenModel.chapterSwipeEndAction,
|
||||
chapterSwipeStartAction = screenModel.chapterSwipeStartAction,
|
||||
onBackClicked = navigator::pop,
|
||||
onChapterClicked = { openChapter(context, it) },
|
||||
onDownloadChapter = screenModel::runChapterDownloadActions.takeIf { !successState.source.isLocalOrStub() },
|
||||
@ -125,6 +127,7 @@ class MangaScreen(
|
||||
onMultiMarkAsReadClicked = screenModel::markChaptersRead,
|
||||
onMarkPreviousAsReadClicked = screenModel::markPreviousChapterRead,
|
||||
onMultiDeleteClicked = screenModel::showDeleteChapterDialog,
|
||||
onChapterSwipe = screenModel::chapterSwipe,
|
||||
onChapterSelected = screenModel::toggleSelection,
|
||||
onAllChapterSelected = screenModel::toggleAllSelection,
|
||||
onInvertSelection = screenModel::invertSelection,
|
||||
|
@ -121,6 +121,9 @@ class MangaInfoScreenModel(
|
||||
private val filteredChapters: Sequence<ChapterItem>?
|
||||
get() = successState?.processedChapters
|
||||
|
||||
val chapterSwipeEndAction = libraryPreferences.swipeEndAction().get()
|
||||
val chapterSwipeStartAction = libraryPreferences.swipeStartAction().get()
|
||||
|
||||
val relativeTime by uiPreferences.relativeTime().asState(coroutineScope)
|
||||
val dateFormat by mutableStateOf(UiPreferences.dateFormat(uiPreferences.dateFormat().get()))
|
||||
private val skipFiltered by readerPreferences.skipFiltered().asState(coroutineScope)
|
||||
@ -523,6 +526,49 @@ class MangaInfoScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
||||
*/
|
||||
fun chapterSwipe(chapterItem: ChapterItem, swipeAction: LibraryPreferences.ChapterSwipeAction) {
|
||||
coroutineScope.launch {
|
||||
executeChapterSwipeAction(chapterItem, swipeAction)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
||||
*/
|
||||
private fun executeChapterSwipeAction(
|
||||
chapterItem: ChapterItem,
|
||||
swipeAction: LibraryPreferences.ChapterSwipeAction,
|
||||
) {
|
||||
val chapter = chapterItem.chapter
|
||||
when (swipeAction) {
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleRead -> {
|
||||
markChaptersRead(listOf(chapter), !chapter.read)
|
||||
}
|
||||
LibraryPreferences.ChapterSwipeAction.ToggleBookmark -> {
|
||||
bookmarkChapters(listOf(chapter), !chapter.bookmark)
|
||||
}
|
||||
LibraryPreferences.ChapterSwipeAction.Download -> {
|
||||
val downloadAction: ChapterDownloadAction = when (chapterItem.downloadState) {
|
||||
Download.State.ERROR,
|
||||
Download.State.NOT_DOWNLOADED,
|
||||
-> ChapterDownloadAction.START_NOW
|
||||
Download.State.QUEUE,
|
||||
Download.State.DOWNLOADING,
|
||||
-> ChapterDownloadAction.CANCEL
|
||||
Download.State.DOWNLOADED -> ChapterDownloadAction.DELETE
|
||||
}
|
||||
runChapterDownloadActions(
|
||||
items = listOf(chapterItem),
|
||||
action = downloadAction,
|
||||
)
|
||||
}
|
||||
LibraryPreferences.ChapterSwipeAction.Disabled -> throw IllegalStateException()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next unread chapter or null if everything is read.
|
||||
*/
|
||||
|
@ -118,6 +118,21 @@ class LibraryPreferences(
|
||||
|
||||
// endregion
|
||||
|
||||
// region Swipe Actions
|
||||
|
||||
fun swipeEndAction() = preferenceStore.getEnum("pref_chapter_swipe_end_action", ChapterSwipeAction.ToggleBookmark)
|
||||
|
||||
fun swipeStartAction() = preferenceStore.getEnum("pref_chapter_swipe_start_action", ChapterSwipeAction.ToggleRead)
|
||||
|
||||
// endregion
|
||||
|
||||
enum class ChapterSwipeAction {
|
||||
ToggleRead,
|
||||
ToggleBookmark,
|
||||
Download,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DEVICE_ONLY_ON_WIFI = "wifi"
|
||||
const val DEVICE_NETWORK_NOT_METERED = "network_not_metered"
|
||||
|
@ -167,7 +167,7 @@
|
||||
|
||||
<string name="pref_general_summary">App language, notifications</string>
|
||||
<string name="pref_appearance_summary">Theme, date & time format</string>
|
||||
<string name="pref_library_summary">Categories, global update</string>
|
||||
<string name="pref_library_summary">Categories, global update, chapter swipe</string>
|
||||
<string name="pref_reader_summary">Reading mode, display, navigation</string>
|
||||
<string name="pref_downloads_summary">Automatic download, download ahead</string>
|
||||
<string name="pref_tracking_summary">One-way progress sync, enhanced sync</string>
|
||||
@ -273,6 +273,10 @@
|
||||
<string name="include">Include: %s</string>
|
||||
<string name="exclude">Exclude: %s</string>
|
||||
|
||||
<string name="pref_chapter_swipe">Chapter swipe</string>
|
||||
<string name="pref_chapter_swipe_end">Swipe to end action</string>
|
||||
<string name="pref_chapter_swipe_start">Swipe to start action</string>
|
||||
|
||||
<!-- Extension section -->
|
||||
<string name="multi_lang">Multi</string>
|
||||
<string name="ext_updates_pending">Updates pending</string>
|
||||
|
Loading…
Reference in New Issue
Block a user