diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f06b06..bdf8437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ All notable changes to this project are documented here. The format follows ### Fixed - With 3-button navigation, the navigation bar no longer covers the last home search results, and in landscape it no longer covers the home screen, the - task list or the home top bar's buttons (#152). + task list or the home top bar's buttons. The task details and editor no + longer leave an empty gap between the open keyboard and the text (#152). ## [1.3.1] - 2026-11-09 diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/SideInsets.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/SideInsets.kt new file mode 100644 index 0000000..153d8e8 --- /dev/null +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/SideInsets.kt @@ -0,0 +1,27 @@ +package de.jeanlucmakiola.agendula.ui.common + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.calculateEndPadding +import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.dp + +/** + * Pads by the start/end of a scaffold's [inner] padding and consumes it, so a + * side navigation bar or cutout (3-button nav in landscape) is cleared once. + */ +@Composable +fun Modifier.sideInsetsPadding(inner: PaddingValues): Modifier { + val layoutDirection = LocalLayoutDirection.current + val sides = PaddingValues( + start = inner.calculateStartPadding(layoutDirection), + top = 0.dp, + end = inner.calculateEndPadding(layoutDirection), + bottom = 0.dp, + ) + return this.padding(sides).consumeWindowInsets(sides) +} diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt index ca895bd..50312ab 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -268,6 +269,9 @@ private fun DetailBody( modifier = Modifier .fillMaxSize() .padding(inner) + // Consume the bar insets so imePadding adds only what the keyboard + // covers beyond them. + .consumeWindowInsets(inner) // Shrink the scroll viewport by the keyboard so the focused add-subtask // field scrolls into view above the IME instead of being hidden. .imePadding() diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt index 13991d4..4965e56 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt @@ -57,6 +57,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -219,7 +220,9 @@ fun TaskEditScreen( viewModel = viewModel, scroll = scroll, onTitleBottom = { titleBottom = it }, - modifier = Modifier.padding(inner), + // Consumed so EditContent's imePadding adds only what the keyboard + // covers beyond the bars. + modifier = Modifier.padding(inner).consumeWindowInsets(inner), ) } diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt index 79f39b5..9d5907c 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsScreen.kt @@ -31,14 +31,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides -import androidx.compose.foundation.layout.asPaddingValues -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding +import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.layout.union +import androidx.compose.foundation.layout.windowInsetsBottomHeight import androidx.compose.foundation.layout.windowInsetsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn @@ -84,7 +83,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -110,6 +108,7 @@ import de.jeanlucmakiola.agendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.agendula.ui.common.ShapedActionButton import de.jeanlucmakiola.agendula.ui.common.SyncPullBox import de.jeanlucmakiola.agendula.ui.common.priorityAccent +import de.jeanlucmakiola.agendula.ui.common.sideInsetsPadding import de.jeanlucmakiola.agendula.ui.tasklist.priorityLabel import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.time.formatDateTimeCompact @@ -163,7 +162,6 @@ fun ListsScreen( // System back closes search before leaving the screen. BackHandler(enabled = searchActive, onBack = closeSearch) - val layoutDirection = LocalLayoutDirection.current Scaffold( modifier = modifier, topBar = { @@ -192,12 +190,10 @@ fun ListsScreen( available = syncAvailable && !searchActive, syncing = syncing, onSync = viewModel::syncAll, - // Side insets too: in landscape a 3-button navigation bar sits at the side. - modifier = Modifier.fillMaxSize().padding( - start = inner.calculateStartPadding(layoutDirection), - top = inner.calculateTopPadding(), - end = inner.calculateEndPadding(layoutDirection), - ), + modifier = Modifier + .fillMaxSize() + .padding(top = inner.calculateTopPadding()) + .sideInsetsPadding(inner), ) { when (val s = state) { ListsUiState.Loading -> Unit // brief; avoids a flash before first emission @@ -530,7 +526,10 @@ private fun HomeTopBar( Row( modifier = Modifier .fillMaxWidth() - .windowInsetsPadding(WindowInsets.systemBars.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)) + .windowInsetsPadding( + WindowInsets.systemBars.union(WindowInsets.displayCutout) + .only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal), + ) .height(72.dp) .padding(horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically, @@ -690,15 +689,13 @@ private fun SearchResults( } } else { // The last result scrolls clear of the navigation bar and the keyboard. - LazyColumn( - modifier = Modifier.fillMaxSize(), - contentPadding = WindowInsets.navigationBars.union(WindowInsets.ime) - .only(WindowInsetsSides.Bottom) - .asPaddingValues(), - ) { + LazyColumn(modifier = Modifier.fillMaxSize()) { items(results, key = { it.occurrenceKey }) { task -> UpcomingRow(task = task, onClick = { onOpenTask(task) }) } + item(key = "bottom-inset") { + Spacer(Modifier.windowInsetsBottomHeight(WindowInsets.navigationBars.union(WindowInsets.ime))) + } } } } diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt index 936bfc1..15491f4 100644 --- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt @@ -22,8 +22,6 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.foundation.layout.calculateEndPadding -import androidx.compose.foundation.layout.calculateStartPadding import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.isImeVisible import androidx.compose.foundation.layout.navigationBars @@ -92,7 +90,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor @@ -118,6 +115,7 @@ import de.jeanlucmakiola.agendula.ui.common.LocalUse24HourFormat import de.jeanlucmakiola.agendula.ui.common.SyncPullBox import de.jeanlucmakiola.agendula.ui.common.ignoringEdgeSwipes import de.jeanlucmakiola.agendula.ui.common.priorityAccent +import de.jeanlucmakiola.agendula.ui.common.sideInsetsPadding import de.jeanlucmakiola.agendula.ui.lists.ListEditorSheet import de.jeanlucmakiola.agendula.ui.lists.listWriteFailureMessage import de.jeanlucmakiola.agendula.ui.lists.ListWriteFailure @@ -253,14 +251,7 @@ fun TaskListScreen( } }, ) { inner -> - // Side insets too: in landscape a 3-button navigation bar sits at the side. - val layoutDirection = LocalLayoutDirection.current - Box( - modifier = Modifier.fillMaxSize().padding( - start = inner.calculateStartPadding(layoutDirection), - end = inner.calculateEndPadding(layoutDirection), - ), - ) { + Box(modifier = Modifier.fillMaxSize().sideInsetsPadding(inner)) { SyncPullBox( available = syncAvailable, syncing = syncing, @@ -270,7 +261,10 @@ fun TaskListScreen( ) { when (val s = state) { TaskListUiState.Loading -> Unit // brief; avoids a flash before first emission - TaskListUiState.Failure -> CenteredMessage(stringResource(R.string.task_list_failure), inner) + TaskListUiState.Failure -> CenteredMessage( + stringResource(R.string.task_list_failure), + PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding()), + ) is TaskListUiState.Content -> TaskListBody( s, filter, inner, onOpenTask, onRequestDelete, viewModel, onClearCompleted = if (listId != null && list?.isReadOnly != true) ({ confirmClear = true }) else null,