修复记账项日期组建Bug
新增首页时间选择
This commit is contained in:
parent
3db1f5bfd2
commit
b79fd0b0f4
@ -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("确定")
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user