MangaScreen: Ditch the expanded app bar (#7470)
Animating the content padding that's used for the lazy list is heavy. A simple fix to *just* offset the list is blocked by a Compose fling issue (b/179417109). So I decided to go with the previous layout of this screen by putting everything in the list. MangaInfoHeader is split into separate composables to avoid jank when the item is being inflated. (cherry picked from commit 34906a74253e7463ac23ea96496c59198884e0be) # Conflicts: # app/src/main/java/eu/kanade/presentation/manga/MangaScreen.kt # app/src/main/java/eu/kanade/presentation/manga/components/MangaInfoHeader.kt # app/src/main/java/eu/kanade/presentation/manga/components/MangaTopAppBar.kt
This commit is contained in:
parent
4e29fd5b2a
commit
d5aecaad21
@ -2,15 +2,11 @@ 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.animation.rememberSplineBasedDecay
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.rememberScrollableState
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
@ -36,12 +32,10 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberTopAppBarScrollState
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
@ -49,7 +43,6 @@ import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
@ -65,13 +58,14 @@ import eu.kanade.presentation.components.Scaffold
|
||||
import eu.kanade.presentation.components.SwipeRefreshIndicator
|
||||
import eu.kanade.presentation.components.VerticalFastScroller
|
||||
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.MangaInfoHeader
|
||||
import eu.kanade.presentation.manga.components.MangaInfoBox
|
||||
import eu.kanade.presentation.manga.components.MangaInfoButtons
|
||||
import eu.kanade.presentation.manga.components.MangaSmallAppBar
|
||||
import eu.kanade.presentation.manga.components.MangaTopAppBar
|
||||
import eu.kanade.presentation.manga.components.SearchMetadataChips
|
||||
import eu.kanade.presentation.util.ExitUntilCollapsedScrollBehavior
|
||||
import eu.kanade.presentation.util.isScrolledToEnd
|
||||
import eu.kanade.presentation.util.isScrollingUp
|
||||
import eu.kanade.presentation.util.plus
|
||||
@ -85,7 +79,6 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.lang.toRelativeString
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.source.getMainSource
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.text.DecimalFormat
|
||||
import java.text.DecimalFormatSymbols
|
||||
import java.util.Date
|
||||
@ -238,175 +231,214 @@ private fun MangaScreenSmallImpl(
|
||||
onMultiDeleteClicked: (List<Chapter>) -> Unit,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
|
||||
val scrollBehavior = ExitUntilCollapsedScrollBehavior(rememberTopAppBarScrollState(), decayAnimationSpec)
|
||||
val chapterListState = rememberLazyListState()
|
||||
SideEffect {
|
||||
if (chapterListState.firstVisibleItemIndex > 0 || chapterListState.firstVisibleItemScrollOffset > 0) {
|
||||
// Should go here after a configuration change
|
||||
// Safe to say that the app bar is fully scrolled
|
||||
scrollBehavior.state.offset = scrollBehavior.state.offsetLimit
|
||||
}
|
||||
}
|
||||
|
||||
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||
val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(1) }
|
||||
SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(state.isRefreshingInfo || state.isRefreshingChapter),
|
||||
onRefresh = onRefresh,
|
||||
indicatorPadding = PaddingValues(
|
||||
start = insetPadding.calculateStartPadding(layoutDirection),
|
||||
top = with(LocalDensity.current) { topBarHeight.toDp() },
|
||||
end = insetPadding.calculateEndPadding(layoutDirection),
|
||||
),
|
||||
indicator = { s, trigger ->
|
||||
SwipeRefreshIndicator(
|
||||
state = s,
|
||||
refreshTriggerDistance = trigger,
|
||||
val chapters = remember(state) { state.processedChapters.toList() }
|
||||
val selected = remember(chapters) { emptyList<ChapterItem>().toMutableStateList() }
|
||||
val selectedPositions = remember(chapters) { arrayOf(-1, -1) } // first and last selected index in list
|
||||
// SY -->
|
||||
val metadataSource = remember(state.source.id) { state.source.getMainSource<MetadataSource<*, *>>() }
|
||||
// SY <--
|
||||
|
||||
val internalOnBackPressed = {
|
||||
if (selected.isNotEmpty()) {
|
||||
selected.clear()
|
||||
} else {
|
||||
onBackClicked()
|
||||
}
|
||||
}
|
||||
BackHandler(onBack = internalOnBackPressed)
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.padding(insetPadding),
|
||||
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,
|
||||
)
|
||||
MangaSmallAppBar(
|
||||
title = state.manga.title,
|
||||
titleAlphaProvider = { animatedTitleAlpha },
|
||||
backgroundAlphaProvider = { animatedBgAlpha },
|
||||
incognitoMode = state.isIncognitoMode,
|
||||
downloadedOnlyMode = state.isDownloadedOnlyMode,
|
||||
onBackClicked = onBackClicked,
|
||||
onShareClicked = onShareClicked,
|
||||
onDownloadClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
// SY -->
|
||||
showEditInfo = state.manga.favorite,
|
||||
onEditInfoClicked = onEditInfoClicked,
|
||||
showRecommends = state.showRecommendationsInOverflow,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
showMergeSettings = state.manga.source == MERGED_SOURCE_ID,
|
||||
onMergedSettingsClicked = onMergedSettingsClicked,
|
||||
// SY <--
|
||||
actionModeCounter = selected.size,
|
||||
onSelectAll = {
|
||||
selected.clear()
|
||||
selected.addAll(chapters)
|
||||
},
|
||||
onInvertSelection = {
|
||||
val toSelect = chapters - selected
|
||||
selected.clear()
|
||||
selected.addAll(toSelect)
|
||||
},
|
||||
)
|
||||
},
|
||||
) {
|
||||
val chapters = remember(state) { state.processedChapters.toList() }
|
||||
val selected = remember(chapters) { emptyList<ChapterItem>().toMutableStateList() }
|
||||
val selectedPositions = remember(chapters) { arrayOf(-1, -1) } // first and last selected index in list
|
||||
|
||||
val internalOnBackPressed = {
|
||||
if (selected.isNotEmpty()) {
|
||||
selected.clear()
|
||||
} else {
|
||||
onBackClicked()
|
||||
}
|
||||
}
|
||||
BackHandler(onBack = internalOnBackPressed)
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.padding(insetPadding),
|
||||
topBar = {
|
||||
MangaTopAppBar(
|
||||
bottomBar = {
|
||||
SharedMangaBottomActionMenu(
|
||||
selected = selected,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
||||
fillFraction = 1f,
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(
|
||||
visible = chapters.any { !it.chapter.read } && selected.isEmpty(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
val id = if (chapters.any { it.chapter.read }) {
|
||||
R.string.action_resume
|
||||
} else {
|
||||
R.string.action_start
|
||||
}
|
||||
Text(text = stringResource(id))
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||
modifier = Modifier
|
||||
.scrollable(
|
||||
state = rememberScrollableState {
|
||||
var consumed = runBlocking { chapterListState.scrollBy(-it) } * -1
|
||||
if (consumed == 0f) {
|
||||
// Pass scroll to app bar if we're on the top of the list
|
||||
val newOffset =
|
||||
(scrollBehavior.state.offset + it).coerceIn(scrollBehavior.state.offsetLimit, 0f)
|
||||
consumed = newOffset - scrollBehavior.state.offset
|
||||
scrollBehavior.state.offset = newOffset
|
||||
}
|
||||
consumed
|
||||
},
|
||||
orientation = Orientation.Vertical,
|
||||
interactionSource = chapterListState.interactionSource as MutableInteractionSource,
|
||||
),
|
||||
title = state.manga.title,
|
||||
author = state.manga.author,
|
||||
artist = state.manga.artist,
|
||||
description = state.manga.description,
|
||||
tagsProvider = { state.manga.genre },
|
||||
coverDataProvider = { state.manga },
|
||||
sourceName = remember { state.source.getNameForMangaInfo() },
|
||||
isStubSource = remember { state.source is SourceManager.StubSource },
|
||||
favorite = state.manga.favorite,
|
||||
status = state.manga.status,
|
||||
trackingCount = state.trackingCount,
|
||||
chapterCount = chapters.size,
|
||||
chapterFiltered = state.manga.chaptersFiltered(),
|
||||
incognitoMode = state.isIncognitoMode,
|
||||
downloadedOnlyMode = state.isDownloadedOnlyMode,
|
||||
fromSource = state.isFromSource,
|
||||
onBackClicked = internalOnBackPressed,
|
||||
onCoverClick = onCoverClicked,
|
||||
onTagClicked = onTagClicked,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onTrackingClicked = onTrackingClicked,
|
||||
onFilterButtonClicked = onFilterButtonClicked,
|
||||
onShareClicked = onShareClicked,
|
||||
onDownloadClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
onEditInfoClicked = onEditInfoClicked,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
showRecommendsInOverflow = state.showRecommendationsInOverflow,
|
||||
showMergedSettings = state.manga.source == MERGED_SOURCE_ID,
|
||||
onMergedSettingsClicked = onMergedSettingsClicked,
|
||||
onMergeClicked = onMergeClicked,
|
||||
doGlobalSearch = onSearch,
|
||||
showMergeWithAnother = state.showMergeWithAnother,
|
||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||
mangaMetadataHeader = getDescriptionComposable(
|
||||
source = remember { state.source.getMainSource<MetadataSource<*, *>>() },
|
||||
state = state,
|
||||
openMetadataViewer = onMetadataViewerClicked,
|
||||
search = { onSearch(it, false) },
|
||||
),
|
||||
searchMetadataChips = remember(state) { SearchMetadataChips(state.meta, state.source, state.manga.genre) },
|
||||
scrollBehavior = scrollBehavior,
|
||||
actionModeCounter = selected.size,
|
||||
onSelectAll = {
|
||||
selected.clear()
|
||||
selected.addAll(chapters)
|
||||
},
|
||||
onInvertSelection = {
|
||||
val toSelect = chapters - selected
|
||||
selected.clear()
|
||||
selected.addAll(toSelect)
|
||||
},
|
||||
onSmallAppBarHeightChanged = onTopBarHeightChanged,
|
||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
|
||||
)
|
||||
}
|
||||
},
|
||||
) { contentPadding ->
|
||||
val noTopContentPadding = PaddingValues(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||
bottom = contentPadding.calculateBottomPadding(),
|
||||
) + WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||
val topPadding = contentPadding.calculateTopPadding()
|
||||
|
||||
SwipeRefresh(
|
||||
state = rememberSwipeRefreshState(state.isRefreshingInfo || state.isRefreshingChapter),
|
||||
onRefresh = onRefresh,
|
||||
indicatorPadding = contentPadding,
|
||||
indicator = { s, trigger ->
|
||||
SwipeRefreshIndicator(
|
||||
state = s,
|
||||
refreshTriggerDistance = trigger,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
SharedMangaBottomActionMenu(
|
||||
selected = selected,
|
||||
onMultiBookmarkClicked = onMultiBookmarkClicked,
|
||||
onMultiMarkAsReadClicked = onMultiMarkAsReadClicked,
|
||||
onMarkPreviousAsReadClicked = onMarkPreviousAsReadClicked,
|
||||
onDownloadChapter = onDownloadChapter,
|
||||
onMultiDeleteClicked = onMultiDeleteClicked,
|
||||
fillFraction = 1f,
|
||||
)
|
||||
},
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
AnimatedVisibility(
|
||||
visible = chapters.any { !it.chapter.read } && selected.isEmpty(),
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
text = {
|
||||
val id = if (chapters.any { it.chapter.read }) {
|
||||
R.string.action_resume
|
||||
} else {
|
||||
R.string.action_start
|
||||
}
|
||||
Text(text = stringResource(id))
|
||||
},
|
||||
icon = { Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null) },
|
||||
onClick = onContinueReading,
|
||||
expanded = chapterListState.isScrollingUp() || chapterListState.isScrolledToEnd(),
|
||||
modifier = Modifier
|
||||
.padding(WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()),
|
||||
)
|
||||
}
|
||||
},
|
||||
) { contentPadding ->
|
||||
val withNavBarContentPadding = contentPadding +
|
||||
WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||
) {
|
||||
VerticalFastScroller(
|
||||
listState = chapterListState,
|
||||
thumbAllowed = { scrollBehavior.state.offset == scrollBehavior.state.offsetLimit },
|
||||
topContentPadding = withNavBarContentPadding.calculateTopPadding(),
|
||||
endContentPadding = withNavBarContentPadding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
topContentPadding = topPadding,
|
||||
endContentPadding = noTopContentPadding.calculateEndPadding(layoutDirection),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
state = chapterListState,
|
||||
contentPadding = withNavBarContentPadding,
|
||||
contentPadding = noTopContentPadding,
|
||||
) {
|
||||
item(contentType = "info_box") {
|
||||
MangaInfoBox(
|
||||
windowWidthSizeClass = WindowWidthSizeClass.Compact,
|
||||
appBarPadding = topPadding,
|
||||
title = state.manga.title,
|
||||
author = state.manga.author,
|
||||
artist = state.manga.artist,
|
||||
sourceName = remember { state.source.getNameForMangaInfo() },
|
||||
isStubSource = remember { state.source is SourceManager.StubSource },
|
||||
coverDataProvider = { state.manga },
|
||||
status = state.manga.status,
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
)
|
||||
}
|
||||
|
||||
item(contentType = "action_row") {
|
||||
MangaActionRow(
|
||||
favorite = state.manga.favorite,
|
||||
trackingCount = state.trackingCount,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onTrackingClicked = onTrackingClicked,
|
||||
onEditCategory = onEditCategoryClicked,
|
||||
// SY -->
|
||||
onMergeClicked = onMergeClicked,
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
if (metadataSource != null) {
|
||||
item(contentType = "metadata_info") {
|
||||
metadataSource.DescriptionComposable(
|
||||
state = state,
|
||||
openMetadataViewer = onMetadataViewerClicked,
|
||||
search = { onSearch(it, false) },
|
||||
)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
item(contentType = "desc") {
|
||||
ExpandableMangaDescription(
|
||||
defaultExpandState = state.isFromSource,
|
||||
description = state.manga.description,
|
||||
tagsProvider = { state.manga.genre },
|
||||
onTagClicked = onTagClicked,
|
||||
// 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(contentType = "info_buttons") {
|
||||
MangaInfoButtons(
|
||||
showRecommendsButton = !state.showRecommendationsInOverflow,
|
||||
showMergeWithAnotherButton = state.showMergeWithAnother,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||
)
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
|
||||
item(contentType = "header") {
|
||||
ChapterHeader(
|
||||
chapterCount = chapters.size,
|
||||
isChapterFiltered = state.manga.chaptersFiltered(),
|
||||
onFilterButtonClicked = onFilterButtonClicked,
|
||||
)
|
||||
}
|
||||
|
||||
sharedChapterItems(
|
||||
chapters = chapters,
|
||||
state = state,
|
||||
@ -462,6 +494,10 @@ fun MangaScreenLargeImpl(
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val density = LocalDensity.current
|
||||
|
||||
// SY -->
|
||||
val metadataSource = remember(state.source.id) { state.source.getMainSource<MetadataSource<*, *>>() }
|
||||
// SY <--
|
||||
|
||||
val insetPadding = WindowInsets.systemBars.only(WindowInsetsSides.Horizontal).asPaddingValues()
|
||||
val (topBarHeight, onTopBarHeightChanged) = remember { mutableStateOf(0) }
|
||||
SwipeRefresh(
|
||||
@ -571,45 +607,66 @@ fun MangaScreenLargeImpl(
|
||||
Row {
|
||||
val withNavBarContentPadding = contentPadding +
|
||||
WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||
MangaInfoHeader(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(bottom = withNavBarContentPadding.calculateBottomPadding()),
|
||||
windowWidthSizeClass = WindowWidthSizeClass.Expanded,
|
||||
appBarPadding = contentPadding.calculateTopPadding(),
|
||||
title = state.manga.title,
|
||||
author = state.manga.author,
|
||||
artist = state.manga.artist,
|
||||
description = state.manga.description,
|
||||
tagsProvider = { state.manga.genre },
|
||||
sourceName = remember { state.source.getNameForMangaInfo() },
|
||||
isStubSource = remember { state.source is SourceManager.StubSource },
|
||||
coverDataProvider = { state.manga },
|
||||
favorite = state.manga.favorite,
|
||||
status = state.manga.status,
|
||||
trackingCount = state.trackingCount,
|
||||
fromSource = state.isFromSource,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onTrackingClicked = onTrackingClicked,
|
||||
onMergeClicked = onMergeClicked,
|
||||
onTagClicked = onTagClicked,
|
||||
onEditCategory = onEditCategoryClicked,
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
showRecommendsInOverflow = state.showRecommendationsInOverflow,
|
||||
showMergeWithAnother = state.showMergeWithAnother,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||
mangaMetadataHeader = getDescriptionComposable(
|
||||
source = remember { state.source.getMainSource<MetadataSource<*, *>>() },
|
||||
) {
|
||||
MangaInfoBox(
|
||||
windowWidthSizeClass = windowWidthSizeClass,
|
||||
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 SourceManager.StubSource },
|
||||
coverDataProvider = { state.manga },
|
||||
status = state.manga.status,
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
)
|
||||
MangaActionRow(
|
||||
favorite = state.manga.favorite,
|
||||
trackingCount = state.trackingCount,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onTrackingClicked = onTrackingClicked,
|
||||
onEditCategory = onEditCategoryClicked,
|
||||
// SY -->
|
||||
onMergeClicked = onMergeClicked,
|
||||
// SY <--
|
||||
)
|
||||
// SY -->
|
||||
metadataSource?.DescriptionComposable(
|
||||
state = state,
|
||||
openMetadataViewer = onMetadataViewerClicked,
|
||||
search = { onSearch(it, false) },
|
||||
),
|
||||
searchMetadataChips = remember(state) { SearchMetadataChips(state.meta, state.source, state.manga.genre) },
|
||||
)
|
||||
)
|
||||
// SY <--
|
||||
ExpandableMangaDescription(
|
||||
defaultExpandState = true,
|
||||
description = state.manga.description,
|
||||
tagsProvider = { state.manga.genre },
|
||||
onTagClicked = onTagClicked,
|
||||
// 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,
|
||||
)
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
val chaptersWeight = if (windowWidthSizeClass == WindowWidthSizeClass.Medium) 1f else 2f
|
||||
VerticalFastScroller(
|
||||
@ -727,7 +784,7 @@ private fun LazyListScope.sharedChapterItems(
|
||||
}
|
||||
}
|
||||
val lastPageRead = remember(chapter.lastPageRead) {
|
||||
chapter.lastPageRead.takeIf { !chapter.read && it > 0 }
|
||||
chapter.lastPageRead.takeIf { /* SY --> */(!chapter.read || state.alwaysShowPageProgress)/* SY <-- */ && it > 0 }
|
||||
}
|
||||
val scanlator = remember(chapter.scanlator) { chapter.scanlator.takeIf { !it.isNullOrBlank() } }
|
||||
|
||||
@ -840,19 +897,3 @@ private fun onChapterItemClick(
|
||||
else -> onChapterClicked(chapterItem.chapter)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@ReadOnlyComposable
|
||||
@Stable
|
||||
private fun getDescriptionComposable(
|
||||
source: MetadataSource<*, *>?,
|
||||
state: MangaScreenState.Success,
|
||||
openMetadataViewer: () -> Unit,
|
||||
search: (String) -> Unit,
|
||||
): (@Composable () -> Unit)? {
|
||||
return if (source != null) {
|
||||
@Composable {
|
||||
source.DescriptionComposable(state = state, openMetadataViewer = openMetadataViewer, search = search)
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
@ -83,219 +83,221 @@ import kotlin.math.roundToInt
|
||||
private val whitespaceLineRegex = Regex("[\\r\\n]{2,}", setOf(RegexOption.MULTILINE))
|
||||
|
||||
@Composable
|
||||
fun MangaInfoHeader(
|
||||
fun MangaInfoBox(
|
||||
modifier: Modifier = Modifier,
|
||||
windowWidthSizeClass: WindowWidthSizeClass,
|
||||
appBarPadding: Dp,
|
||||
title: String,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
description: String?,
|
||||
tagsProvider: () -> List<String>?,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
coverDataProvider: () -> Manga,
|
||||
favorite: Boolean,
|
||||
status: Long,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
// Backdrop
|
||||
val backdropGradientColors = listOf(
|
||||
Color.Transparent,
|
||||
MaterialTheme.colorScheme.background,
|
||||
)
|
||||
AsyncImage(
|
||||
model = coverDataProvider(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
brush = Brush.verticalGradient(colors = backdropGradientColors),
|
||||
)
|
||||
}
|
||||
.alpha(.2f),
|
||||
)
|
||||
|
||||
// Manga & source info
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
|
||||
MangaAndSourceTitlesSmall(
|
||||
appBarPadding = appBarPadding,
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
context = LocalContext.current,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
)
|
||||
} else {
|
||||
MangaAndSourceTitlesLarge(
|
||||
appBarPadding = appBarPadding,
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
context = LocalContext.current,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MangaActionRow(
|
||||
modifier: Modifier = Modifier,
|
||||
favorite: Boolean,
|
||||
trackingCount: Int,
|
||||
fromSource: Boolean,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onMergeClicked: () -> Unit,
|
||||
onTagClicked: (String) -> Unit,
|
||||
onEditCategory: (() -> Unit)?,
|
||||
onCoverClick: () -> Unit,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
onRecommendClicked: () -> Unit,
|
||||
showRecommendsInOverflow: Boolean,
|
||||
showMergeWithAnother: Boolean,
|
||||
onMergeWithAnotherClicked: () -> Unit,
|
||||
mangaMetadataHeader: (@Composable () -> Unit)?,
|
||||
// SY -->
|
||||
onMergeClicked: () -> Unit,
|
||||
// SY <--
|
||||
) {
|
||||
Row(modifier = modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||
MangaActionButton(
|
||||
title = if (favorite) {
|
||||
stringResource(R.string.in_library)
|
||||
} else {
|
||||
stringResource(R.string.add_to_library)
|
||||
},
|
||||
icon = if (favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
|
||||
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||
onClick = onAddToLibraryClicked,
|
||||
onLongClick = onEditCategory,
|
||||
)
|
||||
if (onTrackingClicked != null) {
|
||||
MangaActionButton(
|
||||
title = if (trackingCount == 0) {
|
||||
stringResource(R.string.manga_tracking_tab)
|
||||
} else {
|
||||
quantityStringResource(id = R.plurals.num_trackers, quantity = trackingCount, trackingCount)
|
||||
},
|
||||
icon = if (trackingCount == 0) Icons.Default.Sync else Icons.Default.Done,
|
||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||
onClick = onTrackingClicked,
|
||||
)
|
||||
}
|
||||
if (onWebViewClicked != null) {
|
||||
MangaActionButton(
|
||||
title = stringResource(R.string.action_web_view),
|
||||
icon = Icons.Default.Public,
|
||||
color = defaultActionButtonColor,
|
||||
onClick = onWebViewClicked,
|
||||
)
|
||||
}
|
||||
// SY -->
|
||||
MangaActionButton(
|
||||
title = stringResource(R.string.merge),
|
||||
icon = Icons.Outlined.CallMerge,
|
||||
color = defaultActionButtonColor,
|
||||
onClick = onMergeClicked,
|
||||
)
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ExpandableMangaDescription(
|
||||
modifier: Modifier = Modifier,
|
||||
defaultExpandState: Boolean,
|
||||
description: String?,
|
||||
tagsProvider: () -> List<String>?,
|
||||
onTagClicked: (String) -> Unit,
|
||||
// SY -->
|
||||
searchMetadataChips: SearchMetadataChips?,
|
||||
doSearch: (query: String, global: Boolean) -> Unit,
|
||||
// SY <--
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(modifier = modifier) {
|
||||
Box {
|
||||
// Backdrop
|
||||
val backdropGradientColors = listOf(
|
||||
Color.Transparent,
|
||||
MaterialTheme.colorScheme.background,
|
||||
)
|
||||
AsyncImage(
|
||||
model = coverDataProvider(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.matchParentSize()
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(
|
||||
brush = Brush.verticalGradient(colors = backdropGradientColors),
|
||||
)
|
||||
}
|
||||
.alpha(.2f),
|
||||
)
|
||||
|
||||
// Manga & source info
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
if (windowWidthSizeClass == WindowWidthSizeClass.Compact) {
|
||||
MangaAndSourceTitlesSmall(
|
||||
appBarPadding = appBarPadding,
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
context = context,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
)
|
||||
} else {
|
||||
MangaAndSourceTitlesLarge(
|
||||
appBarPadding = appBarPadding,
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
context = context,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
status = status,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
)
|
||||
}
|
||||
}
|
||||
val (expanded, onExpanded) = rememberSaveable {
|
||||
mutableStateOf(defaultExpandState)
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
Row(modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
||||
val defaultActionButtonColor = MaterialTheme.colorScheme.onSurface.copy(alpha = .38f)
|
||||
MangaActionButton(
|
||||
title = if (favorite) {
|
||||
stringResource(R.string.in_library)
|
||||
} else {
|
||||
stringResource(R.string.add_to_library)
|
||||
},
|
||||
icon = if (favorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
|
||||
color = if (favorite) MaterialTheme.colorScheme.primary else defaultActionButtonColor,
|
||||
onClick = onAddToLibraryClicked,
|
||||
onLongClick = onEditCategory,
|
||||
)
|
||||
if (onTrackingClicked != null) {
|
||||
MangaActionButton(
|
||||
title = if (trackingCount == 0) {
|
||||
stringResource(R.string.manga_tracking_tab)
|
||||
} else {
|
||||
quantityStringResource(id = R.plurals.num_trackers, quantity = trackingCount, trackingCount)
|
||||
},
|
||||
icon = if (trackingCount == 0) Icons.Default.Sync else Icons.Default.Done,
|
||||
color = if (trackingCount == 0) defaultActionButtonColor else MaterialTheme.colorScheme.primary,
|
||||
onClick = onTrackingClicked,
|
||||
)
|
||||
}
|
||||
if (onWebViewClicked != null) {
|
||||
MangaActionButton(
|
||||
title = stringResource(R.string.action_web_view),
|
||||
icon = Icons.Default.Public,
|
||||
color = defaultActionButtonColor,
|
||||
onClick = onWebViewClicked,
|
||||
)
|
||||
}
|
||||
MangaActionButton(
|
||||
title = stringResource(R.string.merge),
|
||||
icon = Icons.Outlined.CallMerge,
|
||||
color = defaultActionButtonColor,
|
||||
onClick = onMergeClicked,
|
||||
)
|
||||
val desc =
|
||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(id = R.string.description_placeholder)
|
||||
val trimmedDescription = remember(desc) {
|
||||
desc
|
||||
.replace(whitespaceLineRegex, "\n")
|
||||
.trimEnd()
|
||||
}
|
||||
|
||||
// SY --> Manga metadata
|
||||
mangaMetadataHeader?.invoke()
|
||||
// SY <--
|
||||
|
||||
// Expandable description-tags
|
||||
Column {
|
||||
val (expanded, onExpanded) = rememberSaveable {
|
||||
mutableStateOf(fromSource || windowWidthSizeClass != WindowWidthSizeClass.Compact)
|
||||
}
|
||||
val desc =
|
||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(id = R.string.description_placeholder)
|
||||
val trimmedDescription = remember(desc) {
|
||||
desc
|
||||
.replace(whitespaceLineRegex, "\n")
|
||||
.trimEnd()
|
||||
}
|
||||
MangaSummary(
|
||||
expandedDescription = desc,
|
||||
shrunkDescription = trimmedDescription,
|
||||
expanded = expanded,
|
||||
MangaSummary(
|
||||
expandedDescription = desc,
|
||||
shrunkDescription = trimmedDescription,
|
||||
expanded = expanded,
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickableNoIndication(
|
||||
onLongClick = { context.copyToClipboard(desc, desc) },
|
||||
onClick = { onExpanded(!expanded) },
|
||||
),
|
||||
)
|
||||
val tags = tagsProvider()
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickableNoIndication(
|
||||
onLongClick = { context.copyToClipboard(desc, desc) },
|
||||
onClick = { onExpanded(!expanded) },
|
||||
),
|
||||
)
|
||||
val tags = tagsProvider()
|
||||
if (!tags.isNullOrEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 8.dp)
|
||||
.padding(vertical = 12.dp)
|
||||
.animateContentSize(),
|
||||
) {
|
||||
if (expanded) {
|
||||
if (searchMetadataChips != null) {
|
||||
NamespaceTags(
|
||||
tags = searchMetadataChips,
|
||||
onClick = onTagClicked,
|
||||
onLongClick = { doSearch(it, true) },
|
||||
)
|
||||
} else {
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
mainAxisSpacing = 4.dp,
|
||||
crossAxisSpacing = 8.dp,
|
||||
) {
|
||||
tags.forEach {
|
||||
TagsChip(
|
||||
text = it,
|
||||
onClick = { onTagClicked(it) },
|
||||
onLongClick = { doSearch(it, true) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(vertical = 12.dp)
|
||||
.animateContentSize(),
|
||||
) {
|
||||
if (expanded) {
|
||||
// SY -->
|
||||
if (searchMetadataChips != null) {
|
||||
NamespaceTags(
|
||||
tags = searchMetadataChips,
|
||||
onClick = onTagClicked,
|
||||
onLongClick = { doSearch(it, true) },
|
||||
)
|
||||
} else {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
// SY <--
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
mainAxisSpacing = 4.dp,
|
||||
crossAxisSpacing = 8.dp,
|
||||
) {
|
||||
items(items = tags) {
|
||||
tags.forEach {
|
||||
TagsChip(
|
||||
text = it,
|
||||
onClick = { onTagClicked(it) },
|
||||
// SY -->
|
||||
onLongClick = { doSearch(it, true) },
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(items = tags) {
|
||||
TagsChip(
|
||||
text = it,
|
||||
onClick = { onTagClicked(it) },
|
||||
// SY -->
|
||||
onLongClick = { doSearch(it, true) },
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY -->
|
||||
MangaInfoButtons(
|
||||
showRecommendsButton = !showRecommendsInOverflow,
|
||||
showMergeWithAnotherButton = showMergeWithAnother,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||
)
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,12 +54,14 @@ fun MangaSmallAppBar(
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
// SY -->
|
||||
showEditInfo: Boolean,
|
||||
onEditInfoClicked: () -> Unit,
|
||||
showRecommends: Boolean,
|
||||
onRecommendClicked: () -> Unit,
|
||||
showMergeSettings: Boolean,
|
||||
onMergedSettingsClicked: () -> Unit,
|
||||
// SY <--
|
||||
// For action mode
|
||||
actionModeCounter: Int,
|
||||
onSelectAll: () -> Unit,
|
||||
|
@ -1,167 +0,0 @@
|
||||
package eu.kanade.presentation.manga.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.presentation.manga.DownloadAction
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun MangaTopAppBar(
|
||||
modifier: Modifier = Modifier,
|
||||
title: String,
|
||||
author: String?,
|
||||
artist: String?,
|
||||
description: String?,
|
||||
tagsProvider: () -> List<String>?,
|
||||
coverDataProvider: () -> Manga,
|
||||
sourceName: String,
|
||||
isStubSource: Boolean,
|
||||
favorite: Boolean,
|
||||
status: Long,
|
||||
trackingCount: Int,
|
||||
chapterCount: Int?,
|
||||
chapterFiltered: Boolean,
|
||||
incognitoMode: Boolean,
|
||||
downloadedOnlyMode: Boolean,
|
||||
fromSource: Boolean,
|
||||
onBackClicked: () -> Unit,
|
||||
onCoverClick: () -> Unit,
|
||||
onTagClicked: (String) -> Unit,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onFilterButtonClicked: () -> Unit,
|
||||
onShareClicked: (() -> Unit)?,
|
||||
onDownloadClicked: ((DownloadAction) -> Unit)?,
|
||||
onEditCategoryClicked: (() -> Unit)?,
|
||||
onMigrateClicked: (() -> Unit)?,
|
||||
onEditInfoClicked: () -> Unit,
|
||||
onRecommendClicked: () -> Unit,
|
||||
showRecommendsInOverflow: Boolean,
|
||||
showMergedSettings: Boolean,
|
||||
onMergedSettingsClicked: () -> Unit,
|
||||
onMergeClicked: () -> Unit,
|
||||
showMergeWithAnother: Boolean,
|
||||
onMergeWithAnotherClicked: () -> Unit,
|
||||
doGlobalSearch: (query: String, global: Boolean) -> Unit,
|
||||
mangaMetadataHeader: (@Composable () -> Unit)?,
|
||||
searchMetadataChips: SearchMetadataChips?,
|
||||
scrollBehavior: TopAppBarScrollBehavior?,
|
||||
// For action mode
|
||||
actionModeCounter: Int,
|
||||
onSelectAll: () -> Unit,
|
||||
onInvertSelection: () -> Unit,
|
||||
onSmallAppBarHeightChanged: (Int) -> Unit,
|
||||
) {
|
||||
val scrollPercentageProvider = { scrollBehavior?.scrollFraction?.coerceIn(0f, 1f) ?: 0f }
|
||||
val inverseScrollPercentageProvider = { 1f - scrollPercentageProvider() }
|
||||
|
||||
Layout(
|
||||
modifier = modifier,
|
||||
content = {
|
||||
val (smallHeightPx, onSmallHeightPxChanged) = remember { mutableStateOf(0) }
|
||||
Column(modifier = Modifier.layoutId("mangaInfo")) {
|
||||
MangaInfoHeader(
|
||||
windowWidthSizeClass = WindowWidthSizeClass.Compact,
|
||||
appBarPadding = with(LocalDensity.current) { smallHeightPx.toDp() },
|
||||
title = title,
|
||||
author = author,
|
||||
artist = artist,
|
||||
description = description,
|
||||
tagsProvider = tagsProvider,
|
||||
sourceName = sourceName,
|
||||
isStubSource = isStubSource,
|
||||
coverDataProvider = coverDataProvider,
|
||||
favorite = favorite,
|
||||
status = status,
|
||||
trackingCount = trackingCount,
|
||||
fromSource = fromSource,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onTrackingClicked = onTrackingClicked,
|
||||
onMergeClicked = onMergeClicked,
|
||||
onTagClicked = onTagClicked,
|
||||
onEditCategory = onEditCategoryClicked,
|
||||
onCoverClick = onCoverClick,
|
||||
doSearch = doGlobalSearch,
|
||||
showRecommendsInOverflow = showRecommendsInOverflow,
|
||||
showMergeWithAnother = showMergeWithAnother,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||
mangaMetadataHeader = mangaMetadataHeader,
|
||||
searchMetadataChips = searchMetadataChips,
|
||||
)
|
||||
|
||||
ChapterHeader(
|
||||
chapterCount = chapterCount,
|
||||
isChapterFiltered = chapterFiltered,
|
||||
onFilterButtonClicked = onFilterButtonClicked,
|
||||
)
|
||||
}
|
||||
|
||||
MangaSmallAppBar(
|
||||
modifier = Modifier
|
||||
.layoutId("topBar")
|
||||
.onSizeChanged {
|
||||
onSmallHeightPxChanged(it.height)
|
||||
onSmallAppBarHeightChanged(it.height)
|
||||
},
|
||||
title = title,
|
||||
titleAlphaProvider = { if (actionModeCounter == 0) scrollPercentageProvider() else 1f },
|
||||
incognitoMode = incognitoMode,
|
||||
downloadedOnlyMode = downloadedOnlyMode,
|
||||
onBackClicked = onBackClicked,
|
||||
onShareClicked = onShareClicked,
|
||||
onDownloadClicked = onDownloadClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
// SY -->
|
||||
showEditInfo = favorite,
|
||||
onEditInfoClicked = onEditInfoClicked,
|
||||
showRecommends = showRecommendsInOverflow,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
showMergeSettings = showMergedSettings,
|
||||
onMergedSettingsClicked = onMergedSettingsClicked,
|
||||
// SY <--
|
||||
actionModeCounter = actionModeCounter,
|
||||
onSelectAll = onSelectAll,
|
||||
onInvertSelection = onInvertSelection,
|
||||
)
|
||||
},
|
||||
) { measurables, constraints ->
|
||||
val mangaInfoPlaceable = measurables
|
||||
.first { it.layoutId == "mangaInfo" }
|
||||
.measure(constraints.copy(maxHeight = Constraints.Infinity))
|
||||
val topBarPlaceable = measurables
|
||||
.first { it.layoutId == "topBar" }
|
||||
.measure(constraints)
|
||||
val mangaInfoHeight = mangaInfoPlaceable.height
|
||||
val topBarHeight = topBarPlaceable.height
|
||||
val mangaInfoSansTopBarHeightPx = mangaInfoHeight - topBarHeight
|
||||
val layoutHeight = topBarHeight +
|
||||
(mangaInfoSansTopBarHeightPx * inverseScrollPercentageProvider()).roundToInt()
|
||||
|
||||
layout(constraints.maxWidth, layoutHeight) {
|
||||
val mangaInfoY = (-mangaInfoSansTopBarHeightPx * scrollPercentageProvider()).roundToInt()
|
||||
mangaInfoPlaceable.place(0, mangaInfoY)
|
||||
topBarPlaceable.place(0, 0)
|
||||
|
||||
// Update offset limit
|
||||
val offsetLimit = -mangaInfoSansTopBarHeightPx.toFloat()
|
||||
if (scrollBehavior?.state?.offsetLimit != offsetLimit) {
|
||||
scrollBehavior?.state?.offsetLimit = offsetLimit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -306,6 +306,7 @@ class MangaPresenter(
|
||||
mergedData = mergedData,
|
||||
showRecommendationsInOverflow = preferences.recommendsInOverflow().get(),
|
||||
showMergeWithAnother = smartSearched,
|
||||
alwaysShowPageProgress = preferences.preserveReadingPosition().get() && manga.isEhBasedManga(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1333,6 +1334,7 @@ sealed class MangaScreenState {
|
||||
val mergedData: MergedMangaData?,
|
||||
val showRecommendationsInOverflow: Boolean,
|
||||
val showMergeWithAnother: Boolean,
|
||||
val alwaysShowPageProgress: Boolean,
|
||||
// SY <--
|
||||
) : MangaScreenState() {
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user