123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838 |
- package eu.kanade.presentation.manga
- import androidx.activity.compose.BackHandler
- import androidx.compose.animation.AnimatedVisibility
- import androidx.compose.animation.core.animateFloatAsState
- import androidx.compose.animation.fadeIn
- import androidx.compose.animation.fadeOut
- import androidx.compose.foundation.layout.Box
- import androidx.compose.foundation.layout.Column
- import androidx.compose.foundation.layout.PaddingValues
- import androidx.compose.foundation.layout.WindowInsets
- import androidx.compose.foundation.layout.WindowInsetsSides
- import androidx.compose.foundation.layout.asPaddingValues
- import androidx.compose.foundation.layout.calculateEndPadding
- import androidx.compose.foundation.layout.calculateStartPadding
- import androidx.compose.foundation.layout.fillMaxHeight
- import androidx.compose.foundation.layout.fillMaxWidth
- import androidx.compose.foundation.layout.only
- import androidx.compose.foundation.layout.padding
- import androidx.compose.foundation.layout.systemBars
- import androidx.compose.foundation.lazy.LazyColumn
- import androidx.compose.foundation.lazy.LazyListScope
- import androidx.compose.foundation.lazy.items
- import androidx.compose.foundation.lazy.rememberLazyListState
- import androidx.compose.foundation.rememberScrollState
- import androidx.compose.foundation.verticalScroll
- import androidx.compose.material.icons.Icons
- import androidx.compose.material.icons.filled.PlayArrow
- import androidx.compose.material3.Icon
- import androidx.compose.material3.SnackbarHost
- import androidx.compose.material3.SnackbarHostState
- import androidx.compose.material3.Text
- import androidx.compose.runtime.Composable
- import androidx.compose.runtime.derivedStateOf
- import androidx.compose.runtime.getValue
- import androidx.compose.runtime.mutableIntStateOf
- import androidx.compose.runtime.remember
- import androidx.compose.runtime.setValue
- import androidx.compose.ui.Alignment
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.hapticfeedback.HapticFeedbackType
- import androidx.compose.ui.layout.onSizeChanged
- import androidx.compose.ui.platform.LocalContext
- import androidx.compose.ui.platform.LocalDensity
- import androidx.compose.ui.platform.LocalHapticFeedback
- import androidx.compose.ui.platform.LocalLayoutDirection
- import androidx.compose.ui.util.fastAll
- import androidx.compose.ui.util.fastAny
- import androidx.compose.ui.util.fastMap
- import eu.kanade.presentation.components.relativeDateText
- import eu.kanade.presentation.manga.components.ChapterDownloadAction
- import eu.kanade.presentation.manga.components.ChapterHeader
- import eu.kanade.presentation.manga.components.ExpandableMangaDescription
- import eu.kanade.presentation.manga.components.MangaActionRow
- import eu.kanade.presentation.manga.components.MangaBottomActionMenu
- import eu.kanade.presentation.manga.components.MangaChapterListItem
- import eu.kanade.presentation.manga.components.MangaInfoBox
- import eu.kanade.presentation.manga.components.MangaToolbar
- import eu.kanade.presentation.manga.components.MissingChapterCountListItem
- import eu.kanade.presentation.util.formatChapterNumber
- import eu.kanade.tachiyomi.data.download.model.Download
- import eu.kanade.tachiyomi.source.getNameForMangaInfo
- import eu.kanade.tachiyomi.ui.manga.ChapterList
- import eu.kanade.tachiyomi.ui.manga.MangaScreenModel
- 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.i18n.MR
- import tachiyomi.presentation.core.components.TwoPanelBox
- import tachiyomi.presentation.core.components.VerticalFastScroller
- import tachiyomi.presentation.core.components.material.ExtendedFloatingActionButton
- import tachiyomi.presentation.core.components.material.PullRefresh
- import tachiyomi.presentation.core.components.material.Scaffold
- import tachiyomi.presentation.core.i18n.stringResource
- import tachiyomi.presentation.core.util.isScrolledToEnd
- import tachiyomi.presentation.core.util.isScrollingUp
- import tachiyomi.source.local.isLocal
- import java.time.Instant
- @Composable
- fun MangaScreen(
- state: MangaScreenModel.State.Success,
- snackbarHostState: SnackbarHostState,
- nextUpdate: Instant?,
- isTabletUi: Boolean,
- chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
- chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
- onBackClicked: () -> Unit,
- onChapterClicked: (Chapter) -> Unit,
- onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
- onAddToLibraryClicked: () -> Unit,
- onWebViewClicked: (() -> Unit)?,
- onWebViewLongClicked: (() -> Unit)?,
- onTrackingClicked: () -> Unit,
- // For tags menu
- onTagSearch: (String) -> Unit,
- onFilterButtonClicked: () -> Unit,
- onRefresh: () -> Unit,
- onContinueReading: () -> Unit,
- onSearch: (query: String, global: Boolean) -> Unit,
- // For cover dialog
- onCoverClicked: () -> Unit,
- // For top action menu
- onShareClicked: (() -> Unit)?,
- onDownloadActionClicked: ((DownloadAction) -> Unit)?,
- onEditCategoryClicked: (() -> Unit)?,
- onEditFetchIntervalClicked: (() -> Unit)?,
- onMigrateClicked: (() -> Unit)?,
- // For bottom action menu
- onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
- onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
- onMarkPreviousAsReadClicked: (Chapter) -> Unit,
- onMultiDeleteClicked: (List<Chapter>) -> Unit,
- // For chapter swipe
- onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
- // Chapter selection
- onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
- onAllChapterSelected: (Boolean) -> Unit,
- onInvertSelection: () -> Unit,
- ) {
- val context = LocalContext.current
- val onCopyTagToClipboard: (tag: String) -> Unit = {
- if (it.isNotEmpty()) {
- context.copyToClipboard(it, it)
- }
- }
- if (!isTabletUi) {
- MangaScreenSmallImpl(
- state = state,
- snackbarHostState = snackbarHostState,
- nextUpdate = nextUpdate,
- chapterSwipeStartAction = chapterSwipeStartAction,
- chapterSwipeEndAction = chapterSwipeEndAction,
- onBackClicked = onBackClicked,
- onChapterClicked = onChapterClicked,
- onDownloadChapter = onDownloadChapter,
- onAddToLibraryClicked = onAddToLibraryClicked,
- onWebViewClicked = onWebViewClicked,
- onWebViewLongClicked = onWebViewLongClicked,
- onTrackingClicked = onTrackingClicked,
- onTagSearch = onTagSearch,
- onCopyTagToClipboard = onCopyTagToClipboard,
- onFilterClicked = onFilterButtonClicked,
- onRefresh = onRefresh,
- onContinueReading = onContinueReading,
- onSearch = onSearch,
- onCoverClicked = onCoverClicked,
- onShareClicked = onShareClicked,
- onDownloadActionClicked = onDownloadActionClicked,
- onEditCategoryClicked = onEditCategoryClicked,
- onEditIntervalClicked = onEditFetchIntervalClicked,
- onMigrateClicked = onMigrateClicked,
- onMultiBookmarkClicked = onMultiBookmarkClicked,
- onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
- onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
- onMultiDeleteClicked = onMultiDeleteClicked,
- onChapterSwipe = onChapterSwipe,
- onChapterSelected = onChapterSelected,
- onAllChapterSelected = onAllChapterSelected,
- onInvertSelection = onInvertSelection,
- )
- } else {
- MangaScreenLargeImpl(
- state = state,
- snackbarHostState = snackbarHostState,
- chapterSwipeStartAction = chapterSwipeStartAction,
- chapterSwipeEndAction = chapterSwipeEndAction,
- nextUpdate = nextUpdate,
- onBackClicked = onBackClicked,
- onChapterClicked = onChapterClicked,
- onDownloadChapter = onDownloadChapter,
- onAddToLibraryClicked = onAddToLibraryClicked,
- onWebViewClicked = onWebViewClicked,
- onWebViewLongClicked = onWebViewLongClicked,
- onTrackingClicked = onTrackingClicked,
- onTagSearch = onTagSearch,
- onCopyTagToClipboard = onCopyTagToClipboard,
- onFilterButtonClicked = onFilterButtonClicked,
- onRefresh = onRefresh,
- onContinueReading = onContinueReading,
- onSearch = onSearch,
- onCoverClicked = onCoverClicked,
- onShareClicked = onShareClicked,
- onDownloadActionClicked = onDownloadActionClicked,
- onEditCategoryClicked = onEditCategoryClicked,
- onEditIntervalClicked = onEditFetchIntervalClicked,
- onMigrateClicked = onMigrateClicked,
- onMultiBookmarkClicked = onMultiBookmarkClicked,
- onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
- onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
- onMultiDeleteClicked = onMultiDeleteClicked,
- onChapterSwipe = onChapterSwipe,
- onChapterSelected = onChapterSelected,
- onAllChapterSelected = onAllChapterSelected,
- onInvertSelection = onInvertSelection,
- )
- }
- }
- @Composable
- private fun MangaScreenSmallImpl(
- state: MangaScreenModel.State.Success,
- snackbarHostState: SnackbarHostState,
- nextUpdate: Instant?,
- chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
- chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
- onBackClicked: () -> Unit,
- onChapterClicked: (Chapter) -> Unit,
- onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
- onAddToLibraryClicked: () -> Unit,
- onWebViewClicked: (() -> Unit)?,
- onWebViewLongClicked: (() -> Unit)?,
- onTrackingClicked: () -> Unit,
- // For tags menu
- onTagSearch: (String) -> Unit,
- onCopyTagToClipboard: (tag: String) -> Unit,
- onFilterClicked: () -> Unit,
- onRefresh: () -> Unit,
- onContinueReading: () -> Unit,
- onSearch: (query: String, global: Boolean) -> Unit,
- // For cover dialog
- onCoverClicked: () -> Unit,
- // For top action menu
- onShareClicked: (() -> Unit)?,
- onDownloadActionClicked: ((DownloadAction) -> Unit)?,
- onEditCategoryClicked: (() -> Unit)?,
- onEditIntervalClicked: (() -> Unit)?,
- onMigrateClicked: (() -> Unit)?,
- // For bottom action menu
- onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
- onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
- onMarkPreviousAsReadClicked: (Chapter) -> Unit,
- onMultiDeleteClicked: (List<Chapter>) -> Unit,
- // For chapter swipe
- onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
- // Chapter selection
- onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
- onAllChapterSelected: (Boolean) -> Unit,
- onInvertSelection: () -> Unit,
- ) {
- val chapterListState = rememberLazyListState()
- val (chapters, listItem, isAnySelected) = remember(state) {
- Triple(
- first = state.processedChapters,
- second = state.chapterListItems,
- third = state.isAnySelected,
- )
- }
- val internalOnBackPressed = {
- if (isAnySelected) {
- onAllChapterSelected(false)
- } else {
- onBackClicked()
- }
- }
- BackHandler(onBack = internalOnBackPressed)
- Scaffold(
- topBar = {
- val selectedChapterCount: Int = remember(chapters) {
- chapters.count { it.selected }
- }
- val isFirstItemVisible by remember {
- derivedStateOf { chapterListState.firstVisibleItemIndex == 0 }
- }
- val isFirstItemScrolled by remember {
- derivedStateOf { chapterListState.firstVisibleItemScrollOffset > 0 }
- }
- val animatedTitleAlpha by animateFloatAsState(
- if (!isFirstItemVisible) 1f else 0f,
- label = "Top Bar Title",
- )
- val animatedBgAlpha by animateFloatAsState(
- if (!isFirstItemVisible || isFirstItemScrolled) 1f else 0f,
- label = "Top Bar Background",
- )
- MangaToolbar(
- title = state.manga.title,
- titleAlphaProvider = { animatedTitleAlpha },
- backgroundAlphaProvider = { animatedBgAlpha },
- hasFilters = state.filterActive,
- onBackClicked = internalOnBackPressed,
- onClickFilter = onFilterClicked,
- onClickShare = onShareClicked,
- onClickDownload = onDownloadActionClicked,
- onClickEditCategory = onEditCategoryClicked,
- onClickRefresh = onRefresh,
- onClickMigrate = onMigrateClicked,
- actionModeCounter = selectedChapterCount,
- onSelectAll = { onAllChapterSelected(true) },
- onInvertSelection = { onInvertSelection() },
- )
- },
- bottomBar = {
- val selectedChapters = remember(chapters) {
- chapters.filter { it.selected }
- }
- SharedMangaBottomActionMenu(
- selected = selectedChapters,
- onMultiBookmarkClicked = onMultiBookmarkClicked,
- onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
- onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
- onDownloadChapter = onDownloadChapter,
- onMultiDeleteClicked = onMultiDeleteClicked,
- fillFraction = 1f,
- )
- },
- snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
- floatingActionButton = {
- val isFABVisible = remember(chapters) {
- chapters.fastAny { !it.chapter.read } && !isAnySelected
- }
- AnimatedVisibility(
- visible = isFABVisible,
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- ExtendedFloatingActionButton(
- text = {
- val isReading = remember(state.chapters) {
- state.chapters.fastAny { it.chapter.read }
- }
- Text(
- text = stringResource(if (isReading) MR.strings.action_resume else MR.strings.action_start),
- )
- },
- icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
- onClick = onContinueReading,
- expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
- )
- }
- },
- ) { contentPadding ->
- val topPadding = contentPadding.calculateTopPadding()
- PullRefresh(
- refreshing = state.isRefreshingData,
- onRefresh = onRefresh,
- enabled = { !isAnySelected },
- indicatorPadding = PaddingValues(top = topPadding),
- ) {
- val layoutDirection = LocalLayoutDirection.current
- VerticalFastScroller(
- listState = chapterListState,
- topContentPadding = topPadding,
- endContentPadding = contentPadding.calculateEndPadding(layoutDirection),
- ) {
- LazyColumn(
- modifier = Modifier.fillMaxHeight(),
- state = chapterListState,
- contentPadding = PaddingValues(
- start = contentPadding.calculateStartPadding(layoutDirection),
- end = contentPadding.calculateEndPadding(layoutDirection),
- bottom = contentPadding.calculateBottomPadding(),
- ),
- ) {
- item(
- key = MangaScreenItem.INFO_BOX,
- contentType = MangaScreenItem.INFO_BOX,
- ) {
- MangaInfoBox(
- isTabletUi = false,
- appBarPadding = topPadding,
- title = state.manga.title,
- author = state.manga.author,
- artist = state.manga.artist,
- sourceName = remember { state.source.getNameForMangaInfo() },
- isStubSource = remember { state.source is StubSource },
- coverDataProvider = { state.manga },
- status = state.manga.status,
- onCoverClick = onCoverClicked,
- doSearch = onSearch,
- )
- }
- item(
- key = MangaScreenItem.ACTION_ROW,
- contentType = MangaScreenItem.ACTION_ROW,
- ) {
- MangaActionRow(
- favorite = state.manga.favorite,
- trackingCount = state.trackingCount,
- nextUpdate = nextUpdate,
- isUserIntervalMode = state.manga.fetchInterval < 0,
- onAddToLibraryClicked = onAddToLibraryClicked,
- onWebViewClicked = onWebViewClicked,
- onWebViewLongClicked = onWebViewLongClicked,
- onTrackingClicked = onTrackingClicked,
- onEditIntervalClicked = onEditIntervalClicked,
- onEditCategory = onEditCategoryClicked,
- )
- }
- item(
- key = MangaScreenItem.DESCRIPTION_WITH_TAG,
- contentType = MangaScreenItem.DESCRIPTION_WITH_TAG,
- ) {
- ExpandableMangaDescription(
- defaultExpandState = state.isFromSource,
- description = state.manga.description,
- tagsProvider = { state.manga.genre },
- onTagSearch = onTagSearch,
- onCopyTagToClipboard = onCopyTagToClipboard,
- )
- }
- item(
- key = MangaScreenItem.CHAPTER_HEADER,
- contentType = MangaScreenItem.CHAPTER_HEADER,
- ) {
- val missingChapterCount = remember(chapters) {
- chapters.map { it.chapter.chapterNumber }.missingChaptersCount()
- }
- ChapterHeader(
- enabled = !isAnySelected,
- chapterCount = chapters.size,
- missingChapterCount = missingChapterCount,
- onClick = onFilterClicked,
- )
- }
- sharedChapterItems(
- manga = state.manga,
- chapters = listItem,
- isAnyChapterSelected = chapters.fastAny { it.selected },
- chapterSwipeStartAction = chapterSwipeStartAction,
- chapterSwipeEndAction = chapterSwipeEndAction,
- onChapterClicked = onChapterClicked,
- onDownloadChapter = onDownloadChapter,
- onChapterSelected = onChapterSelected,
- onChapterSwipe = onChapterSwipe,
- )
- }
- }
- }
- }
- }
- @Composable
- fun MangaScreenLargeImpl(
- state: MangaScreenModel.State.Success,
- snackbarHostState: SnackbarHostState,
- nextUpdate: Instant?,
- chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
- chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
- onBackClicked: () -> Unit,
- onChapterClicked: (Chapter) -> Unit,
- onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
- onAddToLibraryClicked: () -> Unit,
- onWebViewClicked: (() -> Unit)?,
- onWebViewLongClicked: (() -> Unit)?,
- onTrackingClicked: () -> Unit,
- // For tags menu
- onTagSearch: (String) -> Unit,
- onCopyTagToClipboard: (tag: String) -> Unit,
- onFilterButtonClicked: () -> Unit,
- onRefresh: () -> Unit,
- onContinueReading: () -> Unit,
- onSearch: (query: String, global: Boolean) -> Unit,
- // For cover dialog
- onCoverClicked: () -> Unit,
- // For top action menu
- onShareClicked: (() -> Unit)?,
- onDownloadActionClicked: ((DownloadAction) -> Unit)?,
- onEditCategoryClicked: (() -> Unit)?,
- onEditIntervalClicked: (() -> Unit)?,
- onMigrateClicked: (() -> Unit)?,
- // For bottom action menu
- onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
- onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
- onMarkPreviousAsReadClicked: (Chapter) -> Unit,
- onMultiDeleteClicked: (List<Chapter>) -> Unit,
- // For swipe actions
- onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
- // Chapter selection
- onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
- onAllChapterSelected: (Boolean) -> Unit,
- onInvertSelection: () -> Unit,
- ) {
- val layoutDirection = LocalLayoutDirection.current
- val density = LocalDensity.current
- val (chapters, listItem, isAnySelected) = remember(state) {
- Triple(
- first = state.processedChapters,
- second = state.chapterListItems,
- third = state.isAnySelected,
- )
- }
- val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
- var topBarHeight by remember { mutableIntStateOf(0) }
- val chapterListState = rememberLazyListState()
- val internalOnBackPressed = {
- if (isAnySelected) {
- onAllChapterSelected(false)
- } else {
- onBackClicked()
- }
- }
- BackHandler(onBack = internalOnBackPressed)
- Scaffold(
- topBar = {
- val selectedChapterCount = remember(chapters) {
- chapters.count { it.selected }
- }
- MangaToolbar(
- modifier = Modifier.onSizeChanged { topBarHeight = it.height },
- title = state.manga.title,
- titleAlphaProvider = { if (isAnySelected) 1f else 0f },
- backgroundAlphaProvider = { 1f },
- hasFilters = state.filterActive,
- onBackClicked = internalOnBackPressed,
- onClickFilter = onFilterButtonClicked,
- onClickShare = onShareClicked,
- onClickDownload = onDownloadActionClicked,
- onClickEditCategory = onEditCategoryClicked,
- onClickRefresh = onRefresh,
- onClickMigrate = onMigrateClicked,
- actionModeCounter = selectedChapterCount,
- onSelectAll = { onAllChapterSelected(true) },
- onInvertSelection = { onInvertSelection() },
- )
- },
- bottomBar = {
- Box(
- modifier = Modifier.fillMaxWidth(),
- contentAlignment = Alignment.BottomEnd,
- ) {
- val selectedChapters = remember(chapters) {
- chapters.filter { it.selected }
- }
- SharedMangaBottomActionMenu(
- selected = selectedChapters,
- onMultiBookmarkClicked = onMultiBookmarkClicked,
- onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
- onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
- onDownloadChapter = onDownloadChapter,
- onMultiDeleteClicked = onMultiDeleteClicked,
- fillFraction = 0.5f,
- )
- }
- },
- snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
- floatingActionButton = {
- val isFABVisible = remember(chapters) {
- chapters.fastAny { !it.chapter.read } && !isAnySelected
- }
- AnimatedVisibility(
- visible = isFABVisible,
- enter = fadeIn(),
- exit = fadeOut(),
- ) {
- ExtendedFloatingActionButton(
- text = {
- val isReading = remember(state.chapters) {
- state.chapters.fastAny { it.chapter.read }
- }
- Text(
- text = stringResource(
- if (isReading) MR.strings.action_resume else MR.strings.action_start,
- ),
- )
- },
- icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) },
- onClick = onContinueReading,
- expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
- )
- }
- },
- ) { contentPadding ->
- PullRefresh(
- refreshing = state.isRefreshingData,
- onRefresh = onRefresh,
- enabled = { !isAnySelected },
- indicatorPadding = PaddingValues(
- start = insetPadding.calculateStartPadding(layoutDirection),
- top = with(density) { topBarHeight.toDp() },
- end = insetPadding.calculateEndPadding(layoutDirection),
- ),
- ) {
- TwoPanelBox(
- modifier = Modifier.padding(
- start = contentPadding.calculateStartPadding(layoutDirection),
- end = contentPadding.calculateEndPadding(layoutDirection),
- ),
- startContent = {
- Column(
- modifier = Modifier
- .verticalScroll(rememberScrollState())
- .padding(bottom = contentPadding.calculateBottomPadding()),
- ) {
- MangaInfoBox(
- isTabletUi = true,
- appBarPadding = contentPadding.calculateTopPadding(),
- title = state.manga.title,
- author = state.manga.author,
- artist = state.manga.artist,
- sourceName = remember { state.source.getNameForMangaInfo() },
- isStubSource = remember { state.source is StubSource },
- coverDataProvider = { state.manga },
- status = state.manga.status,
- onCoverClick = onCoverClicked,
- doSearch = onSearch,
- )
- MangaActionRow(
- favorite = state.manga.favorite,
- trackingCount = state.trackingCount,
- nextUpdate = nextUpdate,
- isUserIntervalMode = state.manga.fetchInterval < 0,
- onAddToLibraryClicked = onAddToLibraryClicked,
- onWebViewClicked = onWebViewClicked,
- onWebViewLongClicked = onWebViewLongClicked,
- onTrackingClicked = onTrackingClicked,
- onEditIntervalClicked = onEditIntervalClicked,
- onEditCategory = onEditCategoryClicked,
- )
- ExpandableMangaDescription(
- defaultExpandState = true,
- description = state.manga.description,
- tagsProvider = { state.manga.genre },
- onTagSearch = onTagSearch,
- onCopyTagToClipboard = onCopyTagToClipboard,
- )
- }
- },
- endContent = {
- VerticalFastScroller(
- listState = chapterListState,
- topContentPadding = contentPadding.calculateTopPadding(),
- ) {
- LazyColumn(
- modifier = Modifier.fillMaxHeight(),
- state = chapterListState,
- contentPadding = PaddingValues(
- top = contentPadding.calculateTopPadding(),
- bottom = contentPadding.calculateBottomPadding(),
- ),
- ) {
- item(
- key = MangaScreenItem.CHAPTER_HEADER,
- contentType = MangaScreenItem.CHAPTER_HEADER,
- ) {
- val missingChapterCount = remember(chapters) {
- chapters.map { it.chapter.chapterNumber }.missingChaptersCount()
- }
- ChapterHeader(
- enabled = !isAnySelected,
- chapterCount = chapters.size,
- missingChapterCount = missingChapterCount,
- onClick = onFilterButtonClicked,
- )
- }
- sharedChapterItems(
- manga = state.manga,
- chapters = listItem,
- isAnyChapterSelected = chapters.fastAny { it.selected },
- chapterSwipeStartAction = chapterSwipeStartAction,
- chapterSwipeEndAction = chapterSwipeEndAction,
- onChapterClicked = onChapterClicked,
- onDownloadChapter = onDownloadChapter,
- onChapterSelected = onChapterSelected,
- onChapterSwipe = onChapterSwipe,
- )
- }
- }
- },
- )
- }
- }
- }
- @Composable
- private fun SharedMangaBottomActionMenu(
- selected: List<ChapterList.Item>,
- onMultiBookmarkClicked: (List<Chapter>, bookmarked: Boolean) -> Unit,
- onMultiMarkAsReadClicked: (List<Chapter>, markAsRead: Boolean) -> Unit,
- onMarkPreviousAsReadClicked: (Chapter) -> Unit,
- onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
- onMultiDeleteClicked: (List<Chapter>) -> Unit,
- fillFraction: Float,
- modifier: Modifier = Modifier,
- ) {
- MangaBottomActionMenu(
- visible = selected.isNotEmpty(),
- modifier = modifier.fillMaxWidth(fillFraction),
- onBookmarkClicked = {
- onMultiBookmarkClicked.invoke(selected.fastMap { it.chapter }, true)
- }.takeIf { selected.fastAny { !it.chapter.bookmark } },
- onRemoveBookmarkClicked = {
- onMultiBookmarkClicked.invoke(selected.fastMap { it.chapter }, false)
- }.takeIf { selected.fastAll { it.chapter.bookmark } },
- onMarkAsReadClicked = {
- onMultiMarkAsReadClicked(selected.fastMap { it.chapter }, true)
- }.takeIf { selected.fastAny { !it.chapter.read } },
- onMarkAsUnreadClicked = {
- onMultiMarkAsReadClicked(selected.fastMap { it.chapter }, false)
- }.takeIf { selected.fastAny { it.chapter.read || it.chapter.lastPageRead > 0L } },
- onMarkPreviousAsReadClicked = {
- onMarkPreviousAsReadClicked(selected[0].chapter)
- }.takeIf { selected.size == 1 },
- onDownloadClicked = {
- onDownloadChapter!!(selected.toList(), ChapterDownloadAction.START)
- }.takeIf {
- onDownloadChapter != null && selected.fastAny { it.downloadState != Download.State.DOWNLOADED }
- },
- onDeleteClicked = {
- onMultiDeleteClicked(selected.fastMap { it.chapter })
- }.takeIf {
- selected.fastAny { it.downloadState == Download.State.DOWNLOADED }
- },
- )
- }
- private fun LazyListScope.sharedChapterItems(
- manga: Manga,
- chapters: List<ChapterList>,
- isAnyChapterSelected: Boolean,
- chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction,
- chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction,
- onChapterClicked: (Chapter) -> Unit,
- onDownloadChapter: ((List<ChapterList.Item>, ChapterDownloadAction) -> Unit)?,
- onChapterSelected: (ChapterList.Item, Boolean, Boolean, Boolean) -> Unit,
- onChapterSwipe: (ChapterList.Item, LibraryPreferences.ChapterSwipeAction) -> Unit,
- ) {
- items(
- items = chapters,
- key = { item ->
- when (item) {
- is ChapterList.MissingCount -> "missing-count-${item.id}"
- is ChapterList.Item -> "chapter-${item.id}"
- }
- },
- contentType = { MangaScreenItem.CHAPTER },
- ) { item ->
- val haptic = LocalHapticFeedback.current
- when (item) {
- is ChapterList.MissingCount -> {
- MissingChapterCountListItem(count = item.count)
- }
- is ChapterList.Item -> {
- MangaChapterListItem(
- title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) {
- stringResource(
- MR.strings.display_mode_chapter,
- formatChapterNumber(item.chapter.chapterNumber),
- )
- } else {
- item.chapter.name
- },
- date = relativeDateText(item.chapter.dateUpload),
- readProgress = item.chapter.lastPageRead
- .takeIf { !item.chapter.read && it > 0L }
- ?.let {
- stringResource(
- MR.strings.chapter_progress,
- it + 1,
- )
- },
- scanlator = item.chapter.scanlator.takeIf { !it.isNullOrBlank() },
- read = item.chapter.read,
- bookmark = item.chapter.bookmark,
- selected = item.selected,
- downloadIndicatorEnabled = !isAnyChapterSelected && !manga.isLocal(),
- downloadStateProvider = { item.downloadState },
- downloadProgressProvider = { item.downloadProgress },
- chapterSwipeStartAction = chapterSwipeStartAction,
- chapterSwipeEndAction = chapterSwipeEndAction,
- onLongClick = {
- onChapterSelected(item, !item.selected, true, true)
- haptic.performHapticFeedback(HapticFeedbackType.LongPress)
- },
- onClick = {
- onChapterItemClick(
- chapterItem = item,
- isAnyChapterSelected = isAnyChapterSelected,
- onToggleSelection = { onChapterSelected(item, !item.selected, true, false) },
- onChapterClicked = onChapterClicked,
- )
- },
- onDownloadClick = if (onDownloadChapter != null) {
- { onDownloadChapter(listOf(item), it) }
- } else {
- null
- },
- onChapterSwipe = {
- onChapterSwipe(item, it)
- },
- )
- }
- }
- }
- }
- private fun onChapterItemClick(
- chapterItem: ChapterList.Item,
- isAnyChapterSelected: Boolean,
- onToggleSelection: (Boolean) -> Unit,
- onChapterClicked: (Chapter) -> Unit,
- ) {
- when {
- chapterItem.selected -> onToggleSelection(false)
- isAnyChapterSelected -> onToggleSelection(true)
- else -> onChapterClicked(chapterItem.chapter)
- }
- }
|