Fix the sidebar's back gesture and alignment (#114) (#121)
All checks were successful
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 6s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Has been skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Has been skipped

- Back now closes the drawer instead of the app: a `BackHandler` inside the sheet, enabled while the drawer is open. The four host screens (Month/Week/Day/Agenda) pass their `drawerState` in.
- Header and section labels use `GroupedListInset` instead of a hardcoded 28.dp, so "Calendula", "View" and "Calendars" share the left edge of the rows below them — same as the Settings screens. The account labels and placeholders in `CalendarFilterList` follow.
- Plain leading icons now sit in a 40.dp box, matching `CalendarColorChip`'s footprint, so view rows, jump-to-date, Settings and the calendar rows all line up on one vertical axis.

Closes #114

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/calendula/pulls/121
This commit is contained in:
Jean-Luc Makiola
2026-07-31 20:50:13 +02:00
parent 9054742503
commit 9767fbbbaf
6 changed files with 50 additions and 9 deletions

View File

@@ -112,6 +112,7 @@ fun AgendaScreen(
CalendarDrawer( CalendarDrawer(
currentView = selectedView, currentView = selectedView,
currentDate = anchor, currentDate = anchor,
drawerState = drawerState,
viewOrder = drawerViewOrder, viewOrder = drawerViewOrder,
onSelectView = { view -> onSelectView = { view ->
onSelectView(view) onSelectView(view)

View File

@@ -1,5 +1,6 @@
package de.jeanlucmakiola.calendula.ui.common package de.jeanlucmakiola.calendula.ui.common
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -19,6 +20,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.DrawerState
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.ModalDrawerSheet
@@ -27,19 +29,23 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.calendula.ui.filter.CalendarFilterList import de.jeanlucmakiola.calendula.ui.filter.CalendarFilterList
import de.jeanlucmakiola.floret.components.GroupedListInset
import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedRow
import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.components.Position
import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.components.positionOf
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
/** /**
@@ -50,7 +56,8 @@ import kotlinx.datetime.LocalDate
* a jump-to-date action, the per-calendar visibility filter (M3) inline, and a * a jump-to-date action, the per-calendar visibility filter (M3) inline, and a
* pinned Settings row. The "View" section mirrors the top-bar switcher pill — * pinned Settings row. The "View" section mirrors the top-bar switcher pill —
* tapping a view here selects it (and closes the drawer) rather than cycling. * tapping a view here selects it (and closes the drawer) rather than cycling.
* The host screen owns the drawer state. * The host screen owns the drawer state; the sheet reads it only to dismiss
* itself on back.
* *
* [currentDate] seeds the jump-to-date picker (the visible day/week-start/month * [currentDate] seeds the jump-to-date picker (the visible day/week-start/month
* anchor); [onJumpToDate] navigates the active view to the chosen day. * anchor); [onJumpToDate] navigates the active view to the chosen day.
@@ -59,12 +66,17 @@ import kotlinx.datetime.LocalDate
fun CalendarDrawer( fun CalendarDrawer(
currentView: CalendarView, currentView: CalendarView,
currentDate: LocalDate, currentDate: LocalDate,
drawerState: DrawerState,
onSelectView: (CalendarView) -> Unit, onSelectView: (CalendarView) -> Unit,
onJumpToDate: (LocalDate) -> Unit, onJumpToDate: (LocalDate) -> Unit,
onSettings: () -> Unit, onSettings: () -> Unit,
viewOrder: List<CalendarView> = IMPLEMENTED_VIEWS, viewOrder: List<CalendarView> = IMPLEMENTED_VIEWS,
) { ) {
var showDatePicker by remember { mutableStateOf(false) } var showDatePicker by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
// Registered in the sheet so it takes precedence over the host's back handler.
BackHandler(enabled = drawerState.isOpen) { scope.launch { drawerState.close() } }
ModalDrawerSheet { ModalDrawerSheet {
// The whole sidebar scrolls as one — header, views, the calendar filter // The whole sidebar scrolls as one — header, views, the calendar filter
@@ -83,7 +95,7 @@ fun CalendarDrawer(
position = positionOf(index, viewOrder.size), position = positionOf(index, viewOrder.size),
selected = view == currentView, selected = view == currentView,
minHeight = 56.dp, minHeight = 56.dp,
leading = { Icon(view.icon, contentDescription = null) }, leading = { DrawerLeadingIcon(view.icon) },
onClick = { onSelectView(view) }, onClick = { onSelectView(view) },
) )
} }
@@ -93,7 +105,7 @@ fun CalendarDrawer(
title = stringResource(R.string.drawer_jump_to_date), title = stringResource(R.string.drawer_jump_to_date),
position = Position.Alone, position = Position.Alone,
minHeight = 56.dp, minHeight = 56.dp,
leading = { Icon(Icons.Filled.DateRange, contentDescription = null) }, leading = { DrawerLeadingIcon(Icons.Filled.DateRange) },
onClick = { showDatePicker = true }, onClick = { showDatePicker = true },
) )
@@ -107,7 +119,7 @@ fun CalendarDrawer(
title = stringResource(R.string.month_action_settings), title = stringResource(R.string.month_action_settings),
position = Position.Alone, position = Position.Alone,
minHeight = 56.dp, minHeight = 56.dp,
leading = { Icon(Icons.Filled.Settings, contentDescription = null) }, leading = { DrawerLeadingIcon(Icons.Filled.Settings) },
onClick = onSettings, onClick = onSettings,
) )
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
@@ -126,13 +138,27 @@ fun CalendarDrawer(
} }
} }
/** Leading slot for the drawer's plain icons: the same 40.dp footprint
* [CalendarColorChip] takes, so every leading glyph shares one vertical axis. */
@Composable
private fun DrawerLeadingIcon(icon: ImageVector) {
Box(Modifier.size(40.dp), contentAlignment = Alignment.Center) {
Icon(icon, contentDescription = null)
}
}
/** Branded header: the app-icon chip beside the app name. */ /** Branded header: the app-icon chip beside the app name. */
@Composable @Composable
private fun DrawerHeader() { private fun DrawerHeader() {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(start = 28.dp, end = 28.dp, top = 24.dp, bottom = 16.dp), .padding(
start = GroupedListInset,
end = GroupedListInset,
top = 24.dp,
bottom = 16.dp,
),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Box( Box(
@@ -164,6 +190,11 @@ private fun DrawerSectionHeader(text: String) {
text = text, text = text,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(start = 28.dp, end = 28.dp, top = 16.dp, bottom = 8.dp), modifier = Modifier.padding(
start = GroupedListInset,
end = GroupedListInset,
top = 16.dp,
bottom = 8.dp,
),
) )
} }

View File

@@ -198,6 +198,7 @@ fun DayScreen(
CalendarDrawer( CalendarDrawer(
currentView = selectedView, currentView = selectedView,
currentDate = date, currentDate = date,
drawerState = drawerState,
viewOrder = drawerViewOrder, viewOrder = drawerViewOrder,
onSelectView = { view -> onSelectView = { view ->
onSelectView(view) onSelectView(view)

View File

@@ -22,6 +22,7 @@ import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.calendula.domain.FailureReason import de.jeanlucmakiola.calendula.domain.FailureReason
import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip
import de.jeanlucmakiola.calendula.ui.common.sourceAppName import de.jeanlucmakiola.calendula.ui.common.sourceAppName
import de.jeanlucmakiola.floret.components.GroupedListInset
import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedRow
import de.jeanlucmakiola.floret.components.positionOf import de.jeanlucmakiola.floret.components.positionOf
@@ -73,7 +74,12 @@ private fun FilterList(
}, },
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant, color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 28.dp, end = 28.dp, top = 12.dp, bottom = 4.dp), modifier = Modifier.padding(
start = GroupedListInset,
end = GroupedListInset,
top = 12.dp,
bottom = 4.dp,
),
) )
group.calendars.forEachIndexed { index, cal -> group.calendars.forEachIndexed { index, cal ->
GroupedRow( GroupedRow(
@@ -103,7 +109,7 @@ private fun FilterLoading(modifier: Modifier = Modifier) {
repeat(4) { repeat(4) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(horizontal = 28.dp) .padding(horizontal = GroupedListInset)
.fillMaxWidth() .fillMaxWidth()
.height(36.dp) .height(36.dp)
.background( .background(
@@ -129,6 +135,6 @@ private fun FilterMessage(reason: FailureReason, modifier: Modifier = Modifier)
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 28.dp, vertical = 24.dp), .padding(horizontal = GroupedListInset, vertical = 24.dp),
) )
} }

View File

@@ -330,6 +330,7 @@ fun MonthScreen(
CalendarDrawer( CalendarDrawer(
currentView = selectedView, currentView = selectedView,
currentDate = LocalDate(titleMonth.year, titleMonth.month, 1), currentDate = LocalDate(titleMonth.year, titleMonth.month, 1),
drawerState = drawerState,
viewOrder = drawerViewOrder, viewOrder = drawerViewOrder,
onSelectView = { view -> onSelectView = { view ->
onSelectView(view) onSelectView(view)

View File

@@ -219,6 +219,7 @@ fun WeekScreen(
CalendarDrawer( CalendarDrawer(
currentView = selectedView, currentView = selectedView,
currentDate = weekStart, currentDate = weekStart,
drawerState = drawerState,
viewOrder = drawerViewOrder, viewOrder = drawerViewOrder,
onSelectView = { view -> onSelectView = { view ->
onSelectView(view) onSelectView(view)