From a8f17a3fabae7070a353661873c7a5ae1ae23eca Mon Sep 17 00:00:00 2001 From: d-najd <59766732+d-najd@users.noreply.github.com> Date: Tue, 25 Apr 2023 23:29:39 +0200 Subject: [PATCH] Add swipe actions for chapters (#9304) * added chapter swipe * Rework corner animtion * Update i18n/src/main/res/values/strings.xml Co-authored-by: arkon * 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 --- .../kanade/presentation/manga/MangaScreen.kt | 36 ++ .../manga/components/MangaChapterListItem.kt | 367 +++++++++++++----- .../settings/screen/SettingsLibraryScreen.kt | 35 ++ .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 3 + .../tachiyomi/ui/manga/MangaScreenModel.kt | 46 +++ .../library/service/LibraryPreferences.kt | 15 + i18n/src/main/res/values/strings.xml | 6 +- 7 files changed, 419 insertions(+), 89 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt index f778082b3c..1d2c27e8c5 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -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, ChapterDownloadAction) -> Unit)?, @@ -117,6 +120,9 @@ fun MangaScreen( onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMultiDeleteClicked: (List) -> 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, ChapterDownloadAction) -> Unit)?, @@ -234,6 +248,9 @@ private fun MangaScreenSmallImpl( onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMultiDeleteClicked: (List) -> 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, ChapterDownloadAction) -> Unit)?, @@ -452,6 +474,9 @@ fun MangaScreenLargeImpl( onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMultiDeleteClicked: (List) -> 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, dateRelativeTime: Int, dateFormat: DateFormat, + chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, + chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, 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) + }, ) } } diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt b/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt index 3a21ec0ed2..d6e22477f1 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt @@ -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() } + 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, + ) + } } diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt index d81c2118c0..3b6326471c 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt @@ -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), + ), + ), + ), + ) + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index dcf4efccd6..49f064da82 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index aab7179f35..875a10d779 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -121,6 +121,9 @@ class MangaInfoScreenModel( private val filteredChapters: Sequence? 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. */ diff --git a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt index 4dd83fd2a5..d6f0ad1412 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -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" diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 6f6f7f61f7..c5daf72518 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -167,7 +167,7 @@ App language, notifications Theme, date & time format - Categories, global update + Categories, global update, chapter swipe Reading mode, display, navigation Automatic download, download ahead One-way progress sync, enhanced sync @@ -273,6 +273,10 @@ Include: %s Exclude: %s + Chapter swipe + Swipe to end action + Swipe to start action + Multi Updates pending