fix(edit): pin the picker title instead of a large collapsing header

Full-screen selection pickers opened with the LargeTopAppBar expanded, so the
title started below the bar and only settled into it after a scroll — a tall
empty header above a short option list. Give CollapsingScaffold a largeTopBar
flag and have FullScreenPicker use a pinned single-line TopAppBar, so every
picker (calendar, theme, week start, language, reminder, agenda range) shows
its title in the bar from the start. Settings and the calendar manager keep
the large collapsing header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 15:22:18 +02:00
parent 0718afd5a6
commit 0a89ff9d0e
2 changed files with 52 additions and 23 deletions

View File

@@ -28,6 +28,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -57,10 +58,16 @@ fun positionOf(index: Int, count: Int): Position = when {
} }
/** /**
* The app's standard full-screen list scaffold: a collapsing [LargeTopAppBar] * The app's standard full-screen list scaffold. By default a collapsing
* whose title shrinks into the bar (next to the back button) as the content * [LargeTopAppBar] whose title shrinks into the bar (next to the back button) as
* scrolls. Content is a scrollable column that feeds the toolbar via nested * the content scrolls — used by Settings and the calendar manager, where the
* scroll. Used by Settings and the calendar manager so they share one shell. * large header sets the page. Content is a scrollable column that feeds the
* toolbar via nested scroll.
*
* Set [largeTopBar] to false for a pinned, single-line [TopAppBar] instead: the
* title sits in the bar from the start, with no expanded header to scroll past.
* Preferred for selection pickers, where the tall header is just wasted space
* above the options.
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -68,12 +75,16 @@ fun CollapsingScaffold(
title: String, title: String,
onBack: () -> Unit, onBack: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
largeTopBar: Boolean = true,
snackbarHost: @Composable () -> Unit = {}, snackbarHost: @Composable () -> Unit = {},
actions: @Composable RowScope.() -> Unit = {}, actions: @Composable RowScope.() -> Unit = {},
content: @Composable ColumnScope.() -> Unit, content: @Composable ColumnScope.() -> Unit,
) { ) {
val scrollBehavior = val scrollBehavior = if (largeTopBar) {
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
} else {
TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
}
Scaffold( Scaffold(
modifier = modifier modifier = modifier
.predictiveBack(onBack = onBack) .predictiveBack(onBack = onBack)
@@ -81,22 +92,34 @@ fun CollapsingScaffold(
.background(MaterialTheme.colorScheme.surface) .background(MaterialTheme.colorScheme.surface)
.nestedScroll(scrollBehavior.nestedScrollConnection), .nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = { topBar = {
LargeTopAppBar( val navigationIcon = @Composable {
title = { Text(title) }, IconButton(onClick = onBack) {
navigationIcon = { Icon(
IconButton(onClick = onBack) { Icons.AutoMirrored.Filled.ArrowBack,
Icon( contentDescription = stringResource(R.string.settings_back),
Icons.AutoMirrored.Filled.ArrowBack, )
contentDescription = stringResource(R.string.settings_back), }
) }
} val colors = TopAppBarDefaults.topAppBarColors(
}, scrolledContainerColor = MaterialTheme.colorScheme.surface,
actions = actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
scrolledContainerColor = MaterialTheme.colorScheme.surface,
),
) )
if (largeTopBar) {
LargeTopAppBar(
title = { Text(title) },
navigationIcon = navigationIcon,
actions = actions,
scrollBehavior = scrollBehavior,
colors = colors,
)
} else {
TopAppBar(
title = { Text(title) },
navigationIcon = navigationIcon,
actions = actions,
scrollBehavior = scrollBehavior,
colors = colors,
)
}
}, },
snackbarHost = snackbarHost, snackbarHost = snackbarHost,
) { innerPadding -> ) { innerPadding ->

View File

@@ -43,8 +43,9 @@ import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
/** /**
* Shared full-screen scaffold for selection pickers: a full-bleed [Dialog] that * Shared full-screen scaffold for selection pickers: a full-bleed [Dialog] that
* reuses the app's [CollapsingScaffold] (collapsing title + back button), so a * reuses the app's [CollapsingScaffold] (back button + full width), but with a
* picker is visually identical to a Settings sub-page and uses the full width. * pinned single-line title rather than the large collapsing header — a picker is
* a short list, so the tall header would only be empty space to scroll past.
* [content] places the connected grouped rows; selecting one calls [onDismiss]. * [content] places the connected grouped rows; selecting one calls [onDismiss].
*/ */
@Composable @Composable
@@ -69,7 +70,12 @@ fun FullScreenPicker(
(view.parent as? DialogWindowProvider)?.window (view.parent as? DialogWindowProvider)?.window
?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING) ?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
} }
CollapsingScaffold(title = title, onBack = onDismiss, content = content) CollapsingScaffold(
title = title,
onBack = onDismiss,
largeTopBar = false,
content = content,
)
} }
} }