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.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
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.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -28,9 +30,11 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.rememberDatePickerState
|
import androidx.compose.material3.rememberDatePickerState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
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
|
||||||
@ -39,6 +43,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.DatePeriod
|
import kotlinx.datetime.DatePeriod
|
||||||
import kotlinx.datetime.LocalDate
|
import kotlinx.datetime.LocalDate
|
||||||
import kotlinx.datetime.Month
|
import kotlinx.datetime.Month
|
||||||
@ -75,14 +80,19 @@ fun ShiftCalendarGrid(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }
|
val today = remember { Clock.System.todayIn(TimeZone.currentSystemDefault()) }
|
||||||
var viewYear by remember { mutableStateOf(today.year) }
|
val initialYear = remember { today.year }
|
||||||
var viewMonth by remember { mutableStateOf(today.month.number) }
|
val initialMonth = remember { today.month.number }
|
||||||
var showMonthPicker by remember { mutableStateOf(false) }
|
var showMonthPicker by remember { mutableStateOf(false) }
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
// 复用主日历的网格计算(周一为首日,动态行数 4/5/6)
|
// Pager 居中无限页,与主日历一致;viewYear/viewMonth 由当前页派生
|
||||||
val gridInfo = remember(viewYear, viewMonth) { getMonthGridInfo(viewYear, viewMonth) }
|
val pagerState = rememberPagerState(initialPage = START_PAGE, pageCount = { Int.MAX_VALUE })
|
||||||
val firstWeekdayOffset = gridInfo.offset
|
val viewYear by remember {
|
||||||
val rows = gridInfo.rows
|
derivedStateOf { pageToYearMonth(pagerState.currentPage, initialYear, initialMonth).first }
|
||||||
|
}
|
||||||
|
val viewMonth by remember {
|
||||||
|
derivedStateOf { pageToYearMonth(pagerState.currentPage, initialYear, initialMonth).second }
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
@ -97,7 +107,7 @@ fun ShiftCalendarGrid(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (viewMonth == 1) { viewMonth = 12; viewYear -= 1 } else viewMonth -= 1
|
coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage - 1) }
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Filled.ChevronLeft, contentDescription = "上个月")
|
Icon(Icons.Filled.ChevronLeft, contentDescription = "上个月")
|
||||||
}
|
}
|
||||||
@ -108,7 +118,7 @@ fun ShiftCalendarGrid(
|
|||||||
modifier = Modifier.clickable { showMonthPicker = true }
|
modifier = Modifier.clickable { showMonthPicker = true }
|
||||||
)
|
)
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if (viewMonth == 12) { viewMonth = 1; viewYear += 1 } else viewMonth += 1
|
coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) }
|
||||||
}) {
|
}) {
|
||||||
Icon(Icons.Filled.ChevronRight, contentDescription = "下个月")
|
Icon(Icons.Filled.ChevronRight, contentDescription = "下个月")
|
||||||
}
|
}
|
||||||
@ -127,25 +137,20 @@ fun ShiftCalendarGrid(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
(0 until rows).forEach { row ->
|
// 滑动翻页区域:每页一个月的网格
|
||||||
Row(Modifier.fillMaxWidth()) {
|
HorizontalPager(
|
||||||
(0 until 7).forEach { col ->
|
state = pagerState,
|
||||||
val cellIndex = row * 7 + col
|
beyondViewportPageCount = 0,
|
||||||
val dayNum = cellIndex - firstWeekdayOffset + 1
|
modifier = Modifier.fillMaxWidth()
|
||||||
if (dayNum in 1..gridInfo.daysInMonth) {
|
) { page ->
|
||||||
val date = LocalDate(viewYear, Month(viewMonth), dayNum)
|
val (year, month) = pageToYearMonth(page, initialYear, initialMonth)
|
||||||
ShiftDayCell(
|
MonthGrid(
|
||||||
date = date,
|
year = year,
|
||||||
pattern = pattern,
|
month = month,
|
||||||
onClick = { onPatternChange(toggleOverride(pattern, date)) },
|
pattern = pattern,
|
||||||
onLongClick = { onPatternChange(toggleFlipAndRephase(pattern, date)) },
|
onPatternChange = onPatternChange,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Box(Modifier.weight(1f).aspectRatio(1f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,8 +166,10 @@ fun ShiftCalendarGrid(
|
|||||||
TextButton(onClick = {
|
TextButton(onClick = {
|
||||||
datePickerState.selectedDateMillis?.let { millis ->
|
datePickerState.selectedDateMillis?.let { millis ->
|
||||||
val picked = millis.toLocalDate()
|
val picked = millis.toLocalDate()
|
||||||
viewYear = picked.year
|
val targetPage = yearMonthToPage(
|
||||||
viewMonth = picked.month.number
|
picked.year, picked.month.number, initialYear, initialMonth
|
||||||
|
)
|
||||||
|
coroutineScope.launch { pagerState.animateScrollToPage(targetPage) }
|
||||||
}
|
}
|
||||||
showMonthPicker = false
|
showMonthPicker = false
|
||||||
}) { Text("确定") }
|
}) { 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。
|
* 翻转某天的班/休(单日 override)。翻转后若与基础周期值一致,则移除 override。
|
||||||
*
|
*
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user