Add "Play" button on manga in library (#8218)

* resume manga button in libarary

* work on resume button

* Backup

* work on opening the last read chapter

* backup

* renaming

* fab instead of image

* done with logic

* cleanup

* cleanup

* import cleanup

* cleanup...

* refactoring

* fixing logic

* fixing scopes

* Reworking design

* adding ability to turn on/off the feature

* cleanup

* refactoring, fixing logic, adding filter logic (partial)

* backup

* backup

* logic done

* backup before merge fix

* merge conflict....

* merge conflict...

* reworking ui logic

* removing unnecessary file

* refactoring

* refactoring

* review changes + minor parameter position movement

* commiting suggestion

Co-authored-by: arkon <arkon@users.noreply.github.com>

* fixing minor mistake

* moving ChapterFilter.kt

Co-authored-by: arkon <arkon@users.noreply.github.com>
This commit is contained in:
d-najd 2022-11-08 04:32:23 +01:00 committed by GitHub
parent bf9edda04c
commit ba00d9e5d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 271 additions and 48 deletions

View File

@ -0,0 +1,82 @@
package eu.kanade.domain.chapter.model
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.ui.manga.ChapterItem
import eu.kanade.tachiyomi.util.chapter.getChapterSort
/**
* Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted.
*/
fun List<Chapter>.applyFilters(manga: Manga, downloadManager: DownloadManager): List<Chapter> {
val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter
val bookmarkedFilter = manga.bookmarkedFilter
return filter { chapter ->
when (unreadFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> !chapter.read
TriStateFilter.ENABLED_NOT -> chapter.read
}
}
.filter { chapter ->
when (bookmarkedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> chapter.bookmark
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
}
}
.filter { chapter ->
val downloaded = downloadManager.isChapterDownloaded(chapter.name, chapter.scanlator, manga.title, manga.source)
val downloadState = when {
downloaded -> Download.State.DOWNLOADED
else -> Download.State.NOT_DOWNLOADED
}
when (downloadedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> downloadState == Download.State.DOWNLOADED || isLocalManga
TriStateFilter.ENABLED_NOT -> downloadState != Download.State.DOWNLOADED && !isLocalManga
}
}
.sortedWith(getChapterSort(manga))
}
/**
* Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted.
*/
fun List<ChapterItem>.applyFilters(manga: Manga): Sequence<ChapterItem> {
val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter
val bookmarkedFilter = manga.bookmarkedFilter
return asSequence()
.filter { (chapter) ->
when (unreadFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> !chapter.read
TriStateFilter.ENABLED_NOT -> chapter.read
}
}
.filter { (chapter) ->
when (bookmarkedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> chapter.bookmark
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
}
}
.filter {
when (downloadedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> it.isDownloaded || isLocalManga
TriStateFilter.ENABLED_NOT -> !it.isDownloaded && !isLocalManga
}
}
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
}

View File

@ -32,6 +32,8 @@ class LibraryPreferences(
fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false) fun autoUpdateTrackers() = preferenceStore.getBoolean("auto_update_trackers", false)
fun showContinueReadingButton() = preferenceStore.getBoolean("display_continue_reading_button", false)
// region Filter // region Filter
fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value) fun filterDownloaded() = preferenceStore.getInt("pref_filter_library_downloaded", ExtendedNavigationView.Item.TriStateGroup.State.IGNORE.value)

View File

