From 47d7e01f7ffb4879d0765a41b0696dfed1b6e266 Mon Sep 17 00:00:00 2001 From: jonasgaudian <43753916+jonasgaudian@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:56:49 +0100 Subject: [PATCH] implement show/hide header on scroll in `LibraryScreen` and prevent haptic feedback on re-selecting the current bottom bar item --- .../view/composable/BottomNavigationBar.kt | 6 +- .../view/library/LibraryComponents.kt | 3 + .../translator/view/library/LibraryScreen.kt | 116 ++++++++++++------ .../view/vocabulary/NewVocListScreen.kt | 5 +- 4 files changed, 87 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt b/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt index f13b3c7..6ab222f 100644 --- a/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt +++ b/app/src/main/java/eu/gaudian/translator/view/composable/BottomNavigationBar.kt @@ -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) { { diff --git a/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt b/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt index 7dbf560..c727ae5 100644 --- a/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt +++ b/app/src/main/java/eu/gaudian/translator/view/library/LibraryComponents.kt @@ -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) diff --git a/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt b/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt index 9c5520e..8c85e87 100644 --- a/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/library/LibraryScreen.kt @@ -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())) { diff --git a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewVocListScreen.kt b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewVocListScreen.kt index 5d6c373..2cce7fd 100644 --- a/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewVocListScreen.kt +++ b/app/src/main/java/eu/gaudian/translator/view/vocabulary/NewVocListScreen.kt @@ -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, 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)