From bb2ffc2dbe4bb323d5b682e7d415d1f2f7f52dcb 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 (cherry picked from commit a8f17a3fabae7070a353661873c7a5ae1ae23eca) # Conflicts: # app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt # app/src/main/java/eu/kanade/presentation/manga/components/MangaChapterListItem.kt # app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt # domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt --- .../kanade/presentation/manga/MangaScreen.kt | 36 ++ .../manga/components/MangaChapterListItem.kt | 387 +++++++++++++----- .../settings/screen/SettingsLibraryScreen.kt | 35 ++ .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 3 + .../tachiyomi/ui/manga/MangaScreenModel.kt | 46 +++ .../tachiyomi/core/util/system/ImageUtil.kt | 2 +- .../library/service/LibraryPreferences.kt | 15 + i18n/src/main/res/values/strings.xml | 6 +- 8 files changed, 430 insertions(+), 100 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 2351c5246..a6537986a 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt @@ -90,6 +90,7 @@ import exh.ui.metadata.adapters.PururinDescription import exh.ui.metadata.adapters.TsuminoDescription 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 @@ -110,6 +111,8 @@ fun MangaScreen( dateRelativeTime: Int, dateFormat: DateFormat, isTabletUi: Boolean, + chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, + chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, @@ -151,6 +154,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, @@ -169,6 +175,8 @@ fun MangaScreen( snackbarHostState = snackbarHostState, dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, + chapterSwipeEndAction = chapterSwipeEndAction, + chapterSwipeStartAction = chapterSwipeStartAction, onBackClicked = onBackClicked, onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, @@ -201,6 +209,7 @@ fun MangaScreen( onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onMultiDeleteClicked = onMultiDeleteClicked, + onChapterSwipe = onChapterSwipe, onChapterSelected = onChapterSelected, onAllChapterSelected = onAllChapterSelected, onInvertSelection = onInvertSelection, @@ -210,6 +219,8 @@ fun MangaScreen( state = state, snackbarHostState = snackbarHostState, dateRelativeTime = dateRelativeTime, + chapterSwipeEndAction = chapterSwipeEndAction, + chapterSwipeStartAction = chapterSwipeStartAction, dateFormat = dateFormat, onBackClicked = onBackClicked, onChapterClicked = onChapterClicked, @@ -243,6 +254,7 @@ fun MangaScreen( onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onMultiDeleteClicked = onMultiDeleteClicked, + onChapterSwipe = onChapterSwipe, onChapterSelected = onChapterSelected, onAllChapterSelected = onAllChapterSelected, onInvertSelection = onInvertSelection, @@ -256,6 +268,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)?, @@ -298,6 +312,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, @@ -530,12 +547,15 @@ private fun MangaScreenSmallImpl( chapters = chapters, dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, + chapterSwipeEndAction = chapterSwipeEndAction, + chapterSwipeStartAction = chapterSwipeStartAction, // SY --> alwaysShowReadingProgress = state.alwaysShowReadingProgress, // SY <-- onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, onChapterSelected = onChapterSelected, + onChapterSwipe = onChapterSwipe, ) } } @@ -549,6 +569,8 @@ fun MangaScreenLargeImpl( snackbarHostState: SnackbarHostState, dateRelativeTime: Int, dateFormat: DateFormat, + chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, + chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, @@ -591,6 +613,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, @@ -798,12 +823,15 @@ fun MangaScreenLargeImpl( chapters = chapters, dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, + chapterSwipeEndAction = chapterSwipeEndAction, + chapterSwipeStartAction = chapterSwipeStartAction, // SY --> alwaysShowReadingProgress = state.alwaysShowReadingProgress, // SY <-- onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, onChapterSelected = onChapterSelected, + onChapterSwipe = onChapterSwipe, ) } } @@ -860,12 +888,15 @@ private fun LazyListScope.sharedChapterItems( chapters: List, dateRelativeTime: Int, dateFormat: DateFormat, + chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, + chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, // SY --> alwaysShowReadingProgress: Boolean, // SY <-- onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, + onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, ) { items( items = chapters, @@ -917,6 +948,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) @@ -934,6 +967,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 757b30882..3d42bed54 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( @@ -50,113 +73,281 @@ 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 /* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() - } - if (readProgress != null) { - Text( - text = readProgress, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.alpha(ReadItemAlpha), - ) - if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() - } - // SY --> - if (sourceName != null) { - Text( - text = sourceName, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - ) - if (scanlator != null) DotSeparatorText() - } - // SY <-- - 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 /* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() + } + if (readProgress != null) { + Text( + text = readProgress, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.alpha(ReadItemAlpha), + ) + if (scanlator != null/* SY --> */ || sourceName != null/* SY <-- */) DotSeparatorText() + } + // SY --> + if (sourceName != null) { + Text( + text = sourceName, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + if (scanlator != null) DotSeparatorText() + } + // SY <-- + 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 c0fd3f059..1244697ef 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 @@ -64,6 +64,7 @@ object SettingsLibraryScreen : SearchableSettings { return mutableListOf( getCategoriesGroup(LocalNavigator.currentOrThrow, allCategories, libraryPreferences), getGlobalUpdateGroup(allCategories, libraryPreferences), + getChapterSwipeActionsGroup(libraryPreferences), // SY --> getSortingCategory(LocalNavigator.currentOrThrow, libraryPreferences), getMigrationCategory(unsortedPreferences), @@ -238,6 +239,40 @@ 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), + ), + ), + ), + ) + } + // SY --> @Composable fun getSortingCategory(navigator: Navigator, libraryPreferences: LibraryPreferences): Preference.PreferenceGroup { 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 7bc297898..0ba0199e8 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 @@ -140,6 +140,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() }, @@ -182,6 +184,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 02d881aaf..3f9479980 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 @@ -185,6 +185,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) @@ -998,6 +1001,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/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt index 37e0e124d..688972faa 100644 --- a/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt +++ b/core/src/main/java/tachiyomi/core/util/system/ImageUtil.kt @@ -265,7 +265,7 @@ object ImageUtil { /** * Splits tall images to improve performance of reader */ - fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String, ): Boolean { + fun splitTallImage(tmpDir: UniFile, imageFile: UniFile, filenamePrefix: String): Boolean { if (isAnimatedAndSupported(imageFile.openInputStream()) || !isTallImage(imageFile.openInputStream())) { return true } 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 a970dbdd3..546e02c04 100644 --- a/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt +++ b/domain/src/main/java/tachiyomi/domain/library/service/LibraryPreferences.kt @@ -124,6 +124,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, + } + // SY --> fun sortTagsForLibrary() = preferenceStore.getStringSet("sort_tags_for_library", mutableSetOf()) diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 043a6f9c7..2f4c093d6 100755 --- 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