From b79fd0b0f4232c74f6db048afa4ae9e9e8c27606 Mon Sep 17 00:00:00 2001 From: yovinchen Date: Wed, 27 Nov 2024 09:08:01 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AE=B0=E8=B4=A6=E9=A1=B9?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E7=BB=84=E5=BB=BABug=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E6=97=B6=E9=97=B4=E9=80=89=E6=8B=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/components/DateTimePicker.kt | 136 +++++++++++--- .../bookkeeping/ui/screen/HomeScreen.kt | 166 ++++++++++++++---- 2 files changed, 247 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/components/DateTimePicker.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/components/DateTimePicker.kt index 8df2534..4cc2e21 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/ui/components/DateTimePicker.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/components/DateTimePicker.kt @@ -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("<") + } + + Text( + text = "${currentYearMonth.year}年${currentYearMonth.monthValue}月", + style = MaterialTheme.typography.titleMedium + ) + + IconButton(onClick = { + currentYearMonth = currentYearMonth.plusMonths(1) + }) { + Text(">") + } + } - DatePicker( - state = datePickerState, - showModeToggle = false - ) + // 星期标题 + 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("确定") } diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/HomeScreen.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/HomeScreen.kt index 2c22ac0..918b9d5 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/HomeScreen.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/HomeScreen.kt @@ -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() @@ -220,12 +327,11 @@ fun MonthlyStatistics( IconButton(onClick = onPreviousMonth) { 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