@ -12,10 +12,17 @@ import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LocalContentColor import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -41,6 +48,7 @@ object CommonMangaItemDefaults {
const val BrowseFavoriteCoverAlpha = 0.34f const val BrowseFavoriteCoverAlpha = 0.34f
} }
private val ContinueReadingButtonSize = 38.dp
private const val GridSelectedCoverAlpha = 0.76f private const val GridSelectedCoverAlpha = 0.76f
/** /**
@ -55,8 +63,10 @@ fun MangaCompactGridItem(
coverAlpha: Float = 1f, coverAlpha: Float = 1f,
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null, coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null, coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
showContinueReadingButton: Boolean = false,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onClickContinueReading: (() -> Unit)? = null,
) { ) {
GridItemSelectable( GridItemSelectable(
isSelected = isSelected, isSelected = isSelected,
@ -76,7 +86,12 @@ fun MangaCompactGridItem(
badgesEnd = coverBadgeEnd, badgesEnd = coverBadgeEnd,
content = { content = {
if (title != null) { if (title != null) {
CoverTextOverlay(title = title) CoverTextOverlay(title = title, showContinueReadingButton)
}
},
continueReadingButton = {
if (showContinueReadingButton && onClickContinueReading != null) {
ContinueReadingButton(onClickContinueReading)
} }
}, },
) )
@ -87,7 +102,10 @@ fun MangaCompactGridItem(
* Title overlay for [MangaCompactGridItem] * Title overlay for [MangaCompactGridItem]
*/ */
@Composable @Composable
private fun BoxScope.CoverTextOverlay(title: String) { private fun BoxScope.CoverTextOverlay(
title: String,
showContinueReadingButton: Boolean = false,
) {
Box( Box(
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp)) .clip(RoundedCornerShape(bottomStart = 4.dp, bottomEnd = 4.dp))
@ -101,9 +119,10 @@ private fun BoxScope.CoverTextOverlay(title: String) {
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.BottomCenter), .align(Alignment.BottomCenter),
) )
val endPadding = if (showContinueReadingButton) ContinueReadingButtonSize else 8.dp
GridItemTitle( GridItemTitle(
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(start = 8.dp, top = 8.dp, end = endPadding, bottom = 8.dp)
.align(Alignment.BottomStart), .align(Alignment.BottomStart),
title = title, title = title,
style = MaterialTheme.typography.titleSmall.copy( style = MaterialTheme.typography.titleSmall.copy(
@ -127,8 +146,10 @@ fun MangaComfortableGridItem(
coverAlpha: Float = 1f, coverAlpha: Float = 1f,
coverBadgeStart: (@Composable RowScope.() -> Unit)? = null, coverBadgeStart: (@Composable RowScope.() -> Unit)? = null,
coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null, coverBadgeEnd: (@Composable RowScope.() -> Unit)? = null,
showContinueReadingButton: Boolean = false,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onClickContinueReading: (() -> Unit)? = null,
) { ) {
GridItemSelectable( GridItemSelectable(
isSelected = isSelected, isSelected = isSelected,
@ -147,6 +168,11 @@ fun MangaComfortableGridItem(
}, },
badgesStart = coverBadgeStart, badgesStart = coverBadgeStart,
badgesEnd = coverBadgeEnd, badgesEnd = coverBadgeEnd,
continueReadingButton = {
if (showContinueReadingButton && onClickContinueReading != null) {
ContinueReadingButton(onClickContinueReading)
}
},
) )
GridItemTitle( GridItemTitle(
modifier = Modifier.padding(4.dp), modifier = Modifier.padding(4.dp),
@ -166,6 +192,7 @@ private fun MangaGridCover(
cover: @Composable BoxScope.() -> Unit = {}, cover: @Composable BoxScope.() -> Unit = {},
badgesStart: (@Composable RowScope.() -> Unit)? = null, badgesStart: (@Composable RowScope.() -> Unit)? = null,
badgesEnd: (@Composable RowScope.() -> Unit)? = null, badgesEnd: (@Composable RowScope.() -> Unit)? = null,
continueReadingButton: (@Composable BoxScope.() -> Unit)? = null,
content: @Composable (BoxScope.() -> Unit)? = null, content: @Composable (BoxScope.() -> Unit)? = null,
) { ) {
Box( Box(
@ -192,6 +219,7 @@ private fun MangaGridCover(
content = badgesEnd, content = badgesEnd,
) )
} }
continueReadingButton?.invoke(this)
} }
} }
@ -283,8 +311,10 @@ fun MangaListItem(
coverData: eu.kanade.domain.manga.model.MangaCover, coverData: eu.kanade.domain.manga.model.MangaCover,
coverAlpha: Float = 1f, coverAlpha: Float = 1f,
badge: @Composable RowScope.() -> Unit, badge: @Composable RowScope.() -> Unit,
showContinueReadingButton: Boolean = false,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onClick: () -> Unit, onClick: () -> Unit,
onClickContinueReading: (() -> Unit)? = null,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -313,5 +343,37 @@ fun MangaListItem(
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
) )
BadgeGroup(content = badge) BadgeGroup(content = badge)
if (showContinueReadingButton && onClickContinueReading != null) {
Box {
ContinueReadingButton(onClickContinueReading)
}
}
}
}
@Composable
private fun BoxScope.ContinueReadingButton(
onClickContinueReading: () -> Unit,
) {
FilledIconButton(
onClick = {
onClickContinueReading()
},
modifier = Modifier
.size(ContinueReadingButtonSize)
.padding(3.dp)
.align(Alignment.BottomEnd),
shape = MaterialTheme.shapes.small,
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = contentColorFor(MaterialTheme.colorScheme.primaryContainer),
),
) {
Icon(
imageVector = Icons.Filled.PlayArrow,
contentDescription = "",
modifier = Modifier
.size(15.dp),
)
} }
} }

