修复记账项日期组建Bug

新增首页时间选择
This commit is contained in:
yovinchen 2024-11-27 09:08:01 +08:00
parent 3db1f5bfd2
commit b79fd0b0f4
2 changed files with 247 additions and 55 deletions

View File

@ -2,14 +2,20 @@ package com.yovinchen.bookkeeping.ui.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.YearMonth
import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -54,8 +60,10 @@ fun DateTimePicker(
}
}
// 日期选择器对话框
// 自定义日期选择器对话框
if (showDatePicker) {
var currentYearMonth by remember { mutableStateOf(YearMonth.from(selectedDateTime)) }
Dialog(onDismissRequest = { showDatePicker = false }) {
Surface(
modifier = Modifier
@ -67,19 +75,109 @@ fun DateTimePicker(
Column(
modifier = Modifier.padding(16.dp)
) {
val datePickerState = rememberDatePickerState(
initialSelectedDateMillis = selectedDateTime
.toLocalDate()
.atStartOfDay()
.toInstant(java.time.ZoneOffset.UTC)
.toEpochMilli()
)
// 年月选择器
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = {
currentYearMonth = currentYearMonth.minusMonths(1)
}) {
Text("<")
}
DatePicker(
state = datePickerState,
showModeToggle = false
)
Text(
text = "${currentYearMonth.year}${currentYearMonth.monthValue}",
style = MaterialTheme.typography.titleMedium
)
IconButton(onClick = {
currentYearMonth = currentYearMonth.plusMonths(1)
}) {
Text(">")
}
}
// 星期标题
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
listOf("", "", "", "", "", "", "").forEach { day ->
Text(
text = day,
modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
Spacer(modifier = Modifier.height(8.dp))
// 日期网格
val dates = remember(currentYearMonth) {
val firstDayOfMonth = currentYearMonth.atDay(1)
val lastDayOfMonth = currentYearMonth.atEndOfMonth()
val firstDayOfGrid = firstDayOfMonth.minusDays(firstDayOfMonth.dayOfWeek.value.toLong())
buildList {
var currentDate = firstDayOfGrid
while (currentDate.isBefore(lastDayOfMonth.plusDays(7))) {
add(currentDate)
currentDate = currentDate.plusDays(1)
}
}
}
LazyVerticalGrid(
columns = GridCells.Fixed(7),
modifier = Modifier.height(240.dp)
) {
items(dates.size) { index ->
val date = dates[index]
val isSelected = date.isEqual(selectedDateTime.toLocalDate())
val isCurrentMonth = date.monthValue == currentYearMonth.monthValue
Surface(
modifier = Modifier
.aspectRatio(1f)
.padding(2.dp)
.clickable {
val newDateTime = selectedDateTime
.withYear(date.year)
.withMonth(date.monthValue)
.withDayOfMonth(date.dayOfMonth)
onDateTimeSelected(newDateTime)
showDatePicker = false
},
shape = MaterialTheme.shapes.small,
color = when {
isSelected -> MaterialTheme.colorScheme.primary
else -> MaterialTheme.colorScheme.surface
}
) {
Text(
text = date.dayOfMonth.toString(),
modifier = Modifier
.fillMaxSize()
.padding(4.dp),
textAlign = TextAlign.Center,
color = when {
isSelected -> MaterialTheme.colorScheme.onPrimary
!isCurrentMonth -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.4f)
else -> MaterialTheme.colorScheme.onSurface
}
)
}
}
}
// 按钮行
Row(
modifier = Modifier
.fillMaxWidth()
@ -91,19 +189,7 @@ fun DateTimePicker(
}
Spacer(modifier = Modifier.width(8.dp))
Button(
onClick = {
datePickerState.selectedDateMillis?.let { millis ->
val newDate = java.time.Instant.ofEpochMilli(millis)
.atZone(java.time.ZoneOffset.UTC)
.toLocalDate()
val newDateTime = newDate.atTime(
selectedDateTime.hour,
selectedDateTime.minute
)
onDateTimeSelected(newDateTime)
}
showDatePicker = false
}
onClick = { showDatePicker = false }
) {
Text("确定")
}

View File

@ -5,6 +5,9 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
@ -16,8 +19,10 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel
import com.yovinchen.bookkeeping.model.BookkeepingRecord
import com.yovinchen.bookkeeping.model.TransactionType
@ -57,8 +62,7 @@ fun HomeScreen(
.background(MaterialTheme.colorScheme.background)
) {
// 顶部统计信息
MonthlyStatistics(
totalIncome = totalIncome,
MonthlyStatistics(totalIncome = totalIncome,
totalExpense = totalExpense,
onIncomeClick = { viewModel.setSelectedRecordType(TransactionType.INCOME) },
onExpenseClick = { viewModel.setSelectedRecordType(TransactionType.EXPENSE) },
@ -66,8 +70,8 @@ fun HomeScreen(
onClearFilter = { viewModel.setSelectedRecordType(null) },
selectedMonth = selectedMonth,
onPreviousMonth = { viewModel.setSelectedMonth(selectedMonth.minusMonths(1)) },
onNextMonth = { viewModel.setSelectedMonth(selectedMonth.plusMonths(1)) }
)
onNextMonth = { viewModel.setSelectedMonth(selectedMonth.plusMonths(1)) },
onMonthSelected = { viewModel.setSelectedMonth(it) })
// 记录列表
LazyColumn(
@ -155,11 +159,10 @@ fun HomeScreen(
if (showAddDialog) {
val selectedDateTime by viewModel.selectedDateTime.collectAsState()
val selectedCategoryType by viewModel.selectedCategoryType.collectAsState()
AddRecordDialog(
onDismiss = {
showAddDialog = false
viewModel.resetSelectedDateTime()
},
AddRecordDialog(onDismiss = {
showAddDialog = false
viewModel.resetSelectedDateTime()
},
onConfirm = { type, amount, category, description ->
viewModel.addRecord(type, amount, category, description)
showAddDialog = false
@ -174,15 +177,116 @@ fun HomeScreen(
// 编辑记录对话框
selectedRecord?.let { record ->
RecordEditDialog(
record = record,
RecordEditDialog(record = record,
categories = categories,
onDismiss = { selectedRecord = null },
onConfirm = { updatedRecord ->
viewModel.updateRecord(updatedRecord)
selectedRecord = null
})
}
}
}
@Composable
fun MonthYearPickerDialog(
selectedMonth: YearMonth, onMonthSelected: (YearMonth) -> Unit, onDismiss: () -> Unit
) {
var currentYearMonth by remember { mutableStateOf(selectedMonth) }
Dialog(onDismissRequest = onDismiss) {
Surface(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
shape = MaterialTheme.shapes.extraLarge,
tonalElevation = 6.dp
) {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(
text = "选择年月",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.padding(bottom = 16.dp)
)
// 年份选择
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = {
currentYearMonth = currentYearMonth.minusYears(1)
}) {
Text("<")
}
Text(
text = "${currentYearMonth.year}",
style = MaterialTheme.typography.titleMedium
)
IconButton(onClick = {
currentYearMonth = currentYearMonth.plusYears(1)
}) {
Text(">")
}
}
)
Spacer(modifier = Modifier.height(16.dp))
// 月份网格
LazyVerticalGrid(
columns = GridCells.Fixed(3), modifier = Modifier.height(200.dp)
) {
items(12) { index ->
val month = index + 1
val isSelected = month == currentYearMonth.monthValue
Surface(
modifier = Modifier
.padding(4.dp)
.aspectRatio(1.5f)
.clickable {
currentYearMonth = YearMonth.of(currentYearMonth.year, month)
},
shape = MaterialTheme.shapes.small,
color = if (isSelected) MaterialTheme.colorScheme.primary
else MaterialTheme.colorScheme.surface
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Text(
text = "${month}",
color = if (isSelected) MaterialTheme.colorScheme.onPrimary
else MaterialTheme.colorScheme.onSurface
)
}
}
}
}
// 按钮行
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp),
horizontalArrangement = Arrangement.End
) {
TextButton(onClick = onDismiss) {
Text("取消")
}
Spacer(modifier = Modifier.width(8.dp))
Button(onClick = {
onMonthSelected(currentYearMonth)
onDismiss()
}) {
Text("确定")
}
}
}
}
}
}
@ -198,8 +302,11 @@ fun MonthlyStatistics(
selectedMonth: YearMonth,
onPreviousMonth: () -> Unit,
onNextMonth: () -> Unit,
onMonthSelected: (YearMonth) -> Unit,
modifier: Modifier = Modifier
) {
var showMonthPicker by remember { mutableStateOf(false) }
Card(
modifier = modifier
.fillMaxWidth()
@ -221,10 +328,9 @@ fun MonthlyStatistics(
Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft, "上个月")
}
Text(
text = "${selectedMonth.year}${selectedMonth.monthValue}",
style = MaterialTheme.typography.titleLarge
)
Text(text = "${selectedMonth.year}${selectedMonth.monthValue}",
style = MaterialTheme.typography.titleLarge,
modifier = Modifier.clickable { showMonthPicker = true })
IconButton(onClick = onNextMonth) {
Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, "下个月")
@ -234,8 +340,7 @@ fun MonthlyStatistics(
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
) {
// 收入统计
Column(modifier = Modifier
@ -243,13 +348,11 @@ fun MonthlyStatistics(
.clickable { onIncomeClick() }
.background(
if (selectedType == TransactionType.INCOME) MaterialTheme.colorScheme.primaryContainer
else Color.Transparent,
RoundedCornerShape(8.dp)
else Color.Transparent, RoundedCornerShape(8.dp)
)
.padding(8.dp)) {
Text(
text = "收入",
style = MaterialTheme.typography.titleMedium
text = "收入", style = MaterialTheme.typography.titleMedium
)
Text(
text = "¥${String.format("%.2f", totalIncome)}",
@ -266,13 +369,11 @@ fun MonthlyStatistics(
.clickable { onExpenseClick() }
.background(
if (selectedType == TransactionType.EXPENSE) MaterialTheme.colorScheme.primaryContainer
else Color.Transparent,
RoundedCornerShape(8.dp)
else Color.Transparent, RoundedCornerShape(8.dp)
)
.padding(8.dp)) {
Text(
text = "支出",
style = MaterialTheme.typography.titleMedium
text = "支出", style = MaterialTheme.typography.titleMedium
)
Text(
text = "¥${String.format("%.2f", totalExpense)}",
@ -284,14 +385,19 @@ fun MonthlyStatistics(
if (selectedType != null) {
TextButton(
onClick = onClearFilter,
modifier = Modifier.align(Alignment.End)
onClick = onClearFilter, modifier = Modifier.align(Alignment.End)
) {
Text("清除筛选")
}
}
}
}
if (showMonthPicker) {
MonthYearPickerDialog(selectedMonth = selectedMonth,
onMonthSelected = onMonthSelected,
onDismiss = { showMonthPicker = false })
}
}
@Composable