calendar: рескин месячной сетки под макет

По «Календарь · Месяц»: дни недели — серые пилюли; сетка безрамочная с
гридлайнами (0.5dp) вместо скруглённых боксов; номер дня сверху-слева;
сегодня — в зелёном круге (Primary, белый текст); событие — зелёная
пилюля PrimaryLight (до 2 + «+N»), было одно превью/точка. Компактная
модель (месяц + список дня снизу) сохранена. Компилируется.
This commit is contained in:
b-dev-mobile
2026-07-08 10:15:41 +00:00
parent 335468b80f
commit a727e2dbd1
2 changed files with 86 additions and 69 deletions
@@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@@ -388,46 +387,55 @@ private fun CalendarIconButton(
fun CalendarMonthGrid( fun CalendarMonthGrid(
month: YearMonth, month: YearMonth,
selectedDay: LocalDate, selectedDay: LocalDate,
daysWithEvents: Set<LocalDate>,
eventsByDay: Map<LocalDate, List<CalendarEventItem>>, eventsByDay: Map<LocalDate, List<CalendarEventItem>>,
onSelectDay: (LocalDate) -> Unit, onSelectDay: (LocalDate) -> Unit,
) { ) {
val days = CalendarRepository.monthGridDays(month) val days = CalendarRepository.monthGridDays(month)
val today = LocalDate.now() val today = LocalDate.now()
Column( Column(
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth() verticalArrangement = Arrangement.spacedBy(6.dp),
.clip(RoundedCornerShape(12.dp)) ) {
.background(F7Colors.Surface) // Заголовок дней недели — серые пилюли (по макету «Календарь · Месяц»)
.border(1.dp, F7Colors.Border, RoundedCornerShape(12.dp)) Row(
.padding(8.dp), modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(4.dp), horizontalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
Row(modifier = Modifier.fillMaxWidth()) {
weekDayLabels.forEach { label -> weekDayLabels.forEach { label ->
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(100.dp))
.background(F7Colors.Grey2)
.padding(vertical = 6.dp),
contentAlignment = Alignment.Center,
) {
Text( Text(
text = label, text = label,
modifier = Modifier.weight(1f), style = MaterialTheme.typography.labelMedium,
textAlign = TextAlign.Center, fontWeight = FontWeight.Medium,
style = MaterialTheme.typography.labelSmall, color = F7Colors.TextSecondary,
color = F7Colors.TextMuted,
) )
} }
} }
}
// Гридлайн-сетка без скруглений (по макету)
Column(
modifier = Modifier
.fillMaxWidth()
.border(0.5.dp, F7Colors.Grey3),
) {
days.chunked(7).forEach { week -> days.chunked(7).forEach { week ->
Row(modifier = Modifier.fillMaxWidth()) { Row(modifier = Modifier.fillMaxWidth()) {
week.forEach { day -> week.forEach { day ->
val inMonth = day.month == month.month val inMonth = day.month == month.month
val selected = day == selectedDay
val hasEvents = daysWithEvents.contains(day)
val dayEvents = eventsByDay[day].orEmpty() val dayEvents = eventsByDay[day].orEmpty()
CalendarDayCell( CalendarDayCell(
day = day.dayOfMonth, day = day.dayOfMonth,
inMonth = inMonth, inMonth = inMonth,
isToday = day == today, isToday = day == today,
selected = selected, selected = day == selectedDay,
hasEvents = hasEvents, events = dayEvents,
preview = dayEvents.firstOrNull()?.summary.orEmpty(),
onClick = { onSelectDay(day) }, onClick = { onSelectDay(day) },
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
) )
@@ -436,6 +444,7 @@ fun CalendarMonthGrid(
} }
} }
} }
}
@Composable @Composable
private fun CalendarDayCell( private fun CalendarDayCell(
@@ -443,63 +452,72 @@ private fun CalendarDayCell(
inMonth: Boolean, inMonth: Boolean,
isToday: Boolean, isToday: Boolean,
selected: Boolean, selected: Boolean,
hasEvents: Boolean, events: List<CalendarEventItem>,
preview: String,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val bg = when { val cellBg = when {
isToday -> F7Colors.Green10
selected -> F7Colors.PrimaryLight selected -> F7Colors.PrimaryLight
isToday -> F7Colors.PrimaryLight.copy(alpha = 0.55f)
else -> F7Colors.Surface else -> F7Colors.Surface
} }
val borderColor = when { Column(
selected -> F7Colors.Primary
isToday -> F7Colors.PrimaryDark
else -> F7Colors.BorderLight
}
Box(
modifier = modifier modifier = modifier
.aspectRatio(1f) .heightIn(min = 66.dp)
.padding(2.dp) .border(0.5.dp, F7Colors.Grey3)
.clip(RoundedCornerShape(8.dp)) .background(cellBg)
.background(bg)
.border(1.dp, borderColor, RoundedCornerShape(8.dp))
.clickable(onClick = onClick) .clickable(onClick = onClick)
.padding(2.dp), .padding(horizontal = 4.dp, vertical = 4.dp),
contentAlignment = Alignment.TopCenter, verticalArrangement = Arrangement.spacedBy(2.dp),
) {
// Номер дня сверху-слева; сегодня — в зелёном круге
Box(
modifier = Modifier
.size(22.dp)
.then(if (isToday) Modifier.clip(CircleShape).background(F7Colors.Primary) else Modifier),
contentAlignment = Alignment.Center,
) { ) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text( Text(
text = day.toString(), text = day.toString(),
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
fontWeight = if (isToday) FontWeight.Bold else FontWeight.Normal, fontWeight = if (isToday) FontWeight.SemiBold else FontWeight.Normal,
color = when { color = when {
isToday -> F7Colors.TextOnPrimary
!inMonth -> F7Colors.TextMuted !inMonth -> F7Colors.TextMuted
selected -> F7Colors.PrimaryDark
else -> F7Colors.TextPrimary else -> F7Colors.TextPrimary
}, },
) )
if (hasEvents && inMonth && preview.isNotBlank()) { }
if (inMonth) {
events.take(2).forEach { event ->
CalendarDayEventPill(event.summary.ifBlank { "Событие" })
}
if (events.size > 2) {
Text( Text(
preview, "+${events.size - 2}",
style = MaterialTheme.typography.labelSmall,
color = F7Colors.Primary,
fontWeight = FontWeight.Medium,
)
}
}
}
}
@Composable
private fun CalendarDayEventPill(text: String) {
Text(
text = text,
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(4.dp))
.background(F7Colors.PrimaryLight)
.padding(horizontal = 4.dp, vertical = 1.dp),
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
color = F7Colors.PrimaryDark, color = F7Colors.PrimaryDark,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(top = 1.dp),
) )
} else if (hasEvents && inMonth) {
Box(
modifier = Modifier
.padding(top = 2.dp)
.size(5.dp)
.clip(CircleShape)
.background(F7Colors.Primary),
)
}
}
}
} }
@Composable @Composable
@@ -343,7 +343,6 @@ fun CalendarScreen(
CalendarMonthGrid( CalendarMonthGrid(
month = state.visibleMonth, month = state.visibleMonth,
selectedDay = state.selectedDay, selectedDay = state.selectedDay,
daysWithEvents = vm.daysWithEvents(),
eventsByDay = eventsByDay, eventsByDay = eventsByDay,
onSelectDay = vm::selectDay, onSelectDay = vm::selectDay,
) )