View File

@ -11,6 +11,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAll
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.library.model.display import eu.kanade.domain.library.model.display
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.presentation.components.EmptyScreen import eu.kanade.presentation.components.EmptyScreen
@ -29,6 +30,7 @@ import eu.kanade.tachiyomi.widget.TachiyomiBottomNavigationView
fun LibraryScreen( fun LibraryScreen(
presenter: LibraryPresenter, presenter: LibraryPresenter,
onMangaClicked: (Long) -> Unit, onMangaClicked: (Long) -> Unit,
onContinueReadingClicked: (LibraryManga) -> Unit,
onGlobalSearchClicked: () -> Unit, onGlobalSearchClicked: () -> Unit,
onChangeCategoryClicked: () -> Unit, onChangeCategoryClicked: () -> Unit,
onMarkAsReadClicked: () -> Unit, onMarkAsReadClicked: () -> Unit,
@ -104,6 +106,7 @@ fun LibraryScreen(
showMangaCount = presenter.mangaCountVisibility, showMangaCount = presenter.mangaCountVisibility,
onChangeCurrentPage = { presenter.activeCategory = it }, onChangeCurrentPage = { presenter.activeCategory = it },
onMangaClicked = onMangaClicked, onMangaClicked = onMangaClicked,
onContinueReadingClicked = onContinueReadingClicked,
onToggleSelection = { presenter.toggleSelection(it) }, onToggleSelection = { presenter.toggleSelection(it) },
onToggleRangeSelection = { onToggleRangeSelection = {
presenter.toggleRangeSelection(it) presenter.toggleRangeSelection(it)
@ -119,6 +122,7 @@ fun LibraryScreen(
showUnreadBadges = presenter.showUnreadBadges, showUnreadBadges = presenter.showUnreadBadges,
showLocalBadges = presenter.showLocalBadges, showLocalBadges = presenter.showLocalBadges,
showLanguageBadges = presenter.showLanguageBadges, showLanguageBadges = presenter.showLanguageBadges,
showContinueReadingButton = presenter.showContinueReadingButton,
isIncognitoMode = presenter.isIncognitoMode, isIncognitoMode = presenter.isIncognitoMode,
isDownloadOnly = presenter.isDownloadOnly, isDownloadOnly = presenter.isDownloadOnly,
) )

View File

@ -18,11 +18,13 @@ fun LibraryComfortableGrid(
showUnreadBadges: Boolean, showUnreadBadges: Boolean,
showLocalBadges: Boolean, showLocalBadges: Boolean,
showLanguageBadges: Boolean, showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
columns: Int, columns: Int,
contentPadding: PaddingValues, contentPadding: PaddingValues,
selection: List<LibraryManga>, selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit, onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit, onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
searchQuery: String?, searchQuery: String?,
onGlobalSearchClicked: () -> Unit, onGlobalSearchClicked: () -> Unit,
) { ) {
@ -65,8 +67,10 @@ fun LibraryComfortableGrid(
item = libraryItem, item = libraryItem,
) )
}, },
showContinueReadingButton = showContinueReadingButton,
onLongClick = { onLongClick(libraryItem.libraryManga) }, onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) }, onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
) )
} }
} }

View File

@ -19,11 +19,13 @@ fun LibraryCompactGrid(
showUnreadBadges: Boolean, showUnreadBadges: Boolean,
showLocalBadges: Boolean, showLocalBadges: Boolean,
showLanguageBadges: Boolean, showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
columns: Int, columns: Int,
contentPadding: PaddingValues, contentPadding: PaddingValues,
selection: List<LibraryManga>, selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit, onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit, onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
searchQuery: String?, searchQuery: String?,
onGlobalSearchClicked: () -> Unit, onGlobalSearchClicked: () -> Unit,
) { ) {
@ -66,8 +68,10 @@ fun LibraryCompactGrid(
item = libraryItem, item = libraryItem,
) )
}, },
showContinueReadingButton = showContinueReadingButton,
onLongClick = { onLongClick(libraryItem.libraryManga) }, onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) }, onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
) )
} }
} }

