From b648afb889a940b469b90fe184fc5d61b669b117 Mon Sep 17 00:00:00 2001 From: Cuong-Tran <16017808+cuong-tran@users.noreply.github.com> Date: Tue, 9 Dec 2025 13:19:54 +0700 Subject: [PATCH] fix(sticky-header): Fix sticky-header and scrolling issues in source screens (#1315) * Fix padding in MigrateSourceScreen * Fix scrolling bar glitch due to sticky headers in RelatedMangasList * Dynamically measure source search's height * sticky header in source screen * fix(sticky-header): update sticky header keys for improved uniqueness * Fix padding on related-manga-grid * Avoid mixing dp and px units when initializing searchBoxHeight * extract `hasKeyword` * fillMaxWidth --- .../browse/MigrateSourceScreen.kt | 15 ++- .../browse/SourcesFilterScreen.kt | 2 +- .../presentation/browse/SourcesScreen.kt | 83 +++++++----- .../RelatedMangasComfortableGrid.kt | 121 ++++++++++------- .../components/RelatedMangasCompactGrid.kt | 119 ++++++++++------- .../browse/components/RelatedMangasList.kt | 122 ++++++++++-------- .../components/SourceSearchBox.kt | 8 ++ .../advanced/design/PreMigrationScreen.kt | 16 ++- .../source/browse/SourceFilterDialog.kt | 5 +- .../tachiyomi/ui/manga/PaletteScreen.kt | 5 +- 10 files changed, 296 insertions(+), 200 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt index 6418c3878..521347b3f 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSourceScreen.kt @@ -30,13 +30,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.dp import eu.kanade.domain.source.interactor.SetMigrateSorting import eu.kanade.domain.source.model.installedExtension import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.browse.components.SourceIcon import eu.kanade.presentation.components.AnimatedFloatingSearchBox +import eu.kanade.presentation.components.SOURCE_SEARCH_BOX_HEIGHT import eu.kanade.presentation.util.animateItemFastScroll import eu.kanade.tachiyomi.ui.browse.migration.sources.MigrateSourceScreenModel import eu.kanade.tachiyomi.util.system.copyToClipboard @@ -180,9 +181,12 @@ private fun MigrateSourceList( // KMK --> Box { + val density = LocalDensity.current + var searchBoxHeight by remember { mutableStateOf(SOURCE_SEARCH_BOX_HEIGHT) } + FastScrollLazyColumn( state = lazyListState, - contentPadding = PaddingValues(top = 65.dp), + contentPadding = PaddingValues(top = searchBoxHeight), // KMK <-- ) { items( @@ -201,7 +205,8 @@ private fun MigrateSourceList( MigrateSourceItem( // KMK --> // modifier = Modifier.animateItem(), - modifier = Modifier.animateItemFastScroll(), + modifier = Modifier.animateItemFastScroll() + .padding(end = MaterialTheme.padding.small), // KMK <-- source = source, count = count, @@ -221,9 +226,11 @@ private fun MigrateSourceList( .background(MaterialTheme.colorScheme.background) .padding( horizontal = MaterialTheme.padding.medium, - vertical = MaterialTheme.padding.small, ) .align(Alignment.TopCenter), + onGloballyPositioned = { layoutCoordinates -> + searchBoxHeight = with(density) { layoutCoordinates.size.height.toDp() } + }, ) // KMK <-- } diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt index 98ce2baa0..ded50d911 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesFilterScreen.kt @@ -86,7 +86,7 @@ private fun SourcesFilterContent( // KMK --> stickyHeader( // KMK <-- - key = language, + key = "$STICKY_HEADER_KEY_PREFIX-$language", contentType = "source-filter-header", ) { SourcesFilterHeader( diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt index 515be57b4..1b17f6385 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourcesScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll @@ -23,15 +22,21 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.times import eu.kanade.domain.source.model.installedExtension import eu.kanade.presentation.browse.components.BaseSourceItem import eu.kanade.presentation.components.AnimatedFloatingSearchBox +import eu.kanade.presentation.components.SOURCE_SEARCH_BOX_HEIGHT import eu.kanade.presentation.util.animateItemFastScroll import eu.kanade.tachiyomi.ui.browse.source.SourcesScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing @@ -46,6 +51,7 @@ import tachiyomi.i18n.kmk.KMR import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.FastScrollLazyColumn import tachiyomi.presentation.core.components.LabeledCheckbox +import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX import tachiyomi.presentation.core.components.material.SECONDARY_ALPHA import tachiyomi.presentation.core.components.material.padding import tachiyomi.presentation.core.i18n.stringResource @@ -88,47 +94,51 @@ fun SourcesScreen( else -> Box( modifier = Modifier.padding(contentPadding), ) { + val density = LocalDensity.current + var searchBoxHeight by remember { mutableStateOf(SOURCE_SEARCH_BOX_HEIGHT) } + FastScrollLazyColumn( state = lazyListState, - contentPadding = PaddingValues(top = 65.dp), + contentPadding = PaddingValues(top = searchBoxHeight), // KMK <-- ) { - items( - items = state.items, - contentType = { - when (it) { - is SourceUiModel.Header -> "header" - is SourceUiModel.Item -> "item" - } - }, - key = { - when (it) { - is SourceUiModel.Header -> "header-${it.hashCode()}" - is SourceUiModel.Item -> "source-${it.source.key()}" - } - }, - ) { model -> + state.items.forEach { model -> when (model) { is SourceUiModel.Header -> { - SourceHeader( - modifier = Modifier.animateItemFastScroll(), - language = model.language, - // SY --> - isCategory = model.isCategory, - // SY <-- - ) + stickyHeader( + key = "$STICKY_HEADER_KEY_PREFIX-header-${model.hashCode()}", + contentType = "header", + ) { + SourceHeader( + modifier = Modifier + .animateItemFastScroll() + .background(MaterialTheme.colorScheme.background) + .fillMaxWidth(), + language = model.language, + // SY --> + isCategory = model.isCategory, + // SY <-- + ) + } + } + is SourceUiModel.Item -> { + item( + key = "source-${model.source.key()}", + contentType = "item", + ) { + SourceItem( + modifier = Modifier.animateItemFastScroll(), + source = model.source, + // SY --> + showLatest = state.showLatest, + showPin = state.showPin, + // SY <-- + onClickItem = onClickItem, + onLongClickItem = onLongClickItem, + onClickPin = onClickPin, + ) + } } - is SourceUiModel.Item -> SourceItem( - modifier = Modifier.animateItemFastScroll(), - source = model.source, - // SY --> - showLatest = state.showLatest, - showPin = state.showPin, - // SY <-- - onClickItem = onClickItem, - onLongClickItem = onLongClickItem, - onClickPin = onClickPin, - ) } } } @@ -146,6 +156,9 @@ fun SourcesScreen( vertical = MaterialTheme.padding.small, ) .align(Alignment.TopCenter), + onGloballyPositioned = { layoutCoordinates -> + searchBoxHeight = with(density) { layoutCoordinates.size.height.toDp() + 2 * MaterialTheme.padding.small } + }, ) // KMK <-- } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt index 4b910b642..9674edb80 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt @@ -2,7 +2,10 @@ package eu.kanade.presentation.browse.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -39,59 +42,81 @@ fun RelatedMangasComfortableGrid( ) { FastScrollLazyVerticalGrid( columns = columns, - contentPadding = contentPadding + PaddingValues(horizontal = MaterialTheme.padding.small), - // padding for scrollbar + contentPadding = contentPadding + PaddingValues(horizontal = MaterialTheme.padding.small), // padding for scrollbar topContentPadding = contentPadding.calculateTopPadding(), verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer), horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer), ) { - relatedMangas.forEach { related -> - val isLoading = related is RelatedManga.Loading - if (isLoading) { - header(key = "${related.hashCode()}#header") { - RelatedMangaTitle( - title = stringResource(MR.strings.loading), - subtitle = null, - onClick = {}, - onLongClick = null, - modifier = Modifier.background(MaterialTheme.colorScheme.background), - ) + relatedMangas.forEach { relatedManga -> + when (relatedManga) { + is RelatedManga.Loading -> { + header(key = "${relatedManga.hashCode()}#header") { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background), + ) { + HorizontalDivider() + RelatedMangaTitle( + title = stringResource(MR.strings.loading), + subtitle = null, + onClick = {}, + onLongClick = null, + modifier = Modifier + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.small, + ), + ) + } + } + header(key = "${relatedManga.hashCode()}#loading") { RelatedMangasLoadingItem() } } - header(key = "${related.hashCode()}#content") { RelatedMangasLoadingItem() } - } else { - val relatedManga = related as RelatedManga.Success - header(key = "${related.hashCode()}#divider") { HorizontalDivider() } - header(key = "${related.hashCode()}#header") { - RelatedMangaTitle( - title = if (relatedManga.keyword.isNotBlank()) { - stringResource(KMR.strings.related_mangas_more) - } else { - stringResource(KMR.strings.related_mangas_website_suggestions) - }, - showArrow = relatedManga.keyword.isNotBlank(), - subtitle = null, - onClick = { - if (relatedManga.keyword.isNotBlank()) onKeywordClick(relatedManga.keyword) - }, - onLongClick = { - if (relatedManga.keyword.isNotBlank()) onKeywordLongClick(relatedManga.keyword) - }, - modifier = Modifier.background(MaterialTheme.colorScheme.background), - ) - } - items( - key = { "related-comfort-${relatedManga.mangaList[it].id}" }, - count = relatedManga.mangaList.size, - ) { index -> - val manga by getManga(relatedManga.mangaList[index]) - BrowseSourceComfortableGridItem( - manga = manga, - onClick = { onMangaClick(manga) }, - onLongClick = { onMangaLongClick(manga) }, - isSelected = selection.fastAny { selected -> selected.id == manga.id }, - metadata = null, - usePanoramaCover = usePanoramaCover, - ) + is RelatedManga.Success -> { + val hasKeyword = relatedManga.keyword.isNotBlank() + header(key = "${relatedManga.hashCode()}#header") { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background), + ) { + HorizontalDivider() + RelatedMangaTitle( + title = if (hasKeyword) { + stringResource(KMR.strings.related_mangas_more) + } else { + stringResource(KMR.strings.related_mangas_website_suggestions) + }, + showArrow = hasKeyword, + subtitle = null, + onClick = { + if (hasKeyword) onKeywordClick(relatedManga.keyword) + }, + onLongClick = { + if (hasKeyword) onKeywordLongClick(relatedManga.keyword) + }, + modifier = Modifier + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.small, + ), + ) + } + } + items( + key = { "related-comfort-${relatedManga.mangaList[it].id}" }, + count = relatedManga.mangaList.size, + ) { index -> + val manga by getManga(relatedManga.mangaList[index]) + BrowseSourceComfortableGridItem( + manga = manga, + onClick = { onMangaClick(manga) }, + onLongClick = { onMangaLongClick(manga) }, + isSelected = selection.fastAny { selected -> selected.id == manga.id }, + metadata = null, + usePanoramaCover = usePanoramaCover, + ) + } } } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt index 054f556b9..5a6de2602 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt @@ -2,7 +2,10 @@ package eu.kanade.presentation.browse.components import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -38,58 +41,80 @@ fun RelatedMangasCompactGrid( ) { FastScrollLazyVerticalGrid( columns = columns, - contentPadding = contentPadding + PaddingValues(horizontal = MaterialTheme.padding.small), - // padding for scrollbar + contentPadding = contentPadding + PaddingValues(horizontal = MaterialTheme.padding.small), // padding for scrollbar topContentPadding = contentPadding.calculateTopPadding(), verticalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridVerticalSpacer), horizontalArrangement = Arrangement.spacedBy(CommonMangaItemDefaults.GridHorizontalSpacer), ) { - relatedMangas.forEach { related -> - val isLoading = related is RelatedManga.Loading - if (isLoading) { - header(key = "${related.hashCode()}#header") { - RelatedMangaTitle( - title = stringResource(MR.strings.loading), - subtitle = null, - onClick = {}, - onLongClick = null, - modifier = Modifier.background(MaterialTheme.colorScheme.background), - ) + relatedMangas.forEach { relatedManga -> + when (relatedManga) { + is RelatedManga.Loading -> { + header(key = "${relatedManga.hashCode()}#header") { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background), + ) { + HorizontalDivider() + RelatedMangaTitle( + title = stringResource(MR.strings.loading), + subtitle = null, + onClick = {}, + onLongClick = null, + modifier = Modifier + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.small, + ), + ) + } + } + header(key = "${relatedManga.hashCode()}#loading") { RelatedMangasLoadingItem() } } - header(key = "${related.hashCode()}#content") { RelatedMangasLoadingItem() } - } else { - val relatedManga = related as RelatedManga.Success - header(key = "${related.hashCode()}#divider") { HorizontalDivider() } - header(key = "${related.hashCode()}#header") { - RelatedMangaTitle( - title = if (relatedManga.keyword.isNotBlank()) { - stringResource(KMR.strings.related_mangas_more) - } else { - stringResource(KMR.strings.related_mangas_website_suggestions) - }, - showArrow = relatedManga.keyword.isNotBlank(), - subtitle = null, - onClick = { - if (relatedManga.keyword.isNotBlank()) onKeywordClick(relatedManga.keyword) - }, - onLongClick = { - if (relatedManga.keyword.isNotBlank()) onKeywordLongClick(relatedManga.keyword) - }, - modifier = Modifier.background(MaterialTheme.colorScheme.background), - ) - } - items( - key = { "related-compact-${relatedManga.mangaList[it].id}" }, - count = relatedManga.mangaList.size, - ) { index -> - val manga by getManga(relatedManga.mangaList[index]) - BrowseSourceCompactGridItem( - manga = manga, - onClick = { onMangaClick(manga) }, - onLongClick = { onMangaLongClick(manga) }, - isSelected = selection.fastAny { selected -> selected.id == manga.id }, - metadata = null, - ) + is RelatedManga.Success -> { + val hasKeyword = relatedManga.keyword.isNotBlank() + header(key = "${relatedManga.hashCode()}#header") { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background), + ) { + HorizontalDivider() + RelatedMangaTitle( + title = if (hasKeyword) { + stringResource(KMR.strings.related_mangas_more) + } else { + stringResource(KMR.strings.related_mangas_website_suggestions) + }, + showArrow = hasKeyword, + subtitle = null, + onClick = { + if (hasKeyword) onKeywordClick(relatedManga.keyword) + }, + onLongClick = { + if (hasKeyword) onKeywordLongClick(relatedManga.keyword) + }, + modifier = Modifier + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.small, + ), + ) + } + } + items( + key = { "related-compact-${relatedManga.mangaList[it].id}" }, + count = relatedManga.mangaList.size, + ) { index -> + val manga by getManga(relatedManga.mangaList[index]) + BrowseSourceCompactGridItem( + manga = manga, + onClick = { onMangaClick(manga) }, + onLongClick = { onMangaLongClick(manga) }, + isSelected = selection.fastAny { selected -> selected.id == manga.id }, + metadata = null, + ) + } } } } diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt index a7b639e3e..044c2a56c 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt @@ -1,7 +1,9 @@ package eu.kanade.presentation.browse.components import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -36,63 +38,75 @@ fun RelatedMangasList( // Using modifier instead of contentPadding so we can use stickyHeader modifier = Modifier.padding(contentPadding), ) { - relatedMangas.forEach { related -> - val isLoading = related is RelatedManga.Loading - if (isLoading) { - item(key = "${related.hashCode()}#divider") { HorizontalDivider() } - stickyHeader(key = "$STICKY_HEADER_KEY_PREFIX${related.hashCode()}#header") { - RelatedMangaTitle( - title = stringResource(MR.strings.loading), - subtitle = null, - onClick = {}, - onLongClick = null, - modifier = Modifier - .padding( - start = MaterialTheme.padding.small, - end = MaterialTheme.padding.medium, + relatedMangas.forEach { relatedManga -> + when (relatedManga) { + is RelatedManga.Loading -> { + stickyHeader(key = "$STICKY_HEADER_KEY_PREFIX-${relatedManga.hashCode()}#header") { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background), + ) { + HorizontalDivider() + RelatedMangaTitle( + title = stringResource(MR.strings.loading), + subtitle = null, + onClick = {}, + onLongClick = null, + modifier = Modifier + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.small, + ), ) - .background(MaterialTheme.colorScheme.background), - ) + } + } + item(key = "${relatedManga.hashCode()}#loading") { RelatedMangasLoadingItem() } } - item(key = "${related.hashCode()}#content") { RelatedMangasLoadingItem() } - } else { - val relatedManga = related as RelatedManga.Success - item(key = "${related.hashCode()}#divider") { HorizontalDivider() } - stickyHeader(key = "$STICKY_HEADER_KEY_PREFIX${related.hashCode()}#header") { - RelatedMangaTitle( - title = if (relatedManga.keyword.isNotBlank()) { - stringResource(KMR.strings.related_mangas_more) - } else { - stringResource(KMR.strings.related_mangas_website_suggestions) - }, - showArrow = relatedManga.keyword.isNotBlank(), - subtitle = null, - onClick = { - if (relatedManga.keyword.isNotBlank()) onKeywordClick(relatedManga.keyword) - }, - onLongClick = { - if (relatedManga.keyword.isNotBlank()) onKeywordLongClick(relatedManga.keyword) - }, - modifier = Modifier - .padding( - start = MaterialTheme.padding.small, - end = MaterialTheme.padding.medium, + is RelatedManga.Success -> { + val hasKeyword = relatedManga.keyword.isNotBlank() + stickyHeader(key = "$STICKY_HEADER_KEY_PREFIX-${relatedManga.hashCode()}#header") { + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background), + ) { + HorizontalDivider() + RelatedMangaTitle( + title = if (hasKeyword) { + stringResource(KMR.strings.related_mangas_more) + } else { + stringResource(KMR.strings.related_mangas_website_suggestions) + }, + showArrow = hasKeyword, + subtitle = null, + onClick = { + if (hasKeyword) onKeywordClick(relatedManga.keyword) + }, + onLongClick = { + if (hasKeyword) onKeywordLongClick(relatedManga.keyword) + }, + modifier = Modifier + .padding( + start = MaterialTheme.padding.small, + end = MaterialTheme.padding.small, + ), ) - .background(MaterialTheme.colorScheme.background), - ) - } - items( - key = { "related-list-${relatedManga.mangaList[it].id}" }, - count = relatedManga.mangaList.size, - ) { index -> - val manga by getManga(relatedManga.mangaList[index]) - BrowseSourceListItem( - manga = manga, - onClick = { onMangaClick(manga) }, - onLongClick = { onMangaLongClick(manga) }, - isSelected = selection.fastAny { selected -> selected.id == manga.id }, - metadata = null, - ) + } + } + items( + key = { "related-list-${relatedManga.mangaList[it].id}" }, + count = relatedManga.mangaList.size, + ) { index -> + val manga by getManga(relatedManga.mangaList[index]) + BrowseSourceListItem( + manga = manga, + onClick = { onMangaClick(manga) }, + onLongClick = { onMangaLongClick(manga) }, + isSelected = selection.fastAny { selected -> selected.id == manga.id }, + metadata = null, + ) + } } } } diff --git a/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt b/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt index bc4852b6b..f531004df 100644 --- a/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt +++ b/app/src/main/java/eu/kanade/presentation/components/SourceSearchBox.kt @@ -32,6 +32,8 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.layout.LayoutCoordinates +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.SoftwareKeyboardController @@ -54,6 +56,7 @@ fun AnimatedFloatingSearchBox( searchQuery: String?, onChangeSearchQuery: (String?) -> Unit, modifier: Modifier = Modifier, + onGloballyPositioned: (LayoutCoordinates) -> Unit = { }, placeholderText: String? = null, focusManager: FocusManager = LocalFocusManager.current, focusRequester: FocusRequester = remember { FocusRequester() }, @@ -68,6 +71,7 @@ fun AnimatedFloatingSearchBox( SourcesSearchBox( searchQuery = searchQuery, onChangeSearchQuery = onChangeSearchQuery, + onGloballyPositioned = onGloballyPositioned, placeholderText = placeholderText, focusManager = focusManager, focusRequester = focusRequester, @@ -81,6 +85,7 @@ fun SourcesSearchBox( searchQuery: String?, onChangeSearchQuery: (String?) -> Unit, modifier: Modifier = Modifier, + onGloballyPositioned: (LayoutCoordinates) -> Unit = { }, placeholderText: String? = null, focusManager: FocusManager = LocalFocusManager.current, focusRequester: FocusRequester = remember { FocusRequester() }, @@ -108,6 +113,7 @@ fun SourcesSearchBox( value = searchQuery ?: "", onValueChange = onChangeSearchQuery, modifier = modifier + .onGloballyPositioned(onGloballyPositioned) .fillMaxWidth() .focusRequester(focusRequester) .onFocusChanged { isFocused = it.isFocused } @@ -207,6 +213,8 @@ fun SearchBoxTrailingIcon( } } +internal val SOURCE_SEARCH_BOX_HEIGHT = 65.dp + @Preview @Composable fun PreviewClearFocusOnKeyboardDismissExample() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt index 4571bb2fb..a3332d0ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/design/PreMigrationScreen.kt @@ -57,7 +57,6 @@ import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalLayoutDirection @@ -74,6 +73,7 @@ import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import eu.kanade.presentation.components.AppBar +import eu.kanade.presentation.components.SOURCE_SEARCH_BOX_HEIGHT import eu.kanade.presentation.components.SourcesSearchBox import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.databinding.PreMigrationListBinding @@ -194,10 +194,10 @@ class PreMigrationScreen(val migration: MigrationType) : Screen() { }, ) { contentPadding -> // KMK --> + val density = LocalDensity.current Box(modifier = Modifier.padding(top = contentPadding.calculateTopPadding())) { - var searchBoxHeight by remember { mutableIntStateOf(40) } + var searchBoxHeight by remember { mutableIntStateOf(with(density) { SOURCE_SEARCH_BOX_HEIGHT.roundToPx() }) } // KMK <-- - val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current val left = with(density) { contentPadding.calculateLeftPadding(layoutDirection).toPx().roundToInt() } // KMK --> @@ -234,12 +234,14 @@ class PreMigrationScreen(val migration: MigrationType) : Screen() { } // KMK --> SourcesSearchBox( - modifier = Modifier - .onSizeChanged { searchBoxHeight = it.height } - .background(MaterialTheme.colorScheme.background) - .padding(horizontal = MaterialTheme.padding.small), searchQuery = searchQuery, onChangeSearchQuery = { searchQuery = it ?: "" }, + modifier = Modifier + .background(MaterialTheme.colorScheme.background) + .padding(horizontal = MaterialTheme.padding.small), + onGloballyPositioned = { layoutCoordinates -> + searchBoxHeight = layoutCoordinates.size.height + }, placeholderText = stringResource(KMR.strings.action_search_for_source), ) // KMK <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt index d7c24dde4..3015c1590 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceFilterDialog.kt @@ -30,6 +30,7 @@ import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CollapsibleBox import tachiyomi.presentation.core.components.HeadingItem +import tachiyomi.presentation.core.components.Scroller.STICKY_HEADER_KEY_PREFIX import tachiyomi.presentation.core.components.SelectItem import tachiyomi.presentation.core.components.SortItem import tachiyomi.presentation.core.components.TextItem @@ -62,7 +63,9 @@ fun SourceFilterDialog( AdaptiveSheet(onDismissRequest = onDismissRequest) { LazyColumn { - stickyHeader { + stickyHeader( + key = "$STICKY_HEADER_KEY_PREFIX-title", + ) { Row( modifier = Modifier .background(MaterialTheme.colorScheme.background) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/PaletteScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/PaletteScreen.kt index 494fc6f97..b9e8772b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/PaletteScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/PaletteScreen.kt @@ -28,7 +28,7 @@ import uy.kohesive.injekt.api.get * A screen that displays a colors palette of current theme. */ class PaletteScreen( - @ColorInt private val seedColor: Int?, + @param:ColorInt private val seedColor: Int?, ) : Screen() { @Composable @@ -59,8 +59,7 @@ class PaletteScreen( }, ) { contentPadding -> ScrollbarLazyColumn( - // Using modifier instead of contentPadding so we can use stickyHeader - modifier = Modifier.padding(contentPadding), + contentPadding = contentPadding, ) { item { ButtonsColor(