优化细节

This commit is contained in:
BBIT-Kai
2026-05-29 11:34:55 +08:00
parent c5a72e938f
commit f91251adc6
2 changed files with 464 additions and 220 deletions
@@ -1,7 +1,10 @@
package com.bbitcn.f8.pad.ui.screen.mainFunc package com.bbitcn.f8.pad.ui.screen.mainFunc
import android.content.res.Configuration 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.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
import androidx.compose.foundation.clickable 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.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width 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.foundation.shape.RoundedCornerShape
import androidx.compose.material3.DatePickerDefaults import androidx.compose.material3.DatePickerDefaults
import androidx.compose.material3.DatePickerDialog import androidx.compose.material3.DatePickerDialog
@@ -31,7 +31,6 @@ import androidx.compose.material3.rememberDateRangePickerState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
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
@@ -40,18 +39,16 @@ 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
import androidx.compose.ui.draw.clip 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.geometry.Offset
import androidx.compose.ui.graphics.Color 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.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.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.tooling.preview.Preview
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.bbitcn.f8.pad.M import com.bbitcn.f8.pad.M
import com.bbitcn.f8.pad.base.MainFuncFrame import com.bbitcn.f8.pad.base.MainFuncFrame
@@ -362,10 +359,7 @@ fun InfoList(
}, },
columns = columns, columns = columns,
item = { data, index -> item = { data, index ->
val isSelected by remember(data.czSysid) { InfoItem(isSelect = data.czSysid == selectedId.value, data = data) {
derivedStateOf { data.czSysid == selectedId.value }
}
InfoItem(isSelect = isSelected, data = data) {
selectedId.value = data.czSysid selectedId.value = data.czSysid
onClick(data) onClick(data)
} }
@@ -397,55 +391,12 @@ fun InfoItem(
) )
.clickable(onClick = onClick) .clickable(onClick = onClick)
) { ) {
Row( PurchaseCardHeader(
verticalAlignment = Alignment.CenterVertically, isSelect = isSelect,
modifier = M name = data.nhName,
.fillMaxWidth() billCode = data.billCode.toString(),
.height(32.dp) billState = data.billState
.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
)
}
}
PurchaseSummaryTable( PurchaseSummaryTable(
visibleItems = visibleItems, visibleItems = visibleItems,
hiddenCount = hiddenCount 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<String>,
val rows: List<List<String>>,
val moreText: String?
)
@Composable @Composable
private fun PurchaseSummaryTable( private fun PurchaseSummaryTable(
visibleItems: List<PurchaseDataResponse.Data.ChengZhongItemSum>, visibleItems: List<PurchaseDataResponse.Data.ChengZhongItemSum>,
hiddenCount: Int hiddenCount: Int
) { ) {
val textMeasurer = rememberTextMeasurer()
val bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize val bodyFontSize = MaterialTheme.typography.bodyMedium.fontSize
val labelFontSize = MaterialTheme.typography.labelSmall.fontSize val labelFontSize = MaterialTheme.typography.labelSmall.fontSize
val headerBackground = MyColors.White val headerBackground = MyColors.White
val headerTextColor = MyColors.Black val headerTextColor = MyColors.Black
val rowBackground = MyColors.White val rowBackground = MyColors.White
val rowTextColor = MyColors.Black
val moreTextColor = MyColors.Gray val moreTextColor = MyColors.Gray
val headers = remember { val headers = remember {
listOf("茧别", "毛重", "皮重", "扣重", "净重", "单价") 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 modifier = M
.fillMaxWidth() .fillMaxWidth()
.height(88.dp) .height(88.dp)
) { .drawWithCache {
val headerHeight = 28.dp.toPx() val headerPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
val rowHeight = 24.dp.toPx() color = headerTextColor.toArgb()
val cellWidth = size.width / headers.size textSize = bodyFontSize.toPx()
val cellConstraints = Constraints.fixedWidth(cellWidth.toInt().coerceAtLeast(1)) textAlign = Paint.Align.CENTER
isFakeBoldText = true
fun drawCell(text: String, column: Int, top: Float, height: Float, style: TextStyle) { isSubpixelText = true
val result = textMeasurer.measure( }
text = AnnotatedString(text), val rowPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
style = style, color = rowTextColor.toArgb()
overflow = TextOverflow.Ellipsis, textSize = bodyFontSize.toPx()
maxLines = 1, textAlign = Paint.Align.CENTER
constraints = cellConstraints isSubpixelText = true
) }
drawText( val morePaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
textLayoutResult = result, color = moreTextColor.toArgb()
topLeft = Offset( textSize = labelFontSize.toPx()
x = column * cellWidth, textAlign = Paint.Align.LEFT
y = top + (height - result.size.height) / 2f 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) onDrawBehind {
headers.forEachIndexed { index, text -> fun drawNativeText(
drawCell(text, index, 0f, headerHeight, headerStyle) 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 -> fun drawCell(text: String, column: Int, top: Float, height: Float, paint: TextPaint) {
val rowTop = headerHeight + rowIndex * rowHeight drawNativeText(
drawRect( text = text,
color = rowBackground, x = column * cellWidth + cellWidth / 2f,
topLeft = Offset(0f, rowTop), top = top,
size = androidx.compose.ui.geometry.Size(size.width, rowHeight) height = height,
) paint = paint
row.forEachIndexed { columnIndex, text -> )
drawCell(text, columnIndex, rowTop, rowHeight, rowStyle) }
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 @Composable
fun StateList(hasPrice: Boolean, hasTare: Boolean, hasConfirm: Boolean, hasPay: Boolean) { 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 modifier = M
.fillMaxWidth() .fillMaxWidth()
.height(38.dp) .height(38.dp)
.padding(horizontal = 4.dp, vertical = 3.dp), .drawWithCache {
verticalAlignment = Alignment.CenterVertically val selectedPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
) { color = selectedTextColor.toArgb()
StateItem("定价", hasPrice, M.weight(1f)) textSize = fontSize.toPx()
StateItem("扣皮", hasTare, M.weight(1f)) textAlign = Paint.Align.CENTER
StateItem(if (hasConfirm) "确认" else "确认售", hasConfirm, M.weight(1f)) isSubpixelText = true
StateItem("支付", hasPay, M.weight(1f)) }
} 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 onDrawBehind {
fun StateItem(text: String, isSelect: Boolean, modifier: Modifier) { clippedStates.forEachIndexed { index, state ->
val backgroundColor = if (isSelect) MyColors.LightLightBlueGreen else Color(0xFFF4F6F8) val left = outerPaddingX + index * (chipWidth + chipGap)
val borderColor = if (isSelect) MyColors.BlueGreen else MyColors.LightGray val backgroundColor = if (state.selected) selectedBackgroundColor else normalBackgroundColor
Box( val textPaint = if (state.selected) selectedPaint else normalPaint
modifier = modifier drawRoundRect(
.height(28.dp) color = backgroundColor,
.padding(horizontal = 2.dp) topLeft = Offset(left, chipTop),
.background(backgroundColor, RoundedCornerShape(50)) size = androidx.compose.ui.geometry.Size(chipWidth, chipHeight),
.padding(horizontal = 6.dp, vertical = 4.dp), cornerRadius = CornerRadius(cornerRadius, cornerRadius)
contentAlignment = Alignment.Center )
) {
Row(verticalAlignment = Alignment.CenterVertically) { val textWidth = textPaint.measureText(state.text)
if (isSelect) { val groupWidth = if (state.selected) dotRadius * 2 + dotGap + textWidth else textWidth
Box( val textCenterX = if (state.selected) {
modifier = M left + chipWidth / 2f + (dotRadius * 2 + dotGap) / 2f
.padding(end = 4.dp) } else {
.width(6.dp) left + chipWidth / 2f
.height(6.dp) }
.background(borderColor, CircleShape) 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,
)
}
}
} }
@@ -3,47 +3,47 @@ package com.bbitcn.f8.pad.ui.screen.view.common
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
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.aspectRatio 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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 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.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable 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.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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
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.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.bbitcn.f8.pad.M import com.bbitcn.f8.pad.M
import com.bbitcn.f8.pad.R 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.base.TableHeadLine
import com.bbitcn.f8.pad.ui.theme.MyColors import com.bbitcn.f8.pad.ui.theme.MyColors
import java.time.LocalDate 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) @Preview(showBackground = true)
@Composable @Composable
fun DatePickerRangePV() { fun DatePickerRangePV() {
@@ -148,28 +148,30 @@ fun DatePickerRange(
) )
) )
val firstDayOffset = currentDate.withDayOfMonth(1).dayOfWeek.value % 7
LazyVerticalGrid( LazyVerticalGrid(
columns = GridCells.Fixed(7), columns = GridCells.Fixed(7),
) { ) {
// 填充上个月的日期 // 填充上个月的日期
items((currentDate.withDayOfMonth(1).dayOfWeek.value - 1) % 7) { items(firstDayOffset) {
CalendarDay(0) CalendarDay(0)
} }
// 填充当前月的日期 // 填充当前月的日期
items(currentDate.lengthOfMonth() + 1) { day -> items(currentDate.lengthOfMonth()) { index ->
val day = index + 1
CalendarDay( CalendarDay(
day, day,
importantDaySet.contains(day), importantDaySet.contains(day),
rangeType = if (day == selectedStartDay) rangeType = when {
if (selectedEndDay == -1 day == selectedStartDay &&
|| selectedEndDay == selectedStartDay (selectedEndDay == -1 || selectedEndDay == selectedStartDay) -> RANGE_SINGLE
) day == selectedStartDay -> RANGE_START
3 day == selectedEndDay -> RANGE_END
else selectedStartDay != -1 &&
0 selectedEndDay != -1 &&
else if (day == selectedEndDay) 1 day in selectedStartDay..selectedEndDay -> RANGE_MIDDLE
else if (day in selectedStartDay..selectedEndDay) 2 else -> RANGE_NONE
else -1, },
) { ) {
if (selectedStartDay == -1 // 开始日期没选 if (selectedStartDay == -1 // 开始日期没选
|| selectedEndDay != -1 // 已经选了结束日期 || selectedEndDay != -1 // 已经选了结束日期
@@ -194,28 +196,80 @@ fun DatePickerRange(
fun CalendarDay( fun CalendarDay(
day: Int, day: Int,
isImportant: Boolean = false, isImportant: Boolean = false,
rangeType: Int = -1,// -1:不在范围内 0:在第一天 1:在最后一天 2:在中间 3:只有一天 rangeType: Int = RANGE_NONE,
onClick: () -> Unit = {} onClick: () -> Unit = {}
) { ) {
if (day != 0) { Box(
Box( modifier = M
modifier = M.background( .fillMaxWidth()
color = if (rangeType != -1) MyColors.Gray else MyColors.Transparent, .aspectRatio(1f)
shape = when (rangeType) { .padding(vertical = 2.dp),
-1, 3 -> CircleShape // 圆形 contentAlignment = Alignment.Center
0 -> RoundedCornerShape(topStart = 50.dp, bottomStart = 50.dp) // 左半圆右矩形 ) {
1 -> RoundedCornerShape(topEnd = 50.dp, bottomEnd = 50.dp) // 右半圆左矩形 if (day == 0) {
2 -> RectangleShape // 全矩形 Spacer(modifier = M.fillMaxSize())
else -> RectangleShape } 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) { if (isImportant) {
RedPointBadge { Box(
CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick) modifier = M
} .align(Alignment.TopEnd)
} else { .padding(top = 6.dp, end = 7.dp)
CalText(day.toString(), isSelect = rangeType != -1, onClick = onClick) .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( fun CalText(
day: String, day: String,
isSelect: Boolean = false, isSelect: Boolean = false,
isEndpoint: Boolean = false,
onClick: () -> Unit onClick: () -> Unit
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
.aspectRatio(1f) .fillMaxSize()
.clickable { .clickable {
onClick() onClick()
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
val animatedColor = if (isSelect) MyColors.White else MyColors.Black val dayCircleModifier = if (isEndpoint) {
Text( M
text = day, .size(30.dp)
fontSize = MaterialTheme.typography.bodyMedium.fontSize, .background(MyColors.BlueGreen, CircleShape)
color = animatedColor } 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
)
}
} }
} }