View File

@ -37,6 +37,7 @@ fun LibraryContent(
showMangaCount: Boolean, showMangaCount: Boolean,
onChangeCurrentPage: (Int) -> Unit, onChangeCurrentPage: (Int) -> Unit,
onMangaClicked: (Long) -> Unit, onMangaClicked: (Long) -> Unit,
onContinueReadingClicked: (LibraryManga) -> Unit,
onToggleSelection: (LibraryManga) -> Unit, onToggleSelection: (LibraryManga) -> Unit,
onToggleRangeSelection: (LibraryManga) -> Unit, onToggleRangeSelection: (LibraryManga) -> Unit,
onRefresh: (Category?) -> Boolean, onRefresh: (Category?) -> Boolean,
@ -49,6 +50,7 @@ fun LibraryContent(
showUnreadBadges: Boolean, showUnreadBadges: Boolean,
showLocalBadges: Boolean, showLocalBadges: Boolean,
showLanguageBadges: Boolean, showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
isDownloadOnly: Boolean, isDownloadOnly: Boolean,
isIncognitoMode: Boolean, isIncognitoMode: Boolean,
) { ) {
@ -88,6 +90,9 @@ fun LibraryContent(
val onLongClickManga = { manga: LibraryManga -> val onLongClickManga = { manga: LibraryManga ->
onToggleRangeSelection(manga) onToggleRangeSelection(manga)
} }
val onClickContinueReading = { manga: LibraryManga ->
onContinueReadingClicked(manga)
}
SwipeRefresh( SwipeRefresh(
refreshing = isRefreshing, refreshing = isRefreshing,
@ -115,8 +120,10 @@ fun LibraryContent(
showUnreadBadges = showUnreadBadges, showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges, showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges, showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
onClickManga = onClickManga, onClickManga = onClickManga,
onLongClickManga = onLongClickManga, onLongClickManga = onLongClickManga,
onClickContinueReading = onClickContinueReading,
onGlobalSearchClicked = onGlobalSearchClicked, onGlobalSearchClicked = onGlobalSearchClicked,
searchQuery = state.searchQuery, searchQuery = state.searchQuery,
) )

View File

@ -27,10 +27,12 @@ fun LibraryList(
showUnreadBadges: Boolean, showUnreadBadges: Boolean,
showLocalBadges: Boolean, showLocalBadges: Boolean,
showLanguageBadges: Boolean, showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
contentPadding: PaddingValues, contentPadding: PaddingValues,
selection: List<LibraryManga>, selection: List<LibraryManga>,
onClick: (LibraryManga) -> Unit, onClick: (LibraryManga) -> Unit,
onLongClick: (LibraryManga) -> Unit, onLongClick: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
searchQuery: String?, searchQuery: String?,
onGlobalSearchClicked: () -> Unit, onGlobalSearchClicked: () -> Unit,
) { ) {
@ -72,8 +74,10 @@ fun LibraryList(
UnreadBadge(enabled = showUnreadBadges, item = libraryItem) UnreadBadge(enabled = showUnreadBadges, item = libraryItem)
LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem) LanguageBadge(showLanguage = showLanguageBadges, showLocal = showLocalBadges, item = libraryItem)
}, },
showContinueReadingButton = showContinueReadingButton,
onLongClick = { onLongClick(libraryItem.libraryManga) }, onLongClick = { onLongClick(libraryItem.libraryManga) },
onClick = { onClick(libraryItem.libraryManga) }, onClick = { onClick(libraryItem.libraryManga) },
onClickContinueReading = { onClickContinueReading(libraryItem.libraryManga) },
) )
} }
} }

View File

