修复记账项日期组建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.clickable
import androidx.compose.foundation.layout.* 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.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import java.time.LocalDate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.time.YearMonth
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.temporal.TemporalAdjusters
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -54,8 +60,10 @@ fun DateTimePicker(
} }
} }
// 日期选择器对话框 // 自定义日期选择器对话框
if (showDatePicker) { if (showDatePicker) {
var currentYearMonth by remember { mutableStateOf(YearMonth.from(selectedDateTime)) }
Dialog(onDismissRequest = { showDatePicker = false }) { Dialog(onDismissRequest = { showDatePicker = false }) {
Surface( Surface(
modifier = Modifier modifier = Modifier
@ -67,19 +75,109 @@ fun DateTimePicker(
Column( Column(
modifier = Modifier.padding(16.dp) modifier = Modifier.padding(16.dp)
) { ) {
val datePickerState = rememberDatePickerState( // 年月选择器
initialSelectedDateMillis = selectedDateTime Row(
.toLocalDate() modifier = Modifier
.atStartOfDay() .fillMaxWidth()
.toInstant(java.time.ZoneOffset.UTC) .padding(bottom = 16.dp),
.toEpochMilli() horizontalArrangement = Arrangement.SpaceBetween,
) verticalAlignment = Alignment.CenterVertically
) {
IconButton(onClick = {
currentYearMonth = currentYearMonth.minusMonths(1)
}) {
Text("<")
}
Text(
text = "${currentYearMonth.year}${currentYearMonth.monthValue}",
style = MaterialTheme.typography.titleMedium
)
IconButton(onClick = {
currentYearMonth = currentYearMonth.plusMonths(1)
}) {
Text(">")
}
}
DatePicker( // 星期标题
state = datePickerState, Row(
showModeToggle = false 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( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@ -91,19 +189,7 @@ fun DateTimePicker(
} }
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Button( Button(
onClick = { onClick = { showDatePicker = false }
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
}
) { ) {
Text("确定") Text("确定")
} }

View File

@ -5,6 +5,9 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items 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.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft 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.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
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 androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.yovinchen.bookkeeping.model.BookkeepingRecord import com.yovinchen.bookkeeping.model.BookkeepingRecord
import com.yovinchen.bookkeeping.model.TransactionType import com.yovinchen.bookkeeping.model.TransactionType
@ -57,8 +62,7 @@ fun HomeScreen(
.background(MaterialTheme.colorScheme.background) .background(MaterialTheme.colorScheme.background)
) { ) {
// 顶部统计信息 // 顶部统计信息
MonthlyStatistics( MonthlyStatistics(totalIncome = totalIncome,
totalIncome = totalIncome,
totalExpense = totalExpense, totalExpense = totalExpense,
onIncomeClick = { viewModel.setSelectedRecordType(TransactionType.INCOME) }, onIncomeClick = { viewModel.setSelectedRecordType(TransactionType.INCOME) },
onExpenseClick = { viewModel.setSelectedRecordType(TransactionType.EXPENSE) }, onExpenseClick = { viewModel.setSelectedRecordType(TransactionType.EXPENSE) },
@ -66,8 +70,8 @@ fun HomeScreen(
onClearFilter = { viewModel.setSelectedRecordType(null) }, onClearFilter = { viewModel.setSelectedRecordType(null) },
selectedMonth = selectedMonth, selectedMonth = selectedMonth,
onPreviousMonth = { viewModel.setSelectedMonth(selectedMonth.minusMonths(1)) }, onPreviousMonth = { viewModel.setSelectedMonth(selectedMonth.minusMonths(1)) },
onNextMonth = { viewModel.setSelectedMonth(selectedMonth.plusMonths(1)) } onNextMonth = { viewModel.setSelectedMonth(selectedMonth.plusMonths(1)) },
) onMonthSelected = { viewModel.setSelectedMonth(it) })
// 记录列表 // 记录列表
LazyColumn( LazyColumn(
@ -155,11 +159,10 @@ fun HomeScreen(
if (showAddDialog) { if (showAddDialog) {
val selectedDateTime by viewModel.selectedDateTime.collectAsState() val selectedDateTime by viewModel.selectedDateTime.collectAsState()
val selectedCategoryType by viewModel.selectedCategoryType.collectAsState() val selectedCategoryType by viewModel.selectedCategoryType.collectAsState()
AddRecordDialog( AddRecordDialog(onDismiss = {
onDismiss = { showAddDialog = false
showAddDialog = false viewModel.resetSelectedDateTime()
viewModel.resetSelectedDateTime() },
},
onConfirm = { type, amount, category, description -> onConfirm = { type, amount, category, description ->
viewModel.addRecord(type, amount, category, description) viewModel.addRecord(type, amount, category, description)
showAddDialog = false showAddDialog = false
@ -174,15 +177,116 @@ fun HomeScreen(
// 编辑记录对话框 // 编辑记录对话框
selectedRecord?.let { record -> selectedRecord?.let { record ->
RecordEditDialog( RecordEditDialog(record = record,
record = record,
categories = categories, categories = categories,
onDismiss = { selectedRecord = null }, onDismiss = { selectedRecord = null },
onConfirm = { updatedRecord -> onConfirm = { updatedRecord ->
viewModel.updateRecord(updatedRecord) viewModel.updateRecord(updatedRecord)
selectedRecord = null 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, selectedMonth: YearMonth,
onPreviousMonth: () -> Unit, onPreviousMonth: () -> Unit,
onNextMonth: () -> Unit, onNextMonth: () -> Unit,
onMonthSelected: (YearMonth) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
var showMonthPicker by remember { mutableStateOf(false) }
Card( Card(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
@ -220,12 +327,11 @@ fun MonthlyStatistics(
IconButton(onClick = onPreviousMonth) { IconButton(onClick = onPreviousMonth) {
Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft, "上个月") Icon(Icons.AutoMirrored.Filled.KeyboardArrowLeft, "上个月")
} }
Text( Text(text = "${selectedMonth.year}${selectedMonth.monthValue}",
text = "${selectedMonth.year}${selectedMonth.monthValue}", style = MaterialTheme.typography.titleLarge,
style = MaterialTheme.typography.titleLarge modifier = Modifier.clickable { showMonthPicker = true })
)
IconButton(onClick = onNextMonth) { IconButton(onClick = onNextMonth) {
Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, "下个月") Icon(Icons.AutoMirrored.Filled.KeyboardArrowRight, "下个月")
} }
@ -234,8 +340,7 @@ fun MonthlyStatistics(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween
horizontalArrangement = Arrangement.SpaceBetween
) { ) {
// 收入统计 // 收入统计
Column(modifier = Modifier Column(modifier = Modifier
@ -243,13 +348,11 @@ fun MonthlyStatistics(
.clickable { onIncomeClick() } .clickable { onIncomeClick() }
.background( .background(
if (selectedType == TransactionType.INCOME) MaterialTheme.colorScheme.primaryContainer if (selectedType == TransactionType.INCOME) MaterialTheme.colorScheme.primaryContainer
else Color.Transparent, else Color.Transparent, RoundedCornerShape(8.dp)
RoundedCornerShape(8.dp)
) )
.padding(8.dp)) { .padding(8.dp)) {
Text( Text(
text = "收入", text = "收入", style = MaterialTheme.typography.titleMedium
style = MaterialTheme.typography.titleMedium
) )
Text( Text(
text = "¥${String.format("%.2f", totalIncome)}", text = "¥${String.format("%.2f", totalIncome)}",
@ -266,13 +369,11 @@ fun MonthlyStatistics(
.clickable { onExpenseClick() } .clickable { onExpenseClick() }
.background( .background(
if (selectedType == TransactionType.EXPENSE) MaterialTheme.colorScheme.primaryContainer if (selectedType == TransactionType.EXPENSE) MaterialTheme.colorScheme.primaryContainer
else Color.Transparent, else Color.Transparent, RoundedCornerShape(8.dp)
RoundedCornerShape(8.dp)
) )
.padding(8.dp)) { .padding(8.dp)) {
Text( Text(
text = "支出", text = "支出", style = MaterialTheme.typography.titleMedium
style = MaterialTheme.typography.titleMedium
) )
Text( Text(
text = "¥${String.format("%.2f", totalExpense)}", text = "¥${String.format("%.2f", totalExpense)}",
@ -284,14 +385,19 @@ fun MonthlyStatistics(
if (selectedType != null) { if (selectedType != null) {
TextButton( TextButton(
onClick = onClearFilter, onClick = onClearFilter, modifier = Modifier.align(Alignment.End)
modifier = Modifier.align(Alignment.End)
) { ) {
Text("清除筛选") Text("清除筛选")
} }
} }
} }
} }
if (showMonthPicker) {
MonthYearPickerDialog(selectedMonth = selectedMonth,
onMonthSelected = onMonthSelected,
onDismiss = { showMonthPicker = false })
}
} }
@Composable @Composable