From c996d026cc7c68f6e1f88c81cb4662f035601463 Mon Sep 17 00:00:00 2001 From: meyou <2636699780@qq.com> Date: Sat, 16 May 2026 16:35:56 +0800 Subject: [PATCH] Replace year view month cells with mini calendar grids Each month in the 4x3 year grid now shows a compact calendar with day numbers, matching the iOS Calendar year view style. Today is highlighted with a filled circle. Selected month title uses primary color. --- .../plus/rua/project/ui/CalendarMonthView.kt | 1 + .../plus/rua/project/ui/YearGridView.kt | 140 +++++++++++++----- 2 files changed, 107 insertions(+), 34 deletions(-) diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt index a6350fc..21a0885 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/CalendarMonthView.kt @@ -246,6 +246,7 @@ fun CalendarMonthView( YearGridView( year = viewModel.yearViewYear, selectedMonth = if (viewModel.yearViewYear == currentYear) currentMonth else 0, + today = today, onMonthClick = { month -> viewModel.selectMonthFromYearView(month) // 同步 CalendarPager 到目标月份 diff --git a/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt b/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt index 720cf0c..abb18bb 100644 --- a/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt +++ b/shared/src/commonMain/kotlin/plus/rua/project/ui/YearGridView.kt @@ -5,8 +5,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -14,6 +12,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -23,9 +22,18 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import kotlinx.datetime.DatePeriod +import kotlinx.datetime.LocalDate +import kotlinx.datetime.minus +import kotlinx.datetime.number +import kotlinx.datetime.plus +import kotlinx.datetime.todayIn /** - * 年度网格视图,显示 4×3 月份网格,支持年份切换。 + * 年度网格视图,显示 4×3 精简月历网格,支持年份切换。 + * + * 每格显示一个精简版月历(月份标题 + 日期数字网格), + * 选中月份高亮,点击进入该月。 * * @param year 显示的年份 * @param selectedMonth 当前选中月份(1-12) @@ -37,6 +45,7 @@ import androidx.compose.ui.unit.sp fun YearGridView( year: Int, selectedMonth: Int, + today: LocalDate, onMonthClick: (Int) -> Unit, onYearChange: (Int) -> Unit, modifier: Modifier = Modifier @@ -62,13 +71,13 @@ fun YearGridView( .clickable { onYearChange(year - 1) } .padding(horizontal = 16.dp, vertical = 8.dp) ) - Spacer(modifier = Modifier.weight(1f)) - Text( - text = "${year}年", - style = MaterialTheme.typography.titleLarge, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = Modifier.weight(1f)) + Box(modifier = Modifier.weight(1f), contentAlignment = Alignment.Center) { + Text( + text = "${year}年", + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + } Text( text = "›", fontSize = 24.sp, @@ -81,12 +90,12 @@ fun YearGridView( ) } - // 4×3 月份网格 + // 4×3 月历网格 Column( modifier = Modifier .fillMaxWidth() .weight(1f) - .padding(horizontal = 16.dp), + .padding(horizontal = 8.dp), verticalArrangement = Arrangement.SpaceEvenly ) { (0 until 4).forEach { row -> @@ -96,9 +105,11 @@ fun YearGridView( ) { (0 until 3).forEach { col -> val month = row * 3 + col + 1 - MonthCell( + MiniMonth( + year = year, month = month, isSelected = month == selectedMonth, + today = today, onClick = { onMonthClick(month) }, modifier = Modifier.weight(1f) ) @@ -110,45 +121,106 @@ fun YearGridView( } /** - * 年视图中的月份单元格。 + * 精简版月历:月份标题 + 日期数字网格。 */ @Composable -private fun MonthCell( +private fun MiniMonth( + year: Int, month: Int, isSelected: Boolean, + today: LocalDate, onClick: () -> Unit, modifier: Modifier = Modifier ) { - val contentColor = if (isSelected) { - MaterialTheme.colorScheme.onPrimary + val days = remember(year, month) { generateMiniMonthDays(year, month) } + val titleColor = if (isSelected) { + MaterialTheme.colorScheme.primary } else { MaterialTheme.colorScheme.onSurface } - val bgColor = MaterialTheme.colorScheme.primary + val dayColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f) + val otherMonthColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.2f) + val todayBgColor = MaterialTheme.colorScheme.primary - Box( + Column( modifier = modifier - .aspectRatio(1f) - .padding(6.dp) + .padding(2.dp) .clip(CircleShape) - .drawBehind { - if (isSelected) { - drawCircle( - color = bgColor, - radius = size.minDimension / 2f, - center = Offset(size.width / 2f, size.height / 2f) - ) - } - } - .clickable(onClick = onClick), - contentAlignment = Alignment.Center + .clickable(onClick = onClick) + .padding(vertical = 4.dp), + horizontalAlignment = Alignment.CenterHorizontally ) { + // 月份标题 Text( text = "${month}月", - color = contentColor, - fontSize = 16.sp, + color = titleColor, + fontSize = 10.sp, fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal, textAlign = TextAlign.Center ) + // 日期网格 + days.chunked(7).forEach { week -> + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + week.forEach { dayData -> + val isToday = dayData.date == today + val color = when { + !dayData.isCurrentMonth -> otherMonthColor + isToday -> MaterialTheme.colorScheme.primary + else -> dayColor + } + Box( + contentAlignment = Alignment.Center + ) { + if (isToday) { + Box( + modifier = Modifier + .drawBehind { + drawCircle( + color = todayBgColor, + radius = size.minDimension / 2f, + center = Offset(size.width / 2f, size.height / 2f) + ) + } + .clip(CircleShape) + ) + } + Text( + text = if (dayData.isCurrentMonth) dayData.date.day.toString() else "", + color = if (isToday) MaterialTheme.colorScheme.onPrimary else color, + fontSize = 7.sp, + textAlign = TextAlign.Center, + lineHeight = 10.sp + ) + } + } + } + } + } +} + +private data class MiniDayData( + val date: LocalDate, + val isCurrentMonth: Boolean +) + +@Suppress("DEPRECATION") // monthNumber 无替代 API +private fun generateMiniMonthDays(year: Int, month: Int): List { + val firstOfMonth = LocalDate(year, month, 1) + val offset = firstOfMonth.dayOfWeek.ordinal + val startDate = firstOfMonth.minus(DatePeriod(days = offset)) + val nextMonth = if (month == 12) LocalDate(year + 1, 1, 1) else LocalDate(year, month + 1, 1) + val daysInMonth = nextMonth.minus(DatePeriod(days = 1)).day + val rows = ((offset + daysInMonth - 1) / 7) + 1 + val totalDays = rows * 7 + + return (0 until totalDays).map { i -> + val date = startDate.plus(DatePeriod(days = i)) + MiniDayData( + date = date, + isCurrentMonth = date.month.number == month && date.year == year + ) } }