From 380fdd558915696ad9e5541c47f7863024a5bfdb Mon Sep 17 00:00:00 2001 From: yovinchen Date: Thu, 28 Nov 2024 16:14:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=88=90=E5=91=98=E5=88=86=E6=9E=90?= =?UTF-8?q?=E4=B8=8E=E8=AF=A6=E6=83=85=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 新增成员详情页面,按天分组显示记录 2. 优化分析页面,支持分类/成员视图切换 3. 使用 rememberSaveable 保持视图模式状态 4. 改进 UI 布局和交互体验 --- .../bookkeeping/data/BookkeepingDao.kt | 11 ++ .../com/yovinchen/bookkeeping/data/Record.kt | 3 +- .../ui/components/CategoryPieChart.kt | 28 ++-- .../ui/navigation/MainNavigation.kt | 62 +++++--- .../bookkeeping/ui/screen/AnalysisScreen.kt | 101 +++++++++--- .../ui/screen/MemberDetailScreen.kt | 149 ++++++++++++++++++ .../viewmodel/AnalysisViewModel.kt | 45 +++++- .../viewmodel/MemberDetailViewModel.kt | 34 ++++ 8 files changed, 372 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/com/yovinchen/bookkeeping/ui/screen/MemberDetailScreen.kt create mode 100644 app/src/main/java/com/yovinchen/bookkeeping/viewmodel/MemberDetailViewModel.kt diff --git a/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDao.kt b/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDao.kt index 9d5bba7..749ab2c 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDao.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/data/BookkeepingDao.kt @@ -38,6 +38,17 @@ interface BookkeepingDao { yearMonth: String ): Flow> + @Query(""" + SELECT * FROM bookkeeping_records + WHERE memberId IN (SELECT id FROM members WHERE name = :memberName) + AND strftime('%Y-%m', datetime(date/1000, 'unixepoch')) = :yearMonth + ORDER BY date DESC + """) + fun getRecordsByMemberAndMonth( + memberName: String, + yearMonth: String + ): Flow> + @Insert suspend fun insertRecord(record: BookkeepingRecord): Long diff --git a/app/src/main/java/com/yovinchen/bookkeeping/data/Record.kt b/app/src/main/java/com/yovinchen/bookkeeping/data/Record.kt index d37049d..47c7366 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/data/Record.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/data/Record.kt @@ -12,5 +12,6 @@ data class Record( val category: String, val description: String, val dateTime: LocalDateTime = LocalDateTime.now(), - val isExpense: Boolean = true + val isExpense: Boolean = true, + val member: String = "Default" ) diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/components/CategoryPieChart.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/components/CategoryPieChart.kt index a120c8a..7baae93 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/ui/components/CategoryPieChart.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/components/CategoryPieChart.kt @@ -2,16 +2,14 @@ package com.yovinchen.bookkeeping.ui.components import android.graphics.Color as AndroidColor import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.* import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import com.github.mikephil.charting.charts.PieChart -import com.github.mikephil.charting.components.Legend import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.PieData import com.github.mikephil.charting.data.PieDataSet @@ -24,11 +22,13 @@ import com.github.mikephil.charting.utils.ColorTemplate @Composable fun CategoryPieChart( categoryData: List>, + memberData: List>, + currentViewMode: Boolean = false, // false 为分类视图,true 为成员视图 modifier: Modifier = Modifier, onCategoryClick: (String) -> Unit = {} ) { - isSystemInDarkTheme() val textColor = MaterialTheme.colorScheme.onSurface.toArgb() + val data = if (currentViewMode) memberData else categoryData AndroidView( modifier = modifier @@ -39,23 +39,15 @@ fun CategoryPieChart( description.isEnabled = false setUsePercentValues(true) setDrawEntryLabels(true) - - // 禁用图例显示 legend.isEnabled = false - isDrawHoleEnabled = true holeRadius = 40f setHoleColor(AndroidColor.TRANSPARENT) setTransparentCircleRadius(45f) - - // 设置标签文字颜色 setEntryLabelColor(textColor) setEntryLabelTextSize(12f) - - // 设置中心文字颜色跟随主题 setCenterTextColor(textColor) - // 添加点击事件监听器 setOnChartValueSelectedListener(object : OnChartValueSelectedListener { override fun onValueSelected(e: Entry?, h: Highlight?) { e?.let { @@ -65,18 +57,16 @@ fun CategoryPieChart( } } - override fun onNothingSelected() { - // 不需要处理 - } + override fun onNothingSelected() {} }) } }, update = { chart -> - val entries = categoryData.map { (category, amount) -> - PieEntry(amount, category) + val entries = data.map { (label, amount) -> + PieEntry(amount, label) } - val dataSet = PieDataSet(entries, "").apply { // 将标题设为空字符串 + val dataSet = PieDataSet(entries, "").apply { colors = ColorTemplate.MATERIAL_COLORS.toList() valueTextSize = 14f valueFormatter = PercentFormatter(chart) diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/navigation/MainNavigation.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/navigation/MainNavigation.kt index ee73056..ea0bee9 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/ui/navigation/MainNavigation.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/navigation/MainNavigation.kt @@ -6,6 +6,7 @@ import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.List import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.outlined.Analytics +import androidx.compose.material.icons.outlined.Home import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.NavigationBar @@ -24,28 +25,33 @@ import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument import com.yovinchen.bookkeeping.model.ThemeMode -import com.yovinchen.bookkeeping.ui.screen.AnalysisScreen -import com.yovinchen.bookkeeping.ui.screen.CategoryDetailScreen -import com.yovinchen.bookkeeping.ui.screen.HomeScreen -import com.yovinchen.bookkeeping.ui.screen.SettingsScreen +import com.yovinchen.bookkeeping.ui.screen.* import java.time.YearMonth import java.time.format.DateTimeFormatter sealed class Screen( val route: String, - val icon: ImageVector, - val label: String + val icon: ImageVector? = null, + val label: String? = null ) { - data object Home : Screen("home", Icons.Default.Home, "主页") + data object Home : Screen("home", Icons.Outlined.Home, "首页") data object Analysis : Screen("analysis", Icons.Outlined.Analytics, "分析") data object Settings : Screen("settings", Icons.Default.Settings, "设置") data object CategoryDetail : Screen( - "category_detail/{category}/{yearMonth}", + "category/{category}/{yearMonth}", Icons.Default.List, "分类详情" ) { - fun createRoute(category: String, yearMonth: String) = - "category_detail/$category/$yearMonth" + fun createRoute(category: String, yearMonth: YearMonth): String = + "category/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}" + } + data object MemberDetail : Screen( + "member/{memberName}/{yearMonth}", + Icons.Default.List, + "成员详情" + ) { + fun createRoute(memberName: String, yearMonth: YearMonth): String = + "member/$memberName/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}" } } @@ -69,8 +75,8 @@ fun MainNavigation( Screen.Settings ).forEach { screen -> NavigationBarItem( - icon = { Icon(screen.icon, contentDescription = screen.label) }, - label = { Text(screen.label) }, + icon = { Icon(screen.icon!!, contentDescription = screen.label) }, + label = { Text(screen.label!!) }, selected = currentRoute == screen.route, onClick = { navController.navigate(screen.route) { @@ -92,14 +98,18 @@ fun MainNavigation( modifier = Modifier.padding(innerPadding) ) { composable(Screen.Home.route) { HomeScreen() } + composable(Screen.Analysis.route) { AnalysisScreen( - onNavigateToCategoryDetail = { category, month -> - val monthStr = month.format(DateTimeFormatter.ofPattern("yyyy-MM")) - navController.navigate(Screen.CategoryDetail.createRoute(category, monthStr)) + onNavigateToCategoryDetail = { category, yearMonth -> + navController.navigate(Screen.CategoryDetail.createRoute(category, yearMonth)) + }, + onNavigateToMemberDetail = { memberName, yearMonth -> + navController.navigate(Screen.MemberDetail.createRoute(memberName, yearMonth)) } ) } + composable(Screen.Settings.route) { SettingsScreen( currentTheme = currentTheme, @@ -115,15 +125,31 @@ fun MainNavigation( ) ) { backStackEntry -> val category = backStackEntry.arguments?.getString("category") ?: return@composable - val yearMonth = YearMonth.parse( - backStackEntry.arguments?.getString("yearMonth") ?: return@composable - ) + val yearMonthStr = backStackEntry.arguments?.getString("yearMonth") ?: return@composable + val yearMonth = YearMonth.parse(yearMonthStr, DateTimeFormatter.ofPattern("yyyy-MM")) CategoryDetailScreen( category = category, month = yearMonth, onBack = { navController.popBackStack() } ) } + + composable( + route = Screen.MemberDetail.route, + arguments = listOf( + navArgument("memberName") { type = NavType.StringType }, + navArgument("yearMonth") { type = NavType.StringType } + ) + ) { backStackEntry -> + val memberName = backStackEntry.arguments?.getString("memberName") ?: return@composable + val yearMonthStr = backStackEntry.arguments?.getString("yearMonth") ?: return@composable + val yearMonth = YearMonth.parse(yearMonthStr, DateTimeFormatter.ofPattern("yyyy-MM")) + MemberDetailScreen( + memberName = memberName, + yearMonth = yearMonth, + onNavigateBack = { navController.popBackStack() } + ) + } } } } diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/AnalysisScreen.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/AnalysisScreen.kt index 20ed14c..01ffd31 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/AnalysisScreen.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/AnalysisScreen.kt @@ -3,8 +3,11 @@ package com.yovinchen.bookkeeping.ui.screen import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material3.* import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -18,17 +21,25 @@ import com.yovinchen.bookkeeping.viewmodel.AnalysisViewModel import java.time.YearMonth import java.time.format.DateTimeFormatter +enum class ViewMode { + CATEGORY, MEMBER +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun AnalysisScreen( - onNavigateToCategoryDetail: (String, YearMonth) -> Unit + onNavigateToCategoryDetail: (String, YearMonth) -> Unit, + onNavigateToMemberDetail: (String, YearMonth) -> Unit ) { val viewModel: AnalysisViewModel = viewModel() val selectedMonth by viewModel.selectedMonth.collectAsState() val selectedAnalysisType by viewModel.selectedAnalysisType.collectAsState() val categoryStats by viewModel.categoryStats.collectAsState() + val memberStats by viewModel.memberStats.collectAsState() var showMonthPicker by remember { mutableStateOf(false) } + var showViewModeMenu by remember { mutableStateOf(false) } + var currentViewMode by rememberSaveable { mutableStateOf(ViewMode.CATEGORY) } Scaffold { padding -> Column( @@ -36,17 +47,54 @@ fun AnalysisScreen( .fillMaxSize() .padding(padding) ) { - // 月份选择器和类型切换 + // 时间选择按钮行 Row( modifier = Modifier .fillMaxWidth() - .padding(16.dp), + .padding(horizontal = 16.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + Button(onClick = { showMonthPicker = true }) { + Text(selectedMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))) + } + } + + // 分析类型和视图模式选择行 + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { - // 月份选择按钮 - Button(onClick = { showMonthPicker = true }) { - Text(selectedMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))) + // 分类/成员切换下拉菜单 + Box { + Button( + onClick = { showViewModeMenu = true } + ) { + Text(if (currentViewMode == ViewMode.CATEGORY) "分类" else "成员") + Icon(Icons.Default.ArrowDropDown, "切换视图") + } + DropdownMenu( + expanded = showViewModeMenu, + onDismissRequest = { showViewModeMenu = false } + ) { + DropdownMenuItem( + text = { Text("分类") }, + onClick = { + currentViewMode = ViewMode.CATEGORY + showViewModeMenu = false + } + ) + DropdownMenuItem( + text = { Text("成员") }, + onClick = { + currentViewMode = ViewMode.MEMBER + showViewModeMenu = false + } + ) + } } // 类型切换 @@ -80,37 +128,48 @@ fun AnalysisScreen( item { CategoryPieChart( categoryData = categoryStats.map { Pair(it.category, it.percentage.toFloat()) }, + memberData = memberStats.map { Pair(it.category, it.percentage.toFloat()) }, + currentViewMode = currentViewMode == ViewMode.MEMBER, modifier = Modifier .fillMaxWidth() .height(200.dp) .padding(bottom = 16.dp), onCategoryClick = { category -> - onNavigateToCategoryDetail(category, selectedMonth) + if (currentViewMode == ViewMode.CATEGORY) { + onNavigateToCategoryDetail(category, selectedMonth) + } else { + onNavigateToMemberDetail(category, selectedMonth) + } } ) } } - // 添加分类统计列表项目 - items(categoryStats) { stat -> + // 添加统计列表项目 + items(if (currentViewMode == ViewMode.CATEGORY) categoryStats else memberStats) { stat -> CategoryStatItem( stat = stat, - onClick = { onNavigateToCategoryDetail(stat.category, selectedMonth) } + onClick = { + if (currentViewMode == ViewMode.CATEGORY) { + onNavigateToCategoryDetail(stat.category, selectedMonth) + } else { + onNavigateToMemberDetail(stat.category, selectedMonth) + } + } ) } } - } - // 月份选择器对话框 - if (showMonthPicker) { - MonthYearPicker( - selectedMonth = selectedMonth, - onMonthSelected = { - viewModel.setSelectedMonth(it) - showMonthPicker = false - }, - onDismiss = { showMonthPicker = false } - ) + if (showMonthPicker) { + MonthYearPicker( + selectedMonth = selectedMonth, + onMonthSelected = { month -> + viewModel.setSelectedMonth(month) + showMonthPicker = false + }, + onDismiss = { showMonthPicker = false } + ) + } } } } diff --git a/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/MemberDetailScreen.kt b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/MemberDetailScreen.kt new file mode 100644 index 0000000..99f6129 --- /dev/null +++ b/app/src/main/java/com/yovinchen/bookkeeping/ui/screen/MemberDetailScreen.kt @@ -0,0 +1,149 @@ +package com.yovinchen.bookkeeping.ui.screen + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.yovinchen.bookkeeping.model.BookkeepingRecord +import com.yovinchen.bookkeeping.viewmodel.MemberDetailViewModel +import java.text.NumberFormat +import java.text.SimpleDateFormat +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import java.util.* + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun MemberDetailScreen( + memberName: String, + yearMonth: YearMonth, + onNavigateBack: () -> Unit, + viewModel: MemberDetailViewModel = viewModel() +) { + val records by viewModel.memberRecords.collectAsState(initial = emptyList()) + val totalAmount by viewModel.totalAmount.collectAsState(initial = 0.0) + + LaunchedEffect(memberName, yearMonth) { + viewModel.loadMemberRecords(memberName, yearMonth) + } + + val groupedRecords = remember(records) { + records.groupBy { record -> + SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(record.date) + }.toSortedMap(reverseOrder()) + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text("$memberName - ${yearMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月"))}") + }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon(Icons.Default.ArrowBack, "返回") + } + } + ) + } + ) { padding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(padding) + ) { + // 总金额显示 + Card( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + Column( + modifier = Modifier + .padding(16.dp) + ) { + Text( + text = "总支出", + style = MaterialTheme.typography.titleMedium + ) + Text( + text = NumberFormat.getCurrencyInstance(Locale.CHINA) + .format(totalAmount), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold + ) + } + } + + // 按日期分组的记录列表 + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + groupedRecords.forEach { (date, dayRecords) -> + item { + Text( + text = date, + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(vertical = 8.dp) + ) + } + items(dayRecords.sortedByDescending { it.date }) { record -> + RecordItem(record = record) + } + } + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun RecordItem(record: BookkeepingRecord) { + Card( + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = record.category, + style = MaterialTheme.typography.titleMedium + ) + if (record.description.isNotBlank()) { + Text( + text = record.description, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Text( + text = SimpleDateFormat("HH:mm", Locale.getDefault()).format(record.date), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + Text( + text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(record.amount), + style = MaterialTheme.typography.titleMedium, + color = if (record.amount < 0) MaterialTheme.colorScheme.error else MaterialTheme.colorScheme.primary + ) + } + } +} diff --git a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt index 3d07c3b..704f60e 100644 --- a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt +++ b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/AnalysisViewModel.kt @@ -11,9 +11,11 @@ import kotlinx.coroutines.flow.* import java.time.LocalDateTime import java.time.YearMonth import java.time.ZoneId +import java.util.Date class AnalysisViewModel(application: Application) : AndroidViewModel(application) { private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao() + private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao() private val _selectedMonth = MutableStateFlow(YearMonth.now()) val selectedMonth = _selectedMonth.asStateFlow() @@ -21,11 +23,50 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application private val _selectedAnalysisType = MutableStateFlow(AnalysisType.EXPENSE) val selectedAnalysisType = _selectedAnalysisType.asStateFlow() + private val members = memberDao.getAllMembers() + + val memberStats = combine(selectedMonth, selectedAnalysisType, members) { month, type, membersList -> + val records = recordDao.getAllRecords().first() + val monthRecords = records.filter { + val recordDate = Date(it.date.time) + val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault()) + YearMonth.from(localDateTime) == month && it.type == when(type) { + AnalysisType.EXPENSE -> TransactionType.EXPENSE + AnalysisType.INCOME -> TransactionType.INCOME + else -> null + } + } + + // 按成员统计 + val memberMap = monthRecords.groupBy { record -> + membersList.find { it.id == record.memberId }?.name ?: "未分配" + } + + val stats = memberMap.map { (memberName, records) -> + CategoryStat( + category = memberName, + amount = records.sumOf { it.amount }, + count = records.size + ) + }.sortedByDescending { it.amount } + + // 计算总额 + val total = stats.sumOf { it.amount } + + // 计算百分比 + stats.map { it.copy(percentage = if (total > 0) it.amount / total * 100 else 0.0) } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = emptyList() + ) + val categoryStats = combine(selectedMonth, selectedAnalysisType) { month, type -> val records = recordDao.getAllRecords().first() val monthRecords = records.filter { - val recordDate = LocalDateTime.ofInstant(it.date.toInstant(), ZoneId.systemDefault()) - YearMonth.from(recordDate) == month && it.type == when(type) { + val recordDate = Date(it.date.time) + val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault()) + YearMonth.from(localDateTime) == month && it.type == when(type) { AnalysisType.EXPENSE -> TransactionType.EXPENSE AnalysisType.INCOME -> TransactionType.INCOME else -> null diff --git a/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/MemberDetailViewModel.kt b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/MemberDetailViewModel.kt new file mode 100644 index 0000000..2e56cf7 --- /dev/null +++ b/app/src/main/java/com/yovinchen/bookkeeping/viewmodel/MemberDetailViewModel.kt @@ -0,0 +1,34 @@ +package com.yovinchen.bookkeeping.viewmodel + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import com.yovinchen.bookkeeping.data.BookkeepingDatabase +import com.yovinchen.bookkeeping.model.BookkeepingRecord +import kotlinx.coroutines.flow.* +import java.time.YearMonth +import java.time.format.DateTimeFormatter + +class MemberDetailViewModel(application: Application) : AndroidViewModel(application) { + private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao() + + private val _memberRecords = MutableStateFlow>(emptyList()) + val memberRecords: StateFlow> = _memberRecords.asStateFlow() + + val totalAmount: StateFlow = _memberRecords + .map { records -> records.sumOf { it.amount } } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5000), + initialValue = 0.0 + ) + + fun loadMemberRecords(memberName: String, yearMonth: YearMonth) { + recordDao.getRecordsByMemberAndMonth( + memberName, + yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM")) + ).onEach { records -> + _memberRecords.value = records + }.launchIn(viewModelScope) + } +}