feat(shift): swipeable mini calendar via HorizontalPager
迷你月历接入 HorizontalPager,支持左右滑动翻月: - 复用主日历 START_PAGE/pageToYearMonth/yearMonthToPage 工具 - viewYear/viewMonth 由 pagerState.currentPage 派生 - 箭头按钮改为 animateScrollToPage - DatePicker 选月后 animateScrollToPage 跳转 - 网格提取为 MonthGrid 单页组件
This commit is contained in:
parent
1cf950817e
commit
7b1290dd66
@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
@ -28,9 +30,11 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -39,6 +43,7 @@ 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.coroutines.launch
|
||||
import kotlinx.datetime.DatePeriod
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.Month
|
||||
@ -75,14 +80,19 @@ fun ShiftCalendarGrid(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }
|
||||
var viewYear by remember { mutableStateOf(today.year) }
|
||||
var viewMonth by remember { mutableStateOf(today.month.number) }
|
||||
val initialYear = remember { today.year }
|
||||
val initialMonth = remember { today.month.number }
|
||||
var showMonthPicker by remember { mutableStateOf(false) }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// 复用主日历的网格计算(周一为首日,动态行数 4/5/6)
|
||||
val gridInfo = remember(viewYear, viewMonth) { getMonthGridInfo(viewYear, viewMonth) }
|
||||
val firstWeekdayOffset = gridInfo.offset
|
||||
val rows = gridInfo.rows
|
||||
// Pager 居中无限页,与主日历一致;viewYear/viewMonth 由当前页派生
|
||||
val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE })
|
||||
val viewYear by remember {
|
||||
derivedStateOf { pageToYearMonth(pagerState.currentPage, initialYear, initialMonth).first }
|
||||
}
|
||||
val viewMonth by remember {
|
||||
derivedStateOf { pageToYearMonth(pagerState.currentPage, initialYear, initialMonth).second }
|
||||
}
|
||||
|
||||
Card(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
@ -97,7 +107,7 @@ fun ShiftCalendarGrid(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
if (viewMonth == 1) { viewMonth = 12; viewYear -= 1 } else viewMonth -= 1
|
||||
coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage - 1) }
|
||||
}) {
|
||||
Icon(Icons.Filled.ChevronLeft, contentDescription = "上个月")
|
||||
}
|
||||
@ -108,7 +118,7 @@ fun ShiftCalendarGrid(
|
||||
modifier = Modifier.clickable { showMonthPicker = true }
|
||||
)
|
||||
IconButton(onClick = {
|
||||
if (viewMonth == 12) { viewMonth = 1; viewYear += 1 } else viewMonth += 1
|
||||
coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) }
|
||||
}) {
|
||||
Icon(Icons.Filled.ChevronRight, contentDescription = "下个月")
|
||||
}
|
||||
@ -127,25 +137,20 @@ fun ShiftCalendarGrid(
|
||||
}
|
||||
}
|
||||
|
||||
(0 until rows).forEach { row ->
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
(0 until 7).forEach { col ->
|
||||
val cellIndex = row * 7 + col
|
||||
val dayNum = cellIndex - firstWeekdayOffset + 1
|
||||
if (dayNum in 1..gridInfo.daysInMonth) {
|
||||
val date = LocalDate(viewYear, Month(viewMonth), dayNum)
|
||||
ShiftDayCell(
|
||||
date = date,
|
||||
// 滑动翻页区域:每页一个月的网格
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
beyondViewportPageCount = 0,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) { page ->
|
||||
val (year, month) = pageToYearMonth(page, initialYear, initialMonth)
|
||||
MonthGrid(
|
||||
year = year,
|
||||
month = month,
|
||||
pattern = pattern,
|
||||
onClick = { onPatternChange(toggleOverride(pattern, date)) },
|
||||
onLongClick = { onPatternChange(toggleFlipAndRephase(pattern, date)) },
|
||||
modifier = Modifier.weight(1f)
|
||||
onPatternChange = onPatternChange,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
} else {
|
||||
Box(Modifier.weight(1f).aspectRatio(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -161,8 +166,10 @@ fun ShiftCalendarGrid(
|
||||
TextButton(onClick = {
|
||||
datePickerState.selectedDateMillis?.let { millis ->
|
||||
val picked = millis.toLocalDate()
|
||||
viewYear = picked.year
|
||||
viewMonth = picked.month.number
|
||||
val targetPage = yearMonthToPage(
|
||||
picked.year, picked.month.number, initialYear, initialMonth
|
||||
)
|
||||
coroutineScope.launch { pagerState.animateScrollToPage(targetPage) }
|
||||
}
|
||||
showMonthPicker = false
|
||||
}) { Text("确定") }
|
||||
@ -176,6 +183,48 @@ fun ShiftCalendarGrid(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 迷你月历的单页网格。复用主日历 [getMonthGridInfo] 计算行数与偏移。
|
||||
*
|
||||
* @param year 年
|
||||
* @param month 月(1-12)
|
||||
* @param pattern 当前轮班配置(只读)
|
||||
* @param onPatternChange 修改后的新 pattern 回调
|
||||
* @param modifier 外部布局修饰符
|
||||
*/
|
||||
@Composable
|
||||
private fun MonthGrid(
|
||||
year: Int,
|
||||
month: Int,
|
||||
pattern: ShiftPattern,
|
||||
onPatternChange: (ShiftPattern) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val gridInfo = remember(year, month) { getMonthGridInfo(year, month) }
|
||||
Column(modifier) {
|
||||
(0 until gridInfo.rows).forEach { row ->
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
(0 until 7).forEach { col ->
|
||||
val cellIndex = row * 7 + col
|
||||
val dayNum = cellIndex - gridInfo.offset + 1
|
||||
if (dayNum in 1..gridInfo.daysInMonth) {
|
||||
val date = LocalDate(year, Month(month), dayNum)
|
||||
ShiftDayCell(
|
||||
date = date,
|
||||
pattern = pattern,
|
||||
onClick = { onPatternChange(toggleOverride(pattern, date)) },
|
||||
onLongClick = { onPatternChange(toggleFlipAndRephase(pattern, date)) },
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
} else {
|
||||
Box(Modifier.weight(1f).aspectRatio(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 翻转某天的班/休(单日 override)。翻转后若与基础周期值一致,则移除 override。
|
||||
*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user