@ -32,8 +32,10 @@ fun LibraryPager(
showUnreadBadges: Boolean, showUnreadBadges: Boolean,
showLocalBadges: Boolean, showLocalBadges: Boolean,
showLanguageBadges: Boolean, showLanguageBadges: Boolean,
showContinueReadingButton: Boolean,
onClickManga: (LibraryManga) -> Unit, onClickManga: (LibraryManga) -> Unit,
onLongClickManga: (LibraryManga) -> Unit, onLongClickManga: (LibraryManga) -> Unit,
onClickContinueReading: (LibraryManga) -> Unit,
) { ) {
HorizontalPager( HorizontalPager(
count = pageCount, count = pageCount,
@ -64,10 +66,12 @@ fun LibraryPager(
showUnreadBadges = showUnreadBadges, showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges, showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges, showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
contentPadding = contentPadding, contentPadding = contentPadding,
selection = selectedManga, selection = selectedManga,
onClick = onClickManga, onClick = onClickManga,
onLongClick = onLongClickManga, onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,
searchQuery = searchQuery, searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked, onGlobalSearchClicked = onGlobalSearchClicked,
) )
@ -80,11 +84,13 @@ fun LibraryPager(
showUnreadBadges = showUnreadBadges, showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges, showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges, showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
columns = columns, columns = columns,
contentPadding = contentPadding, contentPadding = contentPadding,
selection = selectedManga, selection = selectedManga,
onClick = onClickManga, onClick = onClickManga,
onLongClick = onLongClickManga, onLongClick = onLongClickManga,
onClickContinueReading = onClickContinueReading,
searchQuery = searchQuery, searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked, onGlobalSearchClicked = onGlobalSearchClicked,
) )
@ -96,10 +102,12 @@ fun LibraryPager(
showUnreadBadges = showUnreadBadges, showUnreadBadges = showUnreadBadges,
showLocalBadges = showLocalBadges, showLocalBadges = showLocalBadges,
showLanguageBadges = showLanguageBadges, showLanguageBadges = showLanguageBadges,
showContinueReadingButton = showContinueReadingButton,
columns = columns, columns = columns,
contentPadding = contentPadding, contentPadding = contentPadding,
selection = selectedManga, selection = selectedManga,
onClick = onClickManga, onClick = onClickManga,
onClickContinueReading = onClickContinueReading,
onLongClick = onLongClickManga, onLongClick = onLongClickManga,
searchQuery = searchQuery, searchQuery = searchQuery,
onGlobalSearchClicked = onGlobalSearchClicked, onGlobalSearchClicked = onGlobalSearchClicked,

View File

@ -9,6 +9,8 @@ import androidx.compose.ui.platform.LocalContext
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import eu.kanade.core.prefs.CheckboxState import eu.kanade.core.prefs.CheckboxState
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.library.model.LibraryManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.isLocal import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
@ -26,6 +28,7 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.category.CategoryController import eu.kanade.tachiyomi.ui.category.CategoryController
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
@ -50,6 +53,7 @@ class LibraryController(
LibraryScreen( LibraryScreen(
presenter = presenter, presenter = presenter,
onMangaClicked = ::openManga, onMangaClicked = ::openManga,
onContinueReadingClicked = ::continueReading,
onGlobalSearchClicked = { onGlobalSearchClicked = {
router.pushController(GlobalSearchController(presenter.searchQuery)) router.pushController(GlobalSearchController(presenter.searchQuery))
}, },
@ -196,6 +200,19 @@ class LibraryController(
router.pushController(MangaController(mangaId)) router.pushController(MangaController(mangaId))
} }
private fun continueReading(libraryManga: LibraryManga) {
viewScope.launchIO {
val chapter = presenter.getNextUnreadChapter(libraryManga.manga)
if (chapter != null) openChapter(chapter)
}
}
private fun openChapter(chapter: Chapter) {
activity?.run {
startActivity(ReaderActivity.newIntent(this, chapter.mangaId, chapter.id))
}
}
/** /**
* Clear all of the manga currently selected, and * Clear all of the manga currently selected, and
* invalidate the action mode to revert the top toolbar * invalidate the action mode to revert the top toolbar

View File

@ -17,7 +17,9 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.category.model.Category import eu.kanade.domain.category.model.Category
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SetReadStatus import eu.kanade.domain.chapter.interactor.SetReadStatus
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.history.interactor.GetNextChapters import eu.kanade.domain.history.interactor.GetNextChapters
import eu.kanade.domain.library.model.LibraryManga import eu.kanade.domain.library.model.LibraryManga
@ -44,6 +46,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
@ -76,9 +79,10 @@ typealias LibraryMap = Map<Long, List<LibraryItem>>
class LibraryPresenter( class LibraryPresenter(
private val state: LibraryStateImpl = LibraryState() as LibraryStateImpl, private val state: LibraryStateImpl = LibraryState() as LibraryStateImpl,
private val getLibraryManga: GetLibraryManga = Injekt.get(), private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getTracksPerManga: GetTracksPerManga = Injekt.get(),
private val getNextChapters: GetNextChapters = Injekt.get(), private val getNextChapters: GetNextChapters = Injekt.get(),
private val getChaptersByMangaId: GetChapterByMangaId = Injekt.get(),
private val setReadStatus: SetReadStatus = Injekt.get(), private val setReadStatus: SetReadStatus = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(),
@ -105,6 +109,8 @@ class LibraryPresenter(
var activeCategory: Int by libraryPreferences.lastUsedCategory().asState() var activeCategory: Int by libraryPreferences.lastUsedCategory().asState()
val showContinueReadingButton by libraryPreferences.showContinueReadingButton().asState()
val isDownloadOnly: Boolean by preferences.downloadedOnly().asState() val isDownloadOnly: Boolean by preferences.downloadedOnly().asState()
val isIncognitoMode: Boolean by preferences.incognitoMode().asState() val isIncognitoMode: Boolean by preferences.incognitoMode().asState()
@ -389,6 +395,10 @@ class LibraryPresenter(
.reduce { set1, set2 -> set1.intersect(set2) } .reduce { set1, set2 -> set1.intersect(set2) }
} }
suspend fun getNextUnreadChapter(manga: Manga): Chapter? {
return getChaptersByMangaId.await(manga.id).getNextUnread(manga, downloadManager)
}
/** /**
* Returns the mix (non-common) categories for the given list of manga. * Returns the mix (non-common) categories for the given list of manga.
* *

View File

@ -282,12 +282,14 @@ class LibrarySettingsSheet(
private val displayGroup: DisplayGroup private val displayGroup: DisplayGroup
private val badgeGroup: BadgeGroup private val badgeGroup: BadgeGroup
private val tabsGroup: TabsGroup private val tabsGroup: TabsGroup
private val otherGroup: OtherGroup
init { init {
displayGroup = DisplayGroup() displayGroup = DisplayGroup()
badgeGroup = BadgeGroup() badgeGroup = BadgeGroup()
tabsGroup = TabsGroup() tabsGroup = TabsGroup()
setGroups(listOf(displayGroup, badgeGroup, tabsGroup)) otherGroup = OtherGroup()
setGroups(listOf(displayGroup, badgeGroup, tabsGroup, otherGroup))
} }
// Refreshes Display Setting selections // Refreshes Display Setting selections
@ -408,6 +410,28 @@ class LibrarySettingsSheet(
adapter.notifyItemChanged(item) adapter.notifyItemChanged(item)
} }
} }
inner class OtherGroup : Group {
private val showContinueReadingButton = Item.CheckboxGroup(R.string.action_display_show_continue_reading_button, this)
override val header = Item.Header(R.string.other_header)
override val items = listOf(showContinueReadingButton)
override val footer = null
override fun initModels() {
showContinueReadingButton.checked = libraryPreferences.showContinueReadingButton().get()
}
override fun onItemClicked(item: Item) {
item as Item.CheckboxGroup
item.checked = !item.checked
when (item) {
showContinueReadingButton -> libraryPreferences.showContinueReadingButton().set(item.checked)
else -> {}
}
adapter.notifyItemChanged(item)
}
}
} }
open inner class Settings(context: Context, attrs: AttributeSet?) : open inner class Settings(context: Context, attrs: AttributeSet?) :

View File

@ -16,6 +16,7 @@ import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.interactor.UpdateChapter import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.applyFilters
import eu.kanade.domain.chapter.model.toDbChapter import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.download.service.DownloadPreferences import eu.kanade.domain.download.service.DownloadPreferences
import eu.kanade.domain.library.service.LibraryPreferences import eu.kanade.domain.library.service.LibraryPreferences
@ -23,8 +24,6 @@ import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.TriStateFilter
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.track.interactor.DeleteTrack import eu.kanade.domain.track.interactor.DeleteTrack
import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.domain.track.interactor.GetTracks
@ -45,6 +44,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.getChapterSort import eu.kanade.tachiyomi.util.chapter.getChapterSort
import eu.kanade.tachiyomi.util.chapter.getNextUnread
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.lang.toRelativeString
@ -579,13 +579,7 @@ class MangaPresenter(
*/ */
fun getNextUnreadChapter(): DomainChapter? { fun getNextUnreadChapter(): DomainChapter? {
val successState = successState ?: return null val successState = successState ?: return null
return successState.processedChapters.map { it.chapter }.let { chapters -> return successState.chapters.getNextUnread(successState.manga)
if (successState.manga.sortDescending()) {
chapters.findLast { !it.read }
} else {
chapters.find { !it.read }
}
}
} }
fun getUnreadChapters(): List<DomainChapter> { fun getUnreadChapters(): List<DomainChapter> {
@ -1092,40 +1086,6 @@ sealed class MangaScreenState {
val processedChapters: Sequence<ChapterItem> val processedChapters: Sequence<ChapterItem>
get() = chapters.applyFilters(manga) get() = chapters.applyFilters(manga)
/**
* Applies the view filters to the list of chapters obtained from the database.
* @return an observable of the list of chapters filtered and sorted.
*/
private fun List<ChapterItem>.applyFilters(manga: DomainManga): Sequence<ChapterItem> {
val isLocalManga = manga.isLocal()
val unreadFilter = manga.unreadFilter
val downloadedFilter = manga.downloadedFilter
val bookmarkedFilter = manga.bookmarkedFilter
return asSequence()
.filter { (chapter) ->
when (unreadFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> !chapter.read
TriStateFilter.ENABLED_NOT -> chapter.read
}
}
.filter { (chapter) ->
when (bookmarkedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> chapter.bookmark
TriStateFilter.ENABLED_NOT -> !chapter.bookmark
}
}
.filter {
when (downloadedFilter) {
TriStateFilter.DISABLED -> true
TriStateFilter.ENABLED_IS -> it.isDownloaded || isLocalManga
TriStateFilter.ENABLED_NOT -> !it.isDownloaded && !isLocalManga
}
}
.sortedWith { (chapter1), (chapter2) -> getChapterSort(manga).invoke(chapter1, chapter2) }
}
} }
} }

View File

@ -0,0 +1,33 @@
package eu.kanade.tachiyomi.util.chapter
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.chapter.model.applyFilters
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.ui.manga.ChapterItem
/**
* Gets next unread chapter with filters and sorting applied
*/
fun List<Chapter>.getNextUnread(manga: Manga, downloadManager: DownloadManager): Chapter? {
return applyFilters(manga, downloadManager).let { chapters ->
if (manga.sortDescending()) {
chapters.findLast { !it.read }
} else {
chapters.find { !it.read }
}
}
}
/**
* Gets next unread chapter with filters and sorting applied
*/
fun List<ChapterItem>.getNextUnread(manga: Manga): Chapter? {
return applyFilters(manga).let { chapters ->
if (manga.sortDescending()) {
chapters.findLast { !it.chapter.read }
} else {
chapters.find { !it.chapter.read }
}
}?.chapter
}

View File

@ -108,6 +108,7 @@
<string name="action_display_language_badge">Language</string> <string name="action_display_language_badge">Language</string>
<string name="action_display_show_tabs">Show category tabs</string> <string name="action_display_show_tabs">Show category tabs</string>
<string name="action_display_show_number_of_items">Show number of items</string> <string name="action_display_show_number_of_items">Show number of items</string>
<string name="action_display_show_continue_reading_button">Show continue reading button</string>
<string name="action_disable">Disable</string> <string name="action_disable">Disable</string>
<string name="action_pin">Pin</string> <string name="action_pin">Pin</string>
<string name="action_unpin">Unpin</string> <string name="action_unpin">Unpin</string>
@ -579,6 +580,7 @@
<string name="downloaded_chapters">Downloaded chapters</string> <string name="downloaded_chapters">Downloaded chapters</string>
<string name="badges_header">Badges</string> <string name="badges_header">Badges</string>
<string name="tabs_header">Tabs</string> <string name="tabs_header">Tabs</string>
<string name="other_header">Other</string>
<!-- Catalogue fragment --> <!-- Catalogue fragment -->
<!-- missing prompt after Compose rewrite #7901 --> <!-- missing prompt after Compose rewrite #7901 -->