implement show/hide header on scroll in LibraryScreen and prevent haptic feedback on re-selecting the current bottom bar item

This commit is contained in:
jonasgaudian
2026-02-16 17:56:49 +01:00
parent eae37715cd
commit 47d7e01f7f
4 changed files with 87 additions and 43 deletions

View File

@@ -207,8 +207,10 @@ fun BottomNavigationBar(
NavigationBarItem(
selected = isSelected,
onClick = {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
if (screen == Screen.More) showMoreMenu = true else onItemSelected(screen)
if (!isSelected) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
if (screen == Screen.More) showMoreMenu = true else onItemSelected(screen)
}
},
label = if (showLabels) {
{

View File

@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
@@ -315,6 +316,7 @@ fun AllCardsView(
onItemClick: (VocabularyItem) -> Unit,
onItemLongClick: (VocabularyItem) -> Unit,
onDeleteClick: (VocabularyItem) -> Unit,
listState: LazyListState,
modifier: Modifier = Modifier
) {
if (vocabularyItems.isEmpty()) {
@@ -340,6 +342,7 @@ fun AllCardsView(
}
} else {
LazyColumn(
state = listState,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 100.dp)

View File

@@ -4,6 +4,10 @@ package eu.gaudian.translator.view.library
import android.os.Parcelable
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -42,6 +46,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -122,6 +127,10 @@ fun LibraryScreen(
val vocabularyItems by vocabularyItemsFlow.collectAsStateWithLifecycle(initialValue = emptyList())
var isHeaderVisible by remember { mutableStateOf(true) }
var previousIndex by remember { mutableStateOf(0) }
var previousScrollOffset by remember { mutableStateOf(0) }
// Set navigation context when vocabulary items are loaded
LaunchedEffect(vocabularyItems) {
if (vocabularyItems.isNotEmpty()) {
@@ -129,6 +138,24 @@ fun LibraryScreen(
}
}
LaunchedEffect(isCategoriesView, isInSelectionMode) {
if (isCategoriesView || isInSelectionMode) {
isHeaderVisible = true
}
}
LaunchedEffect(lazyListState, isCategoriesView, isInSelectionMode) {
if (isCategoriesView || isInSelectionMode) return@LaunchedEffect
snapshotFlow { lazyListState.firstVisibleItemIndex to lazyListState.firstVisibleItemScrollOffset }
.collect { (index, offset) ->
val isScrollingDown = index > previousIndex || (index == previousIndex && offset > previousScrollOffset)
val isAtTop = index == 0 && offset <= 4
isHeaderVisible = if (isAtTop) true else !isScrollingDown
previousIndex = index
previousScrollOffset = offset
}
}
Box(
modifier = modifier.fillMaxSize(),
contentAlignment = Alignment.TopCenter
@@ -139,47 +166,55 @@ fun LibraryScreen(
.fillMaxSize()
.padding(horizontal = 24.dp),
) {
Spacer(modifier = Modifier.height(24.dp))
if (isInSelectionMode) {
SelectionTopBar(
selectionCount = selection.size,
onCloseClick = { selection = emptySet() },
onSelectAllClick = {
selection = if (selection.size == vocabularyItems.size) emptySet()
else vocabularyItems.map { it.id.toLong() }.toSet()
},
onDeleteClick = {
vocabularyViewModel.deleteVocabularyItemsById(selection.map { it.toInt() })
selection = emptySet()
},
onMoveToCategoryClick = { showCategoryDialog = true },
onMoveToStageClick = { showStageDialog = true },
isRemoveEnabled = false,
onRemoveFromCategoryClick = {}
)
} else {
LibraryTopBar(
onAddClick = { /* TODO: Add new card/category */ }
)
AnimatedVisibility(
visible = isHeaderVisible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically()
) {
Column {
Spacer(modifier = Modifier.height(24.dp))
if (isInSelectionMode) {
SelectionTopBar(
selectionCount = selection.size,
onCloseClick = { selection = emptySet() },
onSelectAllClick = {
selection = if (selection.size == vocabularyItems.size) emptySet()
else vocabularyItems.map { it.id.toLong() }.toSet()
},
onDeleteClick = {
vocabularyViewModel.deleteVocabularyItemsById(selection.map { it.toInt() })
selection = emptySet()
},
onMoveToCategoryClick = { showCategoryDialog = true },
onMoveToStageClick = { showStageDialog = true },
isRemoveEnabled = false,
onRemoveFromCategoryClick = {}
)
} else {
LibraryTopBar(
onAddClick = { /* TODO: Add new card/category */ }
)
}
Spacer(modifier = Modifier.height(24.dp))
SearchBar(
searchQuery = filterState.searchQuery,
onQueryChanged = { filterState = filterState.copy(searchQuery = it) },
onFilterClick = { showFilterSheet = true }
)
Spacer(modifier = Modifier.height(24.dp))
SegmentedControl(
isCategoriesView = isCategoriesView,
onTabSelected = { isCategoriesView = it }
)
Spacer(modifier = Modifier.height(24.dp))
}
}
Spacer(modifier = Modifier.height(24.dp))
SearchBar(
searchQuery = filterState.searchQuery,
onQueryChanged = { filterState = filterState.copy(searchQuery = it) },
onFilterClick = { showFilterSheet = true }
)
Spacer(modifier = Modifier.height(24.dp))
SegmentedControl(
isCategoriesView = isCategoriesView,
onTabSelected = { isCategoriesView = it }
)
Spacer(modifier = Modifier.height(24.dp))
LibraryViewContainer(
isCategoriesView = isCategoriesView,
@@ -199,6 +234,7 @@ fun LibraryScreen(
vocabularyItems = vocabularyItems,
allLanguages = allLanguages,
selection = selection,
listState = lazyListState,
onItemClick = { item ->
if (isInSelectionMode) {
selection = if (selection.contains(item.id.toLong())) {

View File

@@ -268,6 +268,7 @@ fun NewVocListScreen(
vocabularyItems = vocabularyItems,
allLanguages = allLanguages,
selection = selection,
listState = lazyListState,
onItemClick = { item ->
if (isInSelectionMode) {
selection = if (selection.contains(item.id.toLong())) {
@@ -588,7 +589,8 @@ fun AllCardsView(
selection: Set<Long>,
onItemClick: (VocabularyItem) -> Unit,
onItemLongClick: (VocabularyItem) -> Unit,
onDeleteClick: (VocabularyItem) -> Unit
onDeleteClick: (VocabularyItem) -> Unit,
listState: androidx.compose.foundation.lazy.LazyListState
) {
if (vocabularyItems.isEmpty()) {
Column(
@@ -613,6 +615,7 @@ fun AllCardsView(
}
} else {
LazyColumn(
state = listState,
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 100.dp)