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,37 +231,15 @@ 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()) {
|
||||
@ -281,70 +252,39 @@ private fun MangaScreenSmallImpl(
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
.padding(insetPadding),
|
||||
topBar = {
|
||||
MangaTopAppBar(
|
||||
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
|
||||
val firstVisibleItemIndex by remember {
|
||||
derivedStateOf { chapterListState.firstVisibleItemIndex }
|
||||
}
|
||||
consumed
|
||||
},
|
||||
orientation = Orientation.Vertical,
|
||||
interactionSource = chapterListState.interactionSource as MutableInteractionSource,
|
||||
),
|
||||
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,
|
||||
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(),
|
||||
titleAlphaProvider = { animatedTitleAlpha },
|
||||
backgroundAlphaProvider = { animatedBgAlpha },
|
||||
incognitoMode = state.isIncognitoMode,
|
||||
downloadedOnlyMode = state.isDownloadedOnlyMode,
|
||||
fromSource = state.isFromSource,
|
||||
onBackClicked = internalOnBackPressed,
|
||||
onCoverClick = onCoverClicked,
|
||||
onTagClicked = onTagClicked,
|
||||
onAddToLibraryClicked = onAddToLibraryClicked,
|
||||
onWebViewClicked = onWebViewClicked,
|
||||
onTrackingClicked = onTrackingClicked,
|
||||
onFilterButtonClicked = onFilterButtonClicked,
|
||||
onBackClicked = onBackClicked,
|
||||
onShareClicked = onShareClicked,
|
||||
onDownloadClicked = onDownloadActionClicked,
|
||||
onEditCategoryClicked = onEditCategoryClicked,
|
||||
onMigrateClicked = onMigrateClicked,
|
||||
// SY -->
|
||||
showEditInfo = state.manga.favorite,
|
||||
onEditInfoClicked = onEditInfoClicked,
|
||||
showRecommends = state.showRecommendationsInOverflow,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
showRecommendsInOverflow = state.showRecommendationsInOverflow,
|
||||
showMergedSettings = state.manga.source == MERGED_SOURCE_ID,
|
||||
showMergeSettings = 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,
|
||||
// SY <--
|
||||
actionModeCounter = selected.size,
|
||||
onSelectAll = {
|
||||
selected.clear()
|
||||
@ -355,7 +295,6 @@ private fun MangaScreenSmallImpl(
|
||||
selected.clear()
|
||||
selected.addAll(toSelect)
|
||||
},
|
||||
onSmallAppBarHeightChanged = onTopBarHeightChanged,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
@ -394,19 +333,112 @@ private fun MangaScreenSmallImpl(
|
||||
}
|
||||
},
|
||||
) { contentPadding ->
|
||||
val withNavBarContentPadding = contentPadding +
|
||||
WindowInsets.navigationBars.only(WindowInsetsSides.Bottom).asPaddingValues()
|
||||
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,
|
||||
)
|
||||
},
|
||||
) {
|
||||
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,
|
||||
) {
|
||||
MangaInfoBox(
|
||||
windowWidthSizeClass = windowWidthSizeClass,
|
||||
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,
|
||||
onCoverClick = onCoverClicked,
|
||||
doSearch = onSearch,
|
||||
)
|
||||
MangaActionRow(
|
||||
favorite = state.manga.favorite,
|
||||
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<*, *>>() },
|
||||
// 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,40 +83,21 @@ 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,
|
||||
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)?,
|
||||
searchMetadataChips: SearchMetadataChips?,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(modifier = modifier) {
|
||||
Box {
|
||||
Box(modifier = modifier) {
|
||||
// Backdrop
|
||||
val backdropGradientColors = listOf(
|
||||
Color.Transparent,
|
||||
@ -145,7 +126,7 @@ fun MangaInfoHeader(
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
context = context,
|
||||
context = LocalContext.current,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
@ -159,7 +140,7 @@ fun MangaInfoHeader(
|
||||
coverDataProvider = coverDataProvider,
|
||||
onCoverClick = onCoverClick,
|
||||
title = title,
|
||||
context = context,
|
||||
context = LocalContext.current,
|
||||
doSearch = doSearch,
|
||||
author = author,
|
||||
artist = artist,
|
||||
@ -170,9 +151,22 @@ fun MangaInfoHeader(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons
|
||||
Row(modifier = Modifier.padding(start = 16.dp, top = 8.dp, end = 16.dp)) {
|
||||
@Composable
|
||||
fun MangaActionRow(
|
||||
modifier: Modifier = Modifier,
|
||||
favorite: Boolean,
|
||||
trackingCount: Int,
|
||||
onAddToLibraryClicked: () -> Unit,
|
||||
onWebViewClicked: (() -> Unit)?,
|
||||
onTrackingClicked: (() -> Unit)?,
|
||||
onEditCategory: (() -> 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) {
|
||||
@ -205,22 +199,33 @@ fun MangaInfoHeader(
|
||||
onClick = onWebViewClicked,
|
||||
)
|
||||
}
|
||||
// SY -->
|
||||
MangaActionButton(
|
||||
title = stringResource(R.string.merge),
|
||||
icon = Icons.Outlined.CallMerge,
|
||||
color = defaultActionButtonColor,
|
||||
onClick = onMergeClicked,
|
||||
)
|
||||
}
|
||||
|
||||
// SY --> Manga metadata
|
||||
mangaMetadataHeader?.invoke()
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
|
||||
// Expandable description-tags
|
||||
Column {
|
||||
@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) {
|
||||
val (expanded, onExpanded) = rememberSaveable {
|
||||
mutableStateOf(fromSource || windowWidthSizeClass != WindowWidthSizeClass.Compact)
|
||||
mutableStateOf(defaultExpandState)
|
||||
}
|
||||
val desc =
|
||||
description.takeIf { !it.isNullOrBlank() } ?: stringResource(id = R.string.description_placeholder)
|
||||
@ -250,6 +255,7 @@ fun MangaInfoHeader(
|
||||
.animateContentSize(),
|
||||
) {
|
||||
if (expanded) {
|
||||
// SY -->
|
||||
if (searchMetadataChips != null) {
|
||||
NamespaceTags(
|
||||
tags = searchMetadataChips,
|
||||
@ -257,6 +263,7 @@ fun MangaInfoHeader(
|
||||
onLongClick = { doSearch(it, true) },
|
||||
)
|
||||
} else {
|
||||
// SY <--
|
||||
FlowRow(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
mainAxisSpacing = 4.dp,
|
||||
@ -266,7 +273,9 @@ fun MangaInfoHeader(
|
||||
TagsChip(
|
||||
text = it,
|
||||
onClick = { onTagClicked(it) },
|
||||
// SY -->
|
||||
onLongClick = { doSearch(it, true) },
|
||||
// SY <--
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -280,22 +289,15 @@ fun MangaInfoHeader(
|
||||
TagsChip(
|
||||
text = it,
|
||||
onClick = { onTagClicked(it) },
|
||||
onLongClick = { doSearch(it, true) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY -->
|
||||
MangaInfoButtons(
|
||||
showRecommendsButton = !showRecommendsInOverflow,
|
||||
showMergeWithAnotherButton = showMergeWithAnother,
|
||||
onRecommendClicked = onRecommendClicked,
|
||||
onMergeWithAnotherClicked = onMergeWithAnotherClicked,
|
||||
)
|
||||
onLongClick = { doSearch(it, true) },
|
||||
// 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