diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt index a6a9dde..2e13365 100644 --- a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt +++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/mainFunc/PurchaseScreen.kt @@ -1,7 +1,10 @@ package com.bbitcn.f8.pad.ui.screen.mainFunc import android.content.res.Configuration -import androidx.compose.foundation.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.text.TextPaint +import android.text.TextUtils import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -13,9 +16,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.DatePickerDialog @@ -31,7 +31,6 @@ import androidx.compose.material3.rememberDateRangePickerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -40,18 +39,16 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.nativeCanvas +import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.drawText import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.text.rememberTextMeasurer import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import com.bbitcn.f8.pad.M import com.bbitcn.f8.pad.base.MainFuncFrame @@ -362,10 +359,7 @@ fun InfoList( }, columns = columns, item = { data, index -> - val isSelected by remember(data.czSysid) { - derivedStateOf { data.czSysid == selectedId.value } - } - InfoItem(isSelect = isSelected, data = data) { + InfoItem(isSelect = data.czSysid == selectedId.value, data = data) { selectedId.value = data.czSysid onClick(data) } @@ -397,55 +391,12 @@ fun InfoItem( ) .clickable(onClick = onClick) ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = M - .fillMaxWidth() - .height(32.dp) - .background(color = if (isSelect) MyColors.BlueGreen else MyColors.White) - ) { - Text( - text = data.nhName, - fontSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = M - .padding(horizontal = 10.dp) - .widthIn(max = 50.dp), - maxLines = 1, - overflow = TextOverflow.Ellipsis, // 超出部分显示省略号 - color = if (isSelect) MyColors.White else MyColors.Black, - fontWeight = FontWeight.Bold - ) - Spacer(modifier = M.weight(1f)) - Text( - text = data.billCode.toString(), - fontSize = MaterialTheme.typography.bodyMedium.fontSize, - modifier = M.padding(start = 4.dp), - color = if (isSelect) MyColors.White else MyColors.Black, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - Box( - modifier = M - .padding(start = 6.dp) - .height(32.dp) - .wrapContentWidth() - .background( - color = MyColors.Orange, - shape = RoundedCornerShape(bottomStart = 7.dp) - ), - contentAlignment = Alignment.Center - ) { - Text( - text = data.billState, - modifier = M.padding(horizontal = 6.dp), - color = MyColors.White, - fontSize = MaterialTheme.typography.bodyMedium.fontSize, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } + PurchaseCardHeader( + isSelect = isSelect, + name = data.nhName, + billCode = data.billCode.toString(), + billState = data.billState + ) PurchaseSummaryTable( visibleItems = visibleItems, hiddenCount = hiddenCount @@ -457,17 +408,149 @@ fun InfoItem( } } +private data class PurchaseCardHeaderText( + val name: String, + val billCode: String, + val billState: String, + val stateWidth: Float +) + +@Composable +private fun PurchaseCardHeader( + isSelect: Boolean, + name: String, + billCode: String, + billState: String +) { + val fontSize = MaterialTheme.typography.bodyMedium.fontSize + val backgroundColor = if (isSelect) MyColors.BlueGreen else MyColors.White + val textColor = if (isSelect) MyColors.White else MyColors.Black + val statusColor = MyColors.Orange + val statusTextColor = MyColors.White + + Box( + modifier = M + .fillMaxWidth() + .height(32.dp) + .drawWithCache { + val boldPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = textColor.toArgb() + textSize = fontSize.toPx() + textAlign = Paint.Align.LEFT + isFakeBoldText = true + isSubpixelText = true + } + val billPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = textColor.toArgb() + textSize = fontSize.toPx() + textAlign = Paint.Align.RIGHT + isFakeBoldText = true + isSubpixelText = true + } + val statusTextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = statusTextColor.toArgb() + textSize = fontSize.toPx() + textAlign = Paint.Align.CENTER + isSubpixelText = true + } + val statusFillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = statusColor.toArgb() + style = Paint.Style.FILL + } + val horizontalPadding = 10.dp.toPx() + val statusHorizontalPadding = 6.dp.toPx() + val nameMaxWidth = 50.dp.toPx() + val gap = 6.dp.toPx() + val stateMaxWidth = (size.width * 0.34f).coerceAtLeast(48.dp.toPx()) + val stateText = TextUtils.ellipsize( + billState, + statusTextPaint, + (stateMaxWidth - statusHorizontalPadding * 2).coerceAtLeast(1f), + TextUtils.TruncateAt.END + ).toString() + val stateWidth = ( + statusTextPaint.measureText(stateText) + statusHorizontalPadding * 2 + ).coerceAtMost(stateMaxWidth) + val nameText = TextUtils.ellipsize( + name, + boldPaint, + nameMaxWidth, + TextUtils.TruncateAt.END + ).toString() + val billRight = (size.width - stateWidth - gap).coerceAtLeast(horizontalPadding) + val billMaxWidth = (billRight - horizontalPadding - nameMaxWidth - gap).coerceAtLeast(1f) + val billText = TextUtils.ellipsize( + billCode, + billPaint, + billMaxWidth, + TextUtils.TruncateAt.END + ).toString() + val clippedText = PurchaseCardHeaderText( + name = nameText, + billCode = billText, + billState = stateText, + stateWidth = stateWidth + ) + val cornerRadius = 7.dp.toPx() + + onDrawBehind { + drawRect(color = backgroundColor, topLeft = Offset.Zero) + + val baseline = (size.height - boldPaint.fontMetrics.ascent - boldPaint.fontMetrics.descent) / 2f + drawIntoCanvas { canvas -> + val nativeCanvas = canvas.nativeCanvas + nativeCanvas.drawText(clippedText.name, horizontalPadding, baseline, boldPaint) + nativeCanvas.drawText(clippedText.billCode, billRight, baseline, billPaint) + + val left = size.width - clippedText.stateWidth + val path = android.graphics.Path().apply { + moveTo(left, 0f) + lineTo(size.width, 0f) + lineTo(size.width, size.height) + lineTo(left + cornerRadius, size.height) + arcTo( + RectF( + left, + size.height - cornerRadius * 2, + left + cornerRadius * 2, + size.height + ), + 90f, + 90f + ) + lineTo(left, 0f) + close() + } + nativeCanvas.drawPath(path, statusFillPaint) + nativeCanvas.drawText( + clippedText.billState, + left + clippedText.stateWidth / 2f, + baseline, + statusTextPaint + ) + } + } + } + ) +} + +private data class PurchaseSummaryTableText( + val headers: List, + val rows: List>, + val moreText: String? +) + @Composable private fun PurchaseSummaryTable( visibleItems: List, hiddenCount: Int ) { - val textMeasurer = rememberTextMeasurer() val bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize val labelFontSize = MaterialTheme.typography.labelSmall.fontSize val headerBackground = MyColors.White val headerTextColor = MyColors.Black val rowBackground = MyColors.White + val rowTextColor = MyColors.Black val moreTextColor = MyColors.Gray val headers = remember { listOf("茧别", "毛重", "皮重", "扣重", "净重", "单价") @@ -484,128 +567,216 @@ private fun PurchaseSummaryTable( ) } } - val headerStyle = TextStyle( - color = headerTextColor, - fontSize = bodyFontSize, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center - ) - val rowStyle = TextStyle( - color = MyColors.Black, - fontSize = bodyFontSize, - textAlign = TextAlign.Center - ) - val moreStyle = TextStyle( - color = moreTextColor, - fontSize = labelFontSize - ) - Canvas( + Box( modifier = M .fillMaxWidth() .height(88.dp) - ) { - val headerHeight = 28.dp.toPx() - val rowHeight = 24.dp.toPx() - val cellWidth = size.width / headers.size - val cellConstraints = Constraints.fixedWidth(cellWidth.toInt().coerceAtLeast(1)) - - fun drawCell(text: String, column: Int, top: Float, height: Float, style: TextStyle) { - val result = textMeasurer.measure( - text = AnnotatedString(text), - style = style, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - constraints = cellConstraints - ) - drawText( - textLayoutResult = result, - topLeft = Offset( - x = column * cellWidth, - y = top + (height - result.size.height) / 2f + .drawWithCache { + val headerPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = headerTextColor.toArgb() + textSize = bodyFontSize.toPx() + textAlign = Paint.Align.CENTER + isFakeBoldText = true + isSubpixelText = true + } + val rowPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = rowTextColor.toArgb() + textSize = bodyFontSize.toPx() + textAlign = Paint.Align.CENTER + isSubpixelText = true + } + val morePaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = moreTextColor.toArgb() + textSize = labelFontSize.toPx() + textAlign = Paint.Align.LEFT + isSubpixelText = true + } + val headerHeight = 28.dp.toPx() + val rowHeight = 24.dp.toPx() + val cellWidth = size.width / headers.size + val cellTextWidthPx = (cellWidth - 8.dp.toPx()).coerceAtLeast(1f) + val moreTextWidthPx = (size.width - 16.dp.toPx()).coerceAtLeast(1f) + val clippedText = PurchaseSummaryTableText( + headers = headers.map { text -> + TextUtils.ellipsize( + text, + headerPaint, + cellTextWidthPx, + TextUtils.TruncateAt.END + ).toString() + }, + rows = rows.map { row -> + row.map { text -> + TextUtils.ellipsize( + text, + rowPaint, + cellTextWidthPx, + TextUtils.TruncateAt.END + ).toString() + } + }, + moreText = if (hiddenCount > 0) { + TextUtils.ellipsize( + "+$hiddenCount", + morePaint, + moreTextWidthPx, + TextUtils.TruncateAt.END + ).toString() + } else { + null + } ) - ) - } - drawRect(color = headerBackground, topLeft = Offset.Zero) - headers.forEachIndexed { index, text -> - drawCell(text, index, 0f, headerHeight, headerStyle) - } + onDrawBehind { + fun drawNativeText( + text: String, + x: Float, + top: Float, + height: Float, + paint: TextPaint + ) { + val baseline = top + (height - paint.fontMetrics.ascent - paint.fontMetrics.descent) / 2f + drawIntoCanvas { canvas -> + canvas.nativeCanvas.drawText(text, x, baseline, paint) + } + } - rows.forEachIndexed { rowIndex, row -> - val rowTop = headerHeight + rowIndex * rowHeight - drawRect( - color = rowBackground, - topLeft = Offset(0f, rowTop), - size = androidx.compose.ui.geometry.Size(size.width, rowHeight) - ) - row.forEachIndexed { columnIndex, text -> - drawCell(text, columnIndex, rowTop, rowHeight, rowStyle) + fun drawCell(text: String, column: Int, top: Float, height: Float, paint: TextPaint) { + drawNativeText( + text = text, + x = column * cellWidth + cellWidth / 2f, + top = top, + height = height, + paint = paint + ) + } + + drawRect(color = headerBackground, topLeft = Offset.Zero) + clippedText.headers.forEachIndexed { index, text -> + drawCell(text, index, 0f, headerHeight, headerPaint) + } + + clippedText.rows.forEachIndexed { rowIndex, row -> + val rowTop = headerHeight + rowIndex * rowHeight + drawRect( + color = rowBackground, + topLeft = Offset(0f, rowTop), + size = androidx.compose.ui.geometry.Size(size.width, rowHeight) + ) + row.forEachIndexed { columnIndex, text -> + drawCell(text, columnIndex, rowTop, rowHeight, rowPaint) + } + } + + clippedText.moreText?.let { text -> + val moreTop = headerHeight + clippedText.rows.size * rowHeight + 2.dp.toPx() + drawNativeText( + text = text, + x = 8.dp.toPx(), + top = moreTop, + height = (size.height - moreTop).coerceAtLeast(1f), + paint = morePaint + ) + } + } } - } - - if (hiddenCount > 0) { - val moreTop = headerHeight + rows.size * rowHeight + 2.dp.toPx() - val result = textMeasurer.measure( - text = AnnotatedString("+$hiddenCount"), - style = moreStyle, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - constraints = Constraints(maxWidth = size.width.toInt().coerceAtLeast(1)) - ) - drawText( - textLayoutResult = result, - topLeft = Offset(8.dp.toPx(), moreTop) - ) - } - } + ) } +private data class PurchaseStateChip( + val text: String, + val selected: Boolean +) + @Composable fun StateList(hasPrice: Boolean, hasTare: Boolean, hasConfirm: Boolean, hasPay: Boolean) { - Row( + val selectedBackgroundColor = MyColors.LightLightBlueGreen + val normalBackgroundColor = Color(0xFFF4F6F8) + val selectedTextColor = MyColors.BlueGreen + val normalTextColor = MyColors.Gray + val fontSize = MaterialTheme.typography.labelSmall.fontSize + val states = remember(hasPrice, hasTare, hasConfirm, hasPay) { + listOf( + PurchaseStateChip("定价", hasPrice), + PurchaseStateChip("扣皮", hasTare), + PurchaseStateChip(if (hasConfirm) "确认" else "确认售", hasConfirm), + PurchaseStateChip("支付", hasPay) + ) + } + + Box( modifier = M .fillMaxWidth() .height(38.dp) - .padding(horizontal = 4.dp, vertical = 3.dp), - verticalAlignment = Alignment.CenterVertically - ) { - StateItem("定价", hasPrice, M.weight(1f)) - StateItem("扣皮", hasTare, M.weight(1f)) - StateItem(if (hasConfirm) "确认" else "确认售", hasConfirm, M.weight(1f)) - StateItem("支付", hasPay, M.weight(1f)) - } -} + .drawWithCache { + val selectedPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = selectedTextColor.toArgb() + textSize = fontSize.toPx() + textAlign = Paint.Align.CENTER + isSubpixelText = true + } + val normalPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = normalTextColor.toArgb() + textSize = fontSize.toPx() + textAlign = Paint.Align.CENTER + isSubpixelText = true + } + val outerPaddingX = 4.dp.toPx() + val chipGap = 4.dp.toPx() + val chipPaddingX = 6.dp.toPx() + val chipHeight = 28.dp.toPx() + val chipTop = (size.height - chipHeight) / 2f + val chipWidth = (size.width - outerPaddingX * 2 - chipGap * 3) / 4f + val dotRadius = 3.dp.toPx() + val dotGap = 4.dp.toPx() + val cornerRadius = chipHeight / 2f + val maxTextWidth = (chipWidth - chipPaddingX * 2 - dotRadius * 2 - dotGap).coerceAtLeast(1f) + val clippedStates = states.map { state -> + state.copy( + text = TextUtils.ellipsize( + state.text, + if (state.selected) selectedPaint else normalPaint, + maxTextWidth, + TextUtils.TruncateAt.END + ).toString() + ) + } -@Composable -fun StateItem(text: String, isSelect: Boolean, modifier: Modifier) { - val backgroundColor = if (isSelect) MyColors.LightLightBlueGreen else Color(0xFFF4F6F8) - val borderColor = if (isSelect) MyColors.BlueGreen else MyColors.LightGray - Box( - modifier = modifier - .height(28.dp) - .padding(horizontal = 2.dp) - .background(backgroundColor, RoundedCornerShape(50)) - .padding(horizontal = 6.dp, vertical = 4.dp), - contentAlignment = Alignment.Center - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - if (isSelect) { - Box( - modifier = M - .padding(end = 4.dp) - .width(6.dp) - .height(6.dp) - .background(borderColor, CircleShape) - ) + onDrawBehind { + clippedStates.forEachIndexed { index, state -> + val left = outerPaddingX + index * (chipWidth + chipGap) + val backgroundColor = if (state.selected) selectedBackgroundColor else normalBackgroundColor + val textPaint = if (state.selected) selectedPaint else normalPaint + drawRoundRect( + color = backgroundColor, + topLeft = Offset(left, chipTop), + size = androidx.compose.ui.geometry.Size(chipWidth, chipHeight), + cornerRadius = CornerRadius(cornerRadius, cornerRadius) + ) + + val textWidth = textPaint.measureText(state.text) + val groupWidth = if (state.selected) dotRadius * 2 + dotGap + textWidth else textWidth + val textCenterX = if (state.selected) { + left + chipWidth / 2f + (dotRadius * 2 + dotGap) / 2f + } else { + left + chipWidth / 2f + } + if (state.selected) { + val dotCenterX = left + (chipWidth - groupWidth) / 2f + dotRadius + drawCircle( + color = selectedTextColor, + radius = dotRadius, + center = Offset(dotCenterX, chipTop + chipHeight / 2f) + ) + } + val baseline = chipTop + (chipHeight - textPaint.fontMetrics.ascent - textPaint.fontMetrics.descent) / 2f + drawIntoCanvas { canvas -> + canvas.nativeCanvas.drawText(state.text, textCenterX, baseline, textPaint) + } + } + } } - Text( - text = text, - color = if (isSelect) MyColors.BlueGreen else MyColors.Gray, - modifier = M.padding(horizontal = 5.dp), - fontSize = MaterialTheme.typography.labelSmall.fontSize, - ) - } - } + ) } diff --git a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/view/common/DatePickerRange.kt b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/view/common/DatePickerRange.kt index 54041f5..462b5fb 100644 --- a/app/src/main/java/com/bbitcn/f8/pad/ui/screen/view/common/DatePickerRange.kt +++ b/app/src/main/java/com/bbitcn/f8/pad/ui/screen/view/common/DatePickerRange.kt @@ -3,47 +3,47 @@ package com.bbitcn.f8.pad.ui.screen.view.common import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope 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.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.RectangleShape -import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.bbitcn.f8.pad.M import com.bbitcn.f8.pad.R -import com.bbitcn.f8.pad.base.RedPointBadge import com.bbitcn.f8.pad.base.TableHeadLine import com.bbitcn.f8.pad.ui.theme.MyColors import java.time.LocalDate +private const val RANGE_NONE = -1 +private const val RANGE_START = 0 +private const val RANGE_END = 1 +private const val RANGE_MIDDLE = 2 +private const val RANGE_SINGLE = 3 + @Preview(showBackground = true) @Composable fun DatePickerRangePV() { @@ -148,28 +148,30 @@ fun DatePickerRange( ) ) + val firstDayOffset = currentDate.withDayOfMonth(1).dayOfWeek.value % 7 LazyVerticalGrid( columns = GridCells.Fixed(7), ) { // 填充上个月的日期 - items((currentDate.withDayOfMonth(1).dayOfWeek.value - 1) % 7) { + items(firstDayOffset) { CalendarDay(0) } // 填充当前月的日期 - items(currentDate.lengthOfMonth() + 1) { day -> + items(currentDate.lengthOfMonth()) { index -> + val day = index + 1 CalendarDay( day, importantDaySet.contains(day), - rangeType = if (day == selectedStartDay) - if (selectedEndDay == -1 - || selectedEndDay == selectedStartDay - ) - 3 - else - 0 - else if (day == selectedEndDay) 1 - else if (day in selectedStartDay..selectedEndDay) 2 - else -1, + rangeType = when { + day == selectedStartDay && + (selectedEndDay == -1 || selectedEndDay == selectedStartDay) -> RANGE_SINGLE + day == selectedStartDay -> RANGE_START + day == selectedEndDay -> RANGE_END + selectedStartDay != -1 && + selectedEndDay != -1 && + day in selectedStartDay..selectedEndDay -> RANGE_MIDDLE + else -> RANGE_NONE + }, ) { if (selectedStartDay == -1 // 开始日期没选 || selectedEndDay != -1 // 已经选了结束日期 @@ -194,28 +196,80 @@ fun DatePickerRange( fun CalendarDay( day: Int, isImportant: Boolean = false, - rangeType: Int = -1,// -1:不在范围内 0:在第一天 1:在最后一天 2:在中间 3:只有一天 + rangeType: Int = RANGE_NONE, onClick: () -> Unit = {} ) { - if (day != 0) { - Box( - modifier = M.background( - color = if (rangeType != -1) MyColors.Gray else MyColors.Transparent, - shape = when (rangeType) { - -1, 3 -> CircleShape // 圆形 - 0 -> RoundedCornerShape(topStart = 50.dp, bottomStart = 50.dp) // 左半圆右矩形 - 1 -> RoundedCornerShape(topEnd = 50.dp, bottomEnd = 50.dp) // 右半圆左矩形 - 2 -> RectangleShape // 全矩形 - else -> RectangleShape - } + Box( + modifier = M + .fillMaxWidth() + .aspectRatio(1f) + .padding(vertical = 2.dp), + contentAlignment = Alignment.Center + ) { + if (day == 0) { + Spacer(modifier = M.fillMaxSize()) + } else { + val isEndpoint = + rangeType == RANGE_START || rangeType == RANGE_END || rangeType == RANGE_SINGLE + if (rangeType != RANGE_NONE && rangeType != RANGE_SINGLE) { + CalendarRangeTrack(rangeType) + } + CalText( + day.toString(), + isSelect = rangeType != RANGE_NONE, + isEndpoint = isEndpoint, + onClick = onClick ) - ) { if (isImportant) { - RedPointBadge { - CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick) - } - } else { - CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick) + Box( + modifier = M + .align(Alignment.TopEnd) + .padding(top = 6.dp, end = 7.dp) + .size(6.dp) + .background(MyColors.Red, CircleShape) + ) + } + } + } +} + +@Composable +private fun BoxScope.CalendarRangeTrack(rangeType: Int) { + val trackColor = MyColors.LightBlueGreen.copy(alpha = 0.72f) + + Row( + modifier = M + .fillMaxWidth() + .height(28.dp) + .align(Alignment.Center) + ) { + when (rangeType) { + RANGE_START -> { + Spacer(modifier = M.weight(1f)) + Box( + modifier = M + .weight(1f) + .fillMaxHeight() + .background(trackColor) + ) + } + + RANGE_END -> { + Box( + modifier = M + .weight(1f) + .fillMaxHeight() + .background(trackColor) + ) + Spacer(modifier = M.weight(1f)) + } + + RANGE_MIDDLE -> { + Box( + modifier = M + .fillMaxSize() + .background(trackColor) + ) } } } @@ -225,21 +279,40 @@ fun CalendarDay( fun CalText( day: String, isSelect: Boolean = false, + isEndpoint: Boolean = false, onClick: () -> Unit ) { Box( modifier = Modifier - .aspectRatio(1f) + .fillMaxSize() .clickable { onClick() }, contentAlignment = Alignment.Center ) { - val animatedColor = if (isSelect) MyColors.White else MyColors.Black - Text( - text = day, - fontSize = MaterialTheme.typography.bodyMedium.fontSize, - color = animatedColor - ) + val dayCircleModifier = if (isEndpoint) { + M + .size(30.dp) + .background(MyColors.BlueGreen, CircleShape) + } else { + M.size(30.dp) + } + val animatedColor = when { + isEndpoint -> MyColors.White + isSelect -> MyColors.BlueGreen + else -> MyColors.Black + } + + Box( + modifier = dayCircleModifier, + contentAlignment = Alignment.Center + ) { + Text( + text = day, + fontSize = MaterialTheme.typography.bodyMedium.fontSize, + color = animatedColor, + fontWeight = if (isSelect) FontWeight.Bold else FontWeight.Normal + ) + } } }