@@ -123,6 +123,7 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
@@ -744,7 +745,7 @@ internal fun WeekdayHeader(weekStart: DayOfWeek, showWeekNumbers: Boolean) {
. padding ( horizontal = AppBarSpacing . Inset , vertical = 4 . dp ) ,
) {
// Reserve the gutter so the weekday labels stay over their day columns.
if ( showWeekNumbers ) Spacer ( Modifier . width ( WEEK _NUMBER _GUTTER ) )
if ( showWeekNumbers ) Spacer ( Modifier . width ( rememberWeekNumberGutter ( ) ) )
days . forEach { dow ->
val isWeekend = dow == DayOfWeek . SATURDAY || dow == DayOfWeek . SUNDAY
val javaDow = java . time . DayOfWeek . of ( dow . ordinal + 1 )
@@ -762,9 +763,34 @@ internal fun WeekdayHeader(weekStart: DayOfWeek, showWeekNumbers: Boolean) {
private val EVENT _ROW _HEIGHT = 20 . dp
private val DAY _NUMBER _HEIGHT = 22 . dp
/** Width of the optional left calendar-week gutter (#25); narrow, since it only
* seats a one- or two-digit week number in a full-height tonal pill. */
private val WEEK _NUMBER _GUTTER = 40 . dp
/** Padding between the week-number pill's edge and the number inside it. */
private val WEEK _NUMBER _PADDING = 6 . dp
/** The widest week number an ISO year reaches; digits are tabular, so one
* measurement of it prices every week in the grid. */
private const val WEEK _NUMBER _SAMPLE = " 53 "
/**
* Width of the optional left calendar-week gutter (#25), measured rather than
* fixed: it is sized to the number it seats at the style the pill draws it in,
* so it follows the font scale instead of reserving slack for it, and spends
* nothing more on a column the grid would rather hand to the seven days (#213).
*/
@Composable
private fun rememberWeekNumberGutter ( ) : Dp {
val measurer = rememberTextMeasurer ( )
val density = LocalDensity . current
val style = weekNumberStyle ( )
return remember ( style , density , measurer ) {
val text = with ( density ) { measurer . measure ( WEEK _NUMBER _SAMPLE , style ) . size . width . toDp ( ) }
text + ( WEEK _NUMBER _PADDING + CELL _GAP ) * 2
}
}
/** The week number's own style — a step down from the day numbers beside it. */
@Composable
private fun weekNumberStyle ( ) =
MaterialTheme . typography . labelMedium . copy ( fontWeight = FontWeight . Bold )
private val DAY _NUMBER _GAP = 4 . dp
private val CELL _TOP _PADDING = 6 . dp
/** Named separately because the split style's selection outline draws its own
@@ -1141,10 +1167,10 @@ private val MONTH_EXPAND_THRESHOLD = 48.dp
* lists whatever day is selected — and a downward drag trades the pane away for
* the full paged grid, an upward one brings it back (#53).
*
* The grid slides between months like the paged style, and stands only as many
* rows tall as its own month spans (#162). A swipe between a five-r ow month and
* a six-row one therefore moves the pane by a row as well as swapping the grid;
* the row it hands back is worth more to the pane than a still edge i s.
* The grid slides between months like the paged style, which it can only do
* because it always reserves [SPLIT_GRID_ROWS] rows. Sized to its own month it
* stood 4– 6 rows tall, so every swipe shunted the pane up or down by a row on
* top of swapping the grid — the pane now holds still and only the grid move s.
*
* Expansion is deliberately **not** a stored preference. It is a way to look at
* the month you are on, not a fourth style; persisted, someone would expand it
@@ -1441,6 +1467,13 @@ private val SPLIT_DOT_SIZE = 5.dp
// the paged grid's lanes, so the two caps have to be the same number or a dot
// would have no bar to become (#53).
/**
* Rows the split grid always reserves — the most any month needs. A month that
* fits in fewer pads the remainder with blank rows rather than shrinking, which
* is what lets the pane below hold still from month to month.
*/
private const val SPLIT _GRID _ROWS = 6
/**
* The expand handle: M3's drag-handle pill (32× 4dp), in a row tall enough to be a
* comfortable tap target on its own.
@@ -1453,11 +1486,6 @@ private val SPLIT_HANDLE_ROW_HEIGHT = 24.dp
* The split style's grid (#53): the month compressed to day numbers and event
* dots, with the selected day listed underneath by [SplitDayPane].
*
* Only the rows the month actually spans. It used to pad every month out to six
* so the pane below held still from page to page, but a row is a sixth of the
* grid and a third of what the pane gets to show — too much to leave blank on
* the months that don't need it (#162).
*
* Tapping selects rather than drilling into the Day view — the pane is the
* answer to "what's on this day", so opening a whole screen for it would defeat
* the layout. The full Day view stays one tap away on the pane's date header.
@@ -1484,7 +1512,7 @@ internal fun SplitMonthGrid(
WeekNumberGutter (
weekStart = week . days . first ( ) ,
modifier = Modifier
. width ( WEEK _NUMBER _GUTTER )
. width ( rememberWeekNumberGutter ( ) )
. fillMaxHeight ( ) ,
)
}
@@ -1516,6 +1544,12 @@ internal fun SplitMonthGrid(
}
}
}
// Hold the grid at a constant height whatever shape the month is, so the
// pane beneath it doesn't move as you page and one month can slide over
// another without a height change under it.
repeat ( SPLIT _GRID _ROWS - state . weeks . size ) {
Spacer ( Modifier . fillMaxWidth ( ) . height ( SPLIT _ROW _HEIGHT ) )
}
}
}
@@ -1999,7 +2033,7 @@ private fun MonthWeekRow(
WeekNumberGutter (
weekStart = week . days . first ( ) ,
modifier = Modifier
. width ( WEEK _NUMBER _GUTTER )
. width ( rememberWeekNumberGutter ( ) )
. fillMaxHeight ( ) ,
)
}
@@ -2414,8 +2448,7 @@ private fun WeekNumberGutter(weekStart: LocalDate, modifier: Modifier = Modifier
) {
Text (
text = weekNumber . toString ( ) ,
style = MaterialTheme . typography . titleSmall ,
fontWeight = FontWeight . Bold ,
style = weekNumberStyle ( ) ,
color = MaterialTheme . colorScheme . onSecondaryContainer ,
)
}