mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	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:
		@@ -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.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user