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.res.stringResource import androidx.compose.ui.util.fastAll import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastMap import eu.kanade.domain.manga.model.chaptersFiltered 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.MangaInfoButtons import eu.kanade.presentation.manga.components.MangaToolbar import eu.kanade.presentation.manga.components.PagePreviews import eu.kanade.presentation.manga.components.SearchMetadataChips import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.getNameForMangaInfo import eu.kanade.tachiyomi.source.online.MetadataSource import eu.kanade.tachiyomi.source.online.all.EHentai import eu.kanade.tachiyomi.source.online.all.MangaDex import eu.kanade.tachiyomi.source.online.all.NHentai import eu.kanade.tachiyomi.source.online.english.EightMuses import eu.kanade.tachiyomi.source.online.english.HBrowse import eu.kanade.tachiyomi.source.online.english.Pururin import eu.kanade.tachiyomi.source.online.english.Tsumino import eu.kanade.tachiyomi.ui.manga.ChapterItem import eu.kanade.tachiyomi.ui.manga.MangaScreenState import eu.kanade.tachiyomi.ui.manga.PagePreviewState import eu.kanade.tachiyomi.ui.manga.chapterDecimalFormat import eu.kanade.tachiyomi.util.lang.toRelativeString import eu.kanade.tachiyomi.util.system.copyToClipboard import exh.metadata.MetadataUtil import exh.source.MERGED_SOURCE_ID import exh.source.getMainSource import exh.source.isEhBasedManga import exh.ui.metadata.adapters.EHentaiDescription import exh.ui.metadata.adapters.EightMusesDescription import exh.ui.metadata.adapters.HBrowseDescription import exh.ui.metadata.adapters.MangaDexDescription import exh.ui.metadata.adapters.NHentaiDescription import exh.ui.metadata.adapters.PururinDescription import exh.ui.metadata.adapters.TsuminoDescription import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.service.missingChaptersCount import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.model.StubSource import tachiyomi.presentation.core.components.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.util.isScrolledToEnd import tachiyomi.presentation.core.util.isScrollingUp import java.text.DateFormat import java.util.Date @Composable fun MangaScreen( state: MangaScreenState.Success, snackbarHostState: SnackbarHostState, dateRelativeTime: Int, dateFormat: DateFormat, isTabletUi: Boolean, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, 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)?, onMigrateClicked: (() -> Unit)?, // SY --> onMetadataViewerClicked: () -> Unit, onEditInfoClicked: () -> Unit, onRecommendClicked: () -> Unit, onMergedSettingsClicked: () -> Unit, onMergeClicked: () -> Unit, onMergeWithAnotherClicked: () -> Unit, onOpenPagePreview: (Int) -> Unit, onMorePreviewsClicked: () -> Unit, // SY <-- // For bottom action menu onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, onMultiMarkAsReadClicked: (List, markAsRead: Boolean) -> Unit, onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMultiDeleteClicked: (List) -> Unit, // For chapter swipe onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, // Chapter selection onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit, 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, dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, 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, onMigrateClicked = onMigrateClicked, // SY --> onMetadataViewerClicked = onMetadataViewerClicked, onEditInfoClicked = onEditInfoClicked, onRecommendClicked = onRecommendClicked, onMergedSettingsClicked = onMergedSettingsClicked, onMergeClicked = onMergeClicked, onMergeWithAnotherClicked = onMergeWithAnotherClicked, onOpenPagePreview = onOpenPagePreview, onMorePreviewsClicked = onMorePreviewsClicked, // SY <-- onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onMultiDeleteClicked = onMultiDeleteClicked, onChapterSwipe = onChapterSwipe, onChapterSelected = onChapterSelected, onAllChapterSelected = onAllChapterSelected, onInvertSelection = onInvertSelection, ) } else { MangaScreenLargeImpl( state = state, snackbarHostState = snackbarHostState, dateRelativeTime = dateRelativeTime, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, dateFormat = dateFormat, 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, onMigrateClicked = onMigrateClicked, // SY --> onMetadataViewerClicked = onMetadataViewerClicked, onEditInfoClicked = onEditInfoClicked, onRecommendClicked = onRecommendClicked, onMergedSettingsClicked = onMergedSettingsClicked, onMergeClicked = onMergeClicked, onMergeWithAnotherClicked = onMergeWithAnotherClicked, onOpenPagePreview = onOpenPagePreview, onMorePreviewsClicked = onMorePreviewsClicked, // SY <-- onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onMultiDeleteClicked = onMultiDeleteClicked, onChapterSwipe = onChapterSwipe, onChapterSelected = onChapterSelected, onAllChapterSelected = onAllChapterSelected, onInvertSelection = onInvertSelection, ) } } @Composable private fun MangaScreenSmallImpl( state: MangaScreenState.Success, snackbarHostState: SnackbarHostState, dateRelativeTime: Int, dateFormat: DateFormat, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, 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)?, onMigrateClicked: (() -> Unit)?, // SY --> onMetadataViewerClicked: () -> Unit, onEditInfoClicked: () -> Unit, onRecommendClicked: () -> Unit, onMergedSettingsClicked: () -> Unit, onMergeClicked: () -> Unit, onMergeWithAnotherClicked: () -> Unit, onOpenPagePreview: (Int) -> Unit, onMorePreviewsClicked: () -> Unit, // SY <-- // For bottom action menu onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, onMultiMarkAsReadClicked: (List, markAsRead: Boolean) -> Unit, onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMultiDeleteClicked: (List) -> Unit, // For chapter swipe onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, // Chapter selection onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit, onInvertSelection: () -> Unit, ) { val chapterListState = rememberLazyListState() val chapters = remember(state) { state.processedChapters } // SY --> val metadataDescription = metadataDescription(state.source) // SY <-- val internalOnBackPressed = { if (chapters.fastAny { it.selected }) { onAllChapterSelected(false) } else { onBackClicked() } } BackHandler(onBack = internalOnBackPressed) Scaffold( topBar = { val firstVisibleItemIndex by remember { derivedStateOf { chapterListState.firstVisibleItemIndex } } val firstVisibleItemScrollOffset by remember { derivedStateOf { chapterListState.firstVisibleItemScrollOffset } } val animatedTitleAlpha by animateFloatAsState( if (firstVisibleItemIndex > 0) 1f else 0f, ) val animatedBgAlpha by animateFloatAsState( if (firstVisibleItemIndex > 0 || firstVisibleItemScrollOffset > 0) 1f else 0f, ) MangaToolbar( title = state.manga.title, titleAlphaProvider = { animatedTitleAlpha }, backgroundAlphaProvider = { animatedBgAlpha }, hasFilters = state.manga.chaptersFiltered(), onBackClicked = internalOnBackPressed, onClickFilter = onFilterClicked, onClickShare = onShareClicked, onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, // SY --> onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite }, onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow }, onClickMergedSettings = onMergedSettingsClicked.takeIf { state.manga.source == MERGED_SOURCE_ID }, onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow }, // SY <-- actionModeCounter = chapters.count { it.selected }, onSelectAll = { onAllChapterSelected(true) }, onInvertSelection = { onInvertSelection() }, ) }, bottomBar = { SharedMangaBottomActionMenu( selected = chapters.filter { it.selected }, onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onDownloadChapter = onDownloadChapter, onMultiDeleteClicked = onMultiDeleteClicked, fillFraction = 1f, ) }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { AnimatedVisibility( visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected }, enter = fadeIn(), exit = fadeOut(), ) { ExtendedFloatingActionButton( text = { val id = if (state.chapters.fastAny { it.chapter.read }) { R.string.action_resume } else { R.string.action_start } Text(text = stringResource(id)) }, 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 = chapters.fastAll { !it.selected }, indicatorPadding = WindowInsets.systemBars.only(WindowInsetsSides.Top).asPaddingValues(), ) { 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(state.mergedData?.sources) }, 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, onAddToLibraryClicked = onAddToLibraryClicked, onWebViewClicked = onWebViewClicked, onWebViewLongClicked = onWebViewLongClicked, onTrackingClicked = onTrackingClicked, onEditCategory = onEditCategoryClicked, // SY --> onMergeClicked = onMergeClicked.takeUnless { state.showMergeInOverflow }, // SY <-- ) } // SY --> if (metadataDescription != null) { item( key = MangaScreenItem.METADATA_INFO, contentType = MangaScreenItem.METADATA_INFO, ) { metadataDescription( state, onMetadataViewerClicked, ) { onSearch(it, false) } } } // SY <-- 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, // SY --> doSearch = onSearch, searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) { SearchMetadataChips(state.meta, state.source, state.manga.genre) }, // SY <-- ) } // SY --> if (!state.showRecommendationsInOverflow || state.showMergeWithAnother) { item( key = MangaScreenItem.INFO_BUTTONS, contentType = MangaScreenItem.INFO_BUTTONS, ) { MangaInfoButtons( showRecommendsButton = !state.showRecommendationsInOverflow, showMergeWithAnotherButton = state.showMergeWithAnother, onRecommendClicked = onRecommendClicked, onMergeWithAnotherClicked = onMergeWithAnotherClicked, ) } } if (state.pagePreviewsState !is PagePreviewState.Unused) { item( key = MangaScreenItem.CHAPTER_PREVIEW, contentType = MangaScreenItem.CHAPTER_PREVIEW, ) { PagePreviews( pagePreviewState = state.pagePreviewsState, onOpenPage = onOpenPagePreview, onMorePreviewsClicked = onMorePreviewsClicked, ) } } // SY <-- item( key = MangaScreenItem.CHAPTER_HEADER, contentType = MangaScreenItem.CHAPTER_HEADER, ) { ChapterHeader( enabled = chapters.fastAll { !it.selected }, chapterCount = chapters.size, missingChapterCount = chapters.map { it.chapter.chapterNumber }.missingChaptersCount(), onClick = onFilterClicked, ) } sharedChapterItems( manga = state.manga, chapters = chapters, dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, // SY --> alwaysShowReadingProgress = state.alwaysShowReadingProgress, // SY <-- onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, onChapterSelected = onChapterSelected, onChapterSwipe = onChapterSwipe, ) } } } } } @Composable fun MangaScreenLargeImpl( state: MangaScreenState.Success, snackbarHostState: SnackbarHostState, dateRelativeTime: Int, dateFormat: DateFormat, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, onBackClicked: () -> Unit, onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, 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)?, onMigrateClicked: (() -> Unit)?, // SY --> onMetadataViewerClicked: () -> Unit, onEditInfoClicked: () -> Unit, onRecommendClicked: () -> Unit, onMergedSettingsClicked: () -> Unit, onMergeClicked: () -> Unit, onMergeWithAnotherClicked: () -> Unit, onOpenPagePreview: (Int) -> Unit, onMorePreviewsClicked: () -> Unit, // SY <-- // For bottom action menu onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, onMultiMarkAsReadClicked: (List, markAsRead: Boolean) -> Unit, onMarkPreviousAsReadClicked: (Chapter) -> Unit, onMultiDeleteClicked: (List) -> Unit, // For swipe actions onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, // Chapter selection onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onAllChapterSelected: (Boolean) -> Unit, onInvertSelection: () -> Unit, ) { val layoutDirection = LocalLayoutDirection.current val density = LocalDensity.current val chapters = remember(state) { state.processedChapters } // SY --> val metadataDescription = metadataDescription(state.source) // SY <-- val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues() var topBarHeight by remember { mutableIntStateOf(0) } PullRefresh( refreshing = state.isRefreshingData, onRefresh = onRefresh, enabled = chapters.fastAll { !it.selected }, indicatorPadding = PaddingValues( start = insetPadding.calculateStartPadding(layoutDirection), top = with(density) { topBarHeight.toDp() }, end = insetPadding.calculateEndPadding(layoutDirection), ), ) { val chapterListState = rememberLazyListState() val internalOnBackPressed = { if (chapters.fastAny { it.selected }) { onAllChapterSelected(false) } else { onBackClicked() } } BackHandler(onBack = internalOnBackPressed) Scaffold( topBar = { MangaToolbar( modifier = Modifier.onSizeChanged { topBarHeight = it.height }, title = state.manga.title, titleAlphaProvider = { if (chapters.fastAny { it.selected }) 1f else 0f }, backgroundAlphaProvider = { 1f }, hasFilters = state.manga.chaptersFiltered(), onBackClicked = internalOnBackPressed, onClickFilter = onFilterButtonClicked, onClickShare = onShareClicked, onClickDownload = onDownloadActionClicked, onClickEditCategory = onEditCategoryClicked, onClickRefresh = onRefresh, onClickMigrate = onMigrateClicked, // SY --> onClickEditInfo = onEditInfoClicked.takeIf { state.manga.favorite }, onClickRecommend = onRecommendClicked.takeIf { state.showRecommendationsInOverflow }, onClickMergedSettings = onMergedSettingsClicked.takeIf { state.manga.source == MERGED_SOURCE_ID }, onClickMerge = onMergeClicked.takeIf { state.showMergeInOverflow }, // SY <-- actionModeCounter = chapters.count { it.selected }, onSelectAll = { onAllChapterSelected(true) }, onInvertSelection = { onInvertSelection() }, ) }, bottomBar = { Box( modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.BottomEnd, ) { SharedMangaBottomActionMenu( selected = chapters.filter { it.selected }, onMultiBookmarkClicked = onMultiBookmarkClicked, onMultiMarkAsReadClicked = onMultiMarkAsReadClicked, onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked, onDownloadChapter = onDownloadChapter, onMultiDeleteClicked = onMultiDeleteClicked, fillFraction = 0.5f, ) } }, snackbarHost = { SnackbarHost(hostState = snackbarHostState) }, floatingActionButton = { AnimatedVisibility( visible = chapters.fastAny { !it.chapter.read } && chapters.fastAll { !it.selected }, enter = fadeIn(), exit = fadeOut(), ) { ExtendedFloatingActionButton( text = { val id = if (state.chapters.fastAny { it.chapter.read }) { R.string.action_resume } else { R.string.action_start } Text(text = stringResource(id)) }, icon = { Icon(imageVector = Icons.Filled.PlayArrow, contentDescription = null) }, onClick = onContinueReading, expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(), ) } }, ) { contentPadding -> 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(state.mergedData?.sources) }, 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, onAddToLibraryClicked = onAddToLibraryClicked, onWebViewClicked = onWebViewClicked, onWebViewLongClicked = onWebViewLongClicked, onTrackingClicked = onTrackingClicked, onEditCategory = onEditCategoryClicked, // SY --> onMergeClicked = onMergeClicked.takeUnless { state.showMergeInOverflow }, // SY <-- ) // SY --> metadataDescription?.invoke( state, onMetadataViewerClicked, ) { onSearch(it, false) } // SY <-- ExpandableMangaDescription( defaultExpandState = true, description = state.manga.description, tagsProvider = { state.manga.genre }, onTagSearch = onTagSearch, onCopyTagToClipboard = onCopyTagToClipboard, // SY --> doSearch = onSearch, searchMetadataChips = remember(state.meta, state.source.id, state.manga.genre) { SearchMetadataChips(state.meta, state.source, state.manga.genre) }, // SY <-- ) // SY --> if (!state.showRecommendationsInOverflow || state.showMergeWithAnother) { MangaInfoButtons( showRecommendsButton = !state.showRecommendationsInOverflow, showMergeWithAnotherButton = state.showMergeWithAnother, onRecommendClicked = onRecommendClicked, onMergeWithAnotherClicked = onMergeWithAnotherClicked, ) } if (state.pagePreviewsState !is PagePreviewState.Unused) { PagePreviews( pagePreviewState = state.pagePreviewsState, onOpenPage = onOpenPagePreview, onMorePreviewsClicked = onMorePreviewsClicked, ) } // SY <-- } }, 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, ) { ChapterHeader( enabled = chapters.fastAll { !it.selected }, chapterCount = chapters.size, missingChapterCount = chapters.map { it.chapter.chapterNumber }.missingChaptersCount(), onClick = onFilterButtonClicked, ) } sharedChapterItems( manga = state.manga, chapters = chapters, dateRelativeTime = dateRelativeTime, dateFormat = dateFormat, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, // SY --> alwaysShowReadingProgress = state.alwaysShowReadingProgress, // SY <-- onChapterClicked = onChapterClicked, onDownloadChapter = onDownloadChapter, onChapterSelected = onChapterSelected, onChapterSwipe = onChapterSwipe, ) } } }, ) } } } @Composable private fun SharedMangaBottomActionMenu( selected: List, modifier: Modifier = Modifier, onMultiBookmarkClicked: (List, bookmarked: Boolean) -> Unit, onMultiMarkAsReadClicked: (List, markAsRead: Boolean) -> Unit, onMarkPreviousAsReadClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, onMultiDeleteClicked: (List) -> Unit, fillFraction: Float, ) { 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 { onDownloadChapter != null && selected.fastAny { it.downloadState == Download.State.DOWNLOADED } }, ) } private fun LazyListScope.sharedChapterItems( manga: Manga, chapters: List, dateRelativeTime: Int, dateFormat: DateFormat, chapterSwipeStartAction: LibraryPreferences.ChapterSwipeAction, chapterSwipeEndAction: LibraryPreferences.ChapterSwipeAction, // SY --> alwaysShowReadingProgress: Boolean, // SY <-- onChapterClicked: (Chapter) -> Unit, onDownloadChapter: ((List, ChapterDownloadAction) -> Unit)?, onChapterSelected: (ChapterItem, Boolean, Boolean, Boolean) -> Unit, onChapterSwipe: (ChapterItem, LibraryPreferences.ChapterSwipeAction) -> Unit, ) { items( items = chapters, key = { "chapter-${it.chapter.id}" }, contentType = { MangaScreenItem.CHAPTER }, ) { chapterItem -> val haptic = LocalHapticFeedback.current val context = LocalContext.current MangaChapterListItem( title = if (manga.displayMode == Manga.CHAPTER_DISPLAY_NUMBER) { stringResource( R.string.display_mode_chapter, chapterDecimalFormat.format(chapterItem.chapter.chapterNumber.toDouble()), ) } else { chapterItem.chapter.name }, date = chapterItem.chapter.dateUpload .takeIf { it > 0L } ?.let { // SY --> if (manga.isEhBasedManga()) { MetadataUtil.EX_DATE_FORMAT.format(Date(it)) } else { Date(it).toRelativeString( context, dateRelativeTime, dateFormat, ) } // SY <-- }, readProgress = chapterItem.chapter.lastPageRead .takeIf { /* SY --> */(!chapterItem.chapter.read || alwaysShowReadingProgress)/* SY <-- */ && it > 0L } ?.let { stringResource( R.string.chapter_progress, it + 1, ) }, scanlator = chapterItem.chapter.scanlator.takeIf { !it.isNullOrBlank() /* SY --> */ && chapterItem.showScanlator /* SY <-- */ }, // SY --> sourceName = chapterItem.sourceName, // SY <-- read = chapterItem.chapter.read, bookmark = chapterItem.chapter.bookmark, selected = chapterItem.selected, downloadIndicatorEnabled = chapters.fastAll { !it.selected }, downloadStateProvider = { chapterItem.downloadState }, downloadProgressProvider = { chapterItem.downloadProgress }, chapterSwipeStartAction = chapterSwipeStartAction, chapterSwipeEndAction = chapterSwipeEndAction, onLongClick = { onChapterSelected(chapterItem, !chapterItem.selected, true, true) haptic.performHapticFeedback(HapticFeedbackType.LongPress) }, onClick = { onChapterItemClick( chapterItem = chapterItem, chapters = chapters, onToggleSelection = { onChapterSelected(chapterItem, !chapterItem.selected, true, false) }, onChapterClicked = onChapterClicked, ) }, onDownloadClick = if (onDownloadChapter != null) { { onDownloadChapter(listOf(chapterItem), it) } } else { null }, onChapterSwipe = { onChapterSwipe(chapterItem, it) }, ) } } private fun onChapterItemClick( chapterItem: ChapterItem, chapters: List, onToggleSelection: (Boolean) -> Unit, onChapterClicked: (Chapter) -> Unit, ) { when { chapterItem.selected -> onToggleSelection(false) chapters.fastAny { it.selected } -> onToggleSelection(true) else -> onChapterClicked(chapterItem.chapter) } } // SY --> typealias MetadataDescriptionComposable = @Composable (state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit) -> Unit @Composable fun metadataDescription(source: Source): MetadataDescriptionComposable? { val metadataSource = remember(source.id) { source.getMainSource>() } return remember(metadataSource) { when (metadataSource) { is EHentai -> { state, openMetadataViewer, search -> EHentaiDescription(state, openMetadataViewer, search) } is MangaDex -> { state, openMetadataViewer, _ -> MangaDexDescription(state, openMetadataViewer) } is NHentai -> { state, openMetadataViewer, _ -> NHentaiDescription(state, openMetadataViewer) } is EightMuses -> { state, openMetadataViewer, _ -> EightMusesDescription(state, openMetadataViewer) } is HBrowse -> { state, openMetadataViewer, _ -> HBrowseDescription(state, openMetadataViewer) } is Pururin -> { state, openMetadataViewer, _ -> PururinDescription(state, openMetadataViewer) } is Tsumino -> { state, openMetadataViewer, _ -> TsuminoDescription(state, openMetadataViewer) } else -> null } } } // SY <--