Compare commits
5 Commits
94fc7b2a7e
...
a0d47864d8
Author | SHA1 | Date | |
---|---|---|---|
a0d47864d8 | |||
63149f9abb | |||
70e79ec584 | |||
882435e25a | |||
37b91ded7f |
@ -16,8 +16,8 @@ android {
|
|||||||
applicationId = "com.yovinchen.bookkeeping"
|
applicationId = "com.yovinchen.bookkeeping"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 4
|
||||||
versionName = "1.0.0"
|
versionName = "1.2.2"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
@ -110,15 +110,47 @@ interface BookkeepingDao {
|
|||||||
|
|
||||||
@Query("""
|
@Query("""
|
||||||
SELECT * FROM bookkeeping_records
|
SELECT * FROM bookkeeping_records
|
||||||
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||||
AND category = :category
|
|
||||||
AND date BETWEEN :startDate AND :endDate
|
AND date BETWEEN :startDate AND :endDate
|
||||||
|
AND (
|
||||||
|
:transactionType IS NULL
|
||||||
|
OR type = (
|
||||||
|
CASE :transactionType
|
||||||
|
WHEN 'INCOME' THEN 'INCOME'
|
||||||
|
WHEN 'EXPENSE' THEN 'EXPENSE'
|
||||||
|
END
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY date DESC
|
||||||
|
""")
|
||||||
|
suspend fun getRecordsByMember(
|
||||||
|
memberName: String,
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date,
|
||||||
|
transactionType: TransactionType?
|
||||||
|
): List<BookkeepingRecord>
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT * FROM bookkeeping_records
|
||||||
|
WHERE memberId IN (SELECT id FROM members WHERE name = :memberName)
|
||||||
|
AND category = :category
|
||||||
|
AND date BETWEEN :startDate AND :endDate
|
||||||
|
AND (
|
||||||
|
:transactionType IS NULL
|
||||||
|
OR type = (
|
||||||
|
CASE :transactionType
|
||||||
|
WHEN 'INCOME' THEN 'INCOME'
|
||||||
|
WHEN 'EXPENSE' THEN 'EXPENSE'
|
||||||
|
END
|
||||||
|
)
|
||||||
|
)
|
||||||
ORDER BY date DESC
|
ORDER BY date DESC
|
||||||
""")
|
""")
|
||||||
suspend fun getRecordsByMemberAndCategory(
|
suspend fun getRecordsByMemberAndCategory(
|
||||||
memberName: String,
|
memberName: String,
|
||||||
category: String,
|
category: String,
|
||||||
startDate: Date,
|
startDate: Date,
|
||||||
endDate: Date
|
endDate: Date,
|
||||||
|
transactionType: TransactionType?
|
||||||
): List<BookkeepingRecord>
|
): List<BookkeepingRecord>
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
import com.yovinchen.bookkeeping.model.ThemeMode
|
import com.yovinchen.bookkeeping.model.ThemeMode
|
||||||
import com.yovinchen.bookkeeping.ui.screen.*
|
import com.yovinchen.bookkeeping.ui.screen.*
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
@ -40,9 +41,9 @@ sealed class Screen(
|
|||||||
return "category_detail/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}"
|
return "category_detail/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
object MemberDetail : Screen("member_detail/{memberName}/{category}/{yearMonth}", "成员详情") {
|
object MemberDetail : Screen("member_detail/{memberName}/{category}/{yearMonth}?type={type}", "成员详情") {
|
||||||
fun createRoute(memberName: String, category: String, yearMonth: YearMonth): String {
|
fun createRoute(memberName: String, category: String, yearMonth: YearMonth, type: AnalysisType): String {
|
||||||
return "member_detail/$memberName/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}"
|
return "member_detail/$memberName/$category/${yearMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"))}?type=${type.name}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,10 +97,8 @@ fun MainNavigation(
|
|||||||
onNavigateToCategoryDetail = { category, yearMonth ->
|
onNavigateToCategoryDetail = { category, yearMonth ->
|
||||||
navController.navigate(Screen.CategoryDetail.createRoute(category, yearMonth))
|
navController.navigate(Screen.CategoryDetail.createRoute(category, yearMonth))
|
||||||
},
|
},
|
||||||
onNavigateToMemberDetail = { memberName, yearMonth ->
|
onNavigateToMemberDetail = { memberName, yearMonth, analysisType ->
|
||||||
// 在这里我们暂时使用一个默认分类,你需要根据实际情况修改这里的逻辑
|
navController.navigate(Screen.MemberDetail.createRoute(memberName, "", yearMonth, analysisType))
|
||||||
val defaultCategory = "默认"
|
|
||||||
navController.navigate(Screen.MemberDetail.createRoute(memberName, defaultCategory, yearMonth))
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -127,7 +126,7 @@ fun MainNavigation(
|
|||||||
yearMonth = yearMonth,
|
yearMonth = yearMonth,
|
||||||
onNavigateBack = { navController.popBackStack() },
|
onNavigateBack = { navController.popBackStack() },
|
||||||
onNavigateToMemberDetail = { memberName ->
|
onNavigateToMemberDetail = { memberName ->
|
||||||
navController.navigate(Screen.MemberDetail.createRoute(memberName, category, yearMonth))
|
navController.navigate(Screen.MemberDetail.createRoute(memberName, category, yearMonth, AnalysisType.EXPENSE))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -137,18 +136,30 @@ fun MainNavigation(
|
|||||||
arguments = listOf(
|
arguments = listOf(
|
||||||
navArgument("memberName") { type = NavType.StringType },
|
navArgument("memberName") { type = NavType.StringType },
|
||||||
navArgument("category") { type = NavType.StringType },
|
navArgument("category") { type = NavType.StringType },
|
||||||
navArgument("yearMonth") { type = NavType.StringType }
|
navArgument("yearMonth") { type = NavType.StringType },
|
||||||
|
navArgument("type") {
|
||||||
|
type = NavType.StringType
|
||||||
|
defaultValue = AnalysisType.EXPENSE.name
|
||||||
|
}
|
||||||
)
|
)
|
||||||
) { backStackEntry ->
|
) { backStackEntry ->
|
||||||
val memberName = backStackEntry.arguments?.getString("memberName") ?: return@composable
|
val memberName = backStackEntry.arguments?.getString("memberName") ?: return@composable
|
||||||
val category = backStackEntry.arguments?.getString("category") ?: return@composable
|
val category = backStackEntry.arguments?.getString("category") ?: return@composable
|
||||||
val yearMonthStr = backStackEntry.arguments?.getString("yearMonth") ?: return@composable
|
val yearMonthStr = backStackEntry.arguments?.getString("yearMonth") ?: return@composable
|
||||||
val yearMonth = YearMonth.parse(yearMonthStr)
|
val yearMonth = YearMonth.parse(yearMonthStr)
|
||||||
|
val type = backStackEntry.arguments?.getString("type")?.let {
|
||||||
|
try {
|
||||||
|
AnalysisType.valueOf(it)
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
AnalysisType.EXPENSE
|
||||||
|
}
|
||||||
|
} ?: AnalysisType.EXPENSE
|
||||||
|
|
||||||
MemberDetailScreen(
|
MemberDetailScreen(
|
||||||
memberName = memberName,
|
memberName = memberName,
|
||||||
category = category,
|
|
||||||
yearMonth = yearMonth,
|
yearMonth = yearMonth,
|
||||||
|
category = category,
|
||||||
|
analysisType = type,
|
||||||
onNavigateBack = { navController.popBackStack() }
|
onNavigateBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ enum class ViewMode {
|
|||||||
@Composable
|
@Composable
|
||||||
fun AnalysisScreen(
|
fun AnalysisScreen(
|
||||||
onNavigateToCategoryDetail: (String, YearMonth) -> Unit,
|
onNavigateToCategoryDetail: (String, YearMonth) -> Unit,
|
||||||
onNavigateToMemberDetail: (String, YearMonth) -> Unit
|
onNavigateToMemberDetail: (String, YearMonth, AnalysisType) -> Unit
|
||||||
) {
|
) {
|
||||||
val viewModel: AnalysisViewModel = viewModel()
|
val viewModel: AnalysisViewModel = viewModel()
|
||||||
val selectedMonth by viewModel.selectedMonth.collectAsState()
|
val selectedMonth by viewModel.selectedMonth.collectAsState()
|
||||||
@ -138,7 +138,7 @@ fun AnalysisScreen(
|
|||||||
if (currentViewMode == ViewMode.CATEGORY) {
|
if (currentViewMode == ViewMode.CATEGORY) {
|
||||||
onNavigateToCategoryDetail(category, selectedMonth)
|
onNavigateToCategoryDetail(category, selectedMonth)
|
||||||
} else {
|
} else {
|
||||||
onNavigateToMemberDetail(category, selectedMonth)
|
onNavigateToMemberDetail(category, selectedMonth, selectedAnalysisType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -149,11 +149,11 @@ fun AnalysisScreen(
|
|||||||
items(if (currentViewMode == ViewMode.CATEGORY) categoryStats else memberStats) { stat ->
|
items(if (currentViewMode == ViewMode.CATEGORY) categoryStats else memberStats) { stat ->
|
||||||
CategoryStatItem(
|
CategoryStatItem(
|
||||||
stat = stat,
|
stat = stat,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (currentViewMode == ViewMode.CATEGORY) {
|
if (currentViewMode == ViewMode.CATEGORY) {
|
||||||
onNavigateToCategoryDetail(stat.category, selectedMonth)
|
onNavigateToCategoryDetail(stat.category, selectedMonth)
|
||||||
} else {
|
} else {
|
||||||
onNavigateToMemberDetail(stat.category, selectedMonth)
|
onNavigateToMemberDetail(stat.category, selectedMonth, selectedAnalysisType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,30 @@
|
|||||||
package com.yovinchen.bookkeeping.ui.screen
|
package com.yovinchen.bookkeeping.ui.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@ -15,6 +33,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||||
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
||||||
import com.yovinchen.bookkeeping.ui.components.RecordItem
|
import com.yovinchen.bookkeeping.ui.components.RecordItem
|
||||||
import com.yovinchen.bookkeeping.viewmodel.CategoryDetailViewModel
|
import com.yovinchen.bookkeeping.viewmodel.CategoryDetailViewModel
|
||||||
@ -22,8 +41,7 @@ import com.yovinchen.bookkeeping.viewmodel.CategoryDetailViewModelFactory
|
|||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
import java.time.format.DateTimeFormatter
|
import java.util.Locale
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -62,27 +80,78 @@ fun CategoryDetailScreen(
|
|||||||
.padding(padding),
|
.padding(padding),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
// 第一部分:总支出
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (records.isNotEmpty() && records.first().type == TransactionType.INCOME) "总收入" else "总支出",
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(total),
|
||||||
|
style = MaterialTheme.typography.headlineMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二部分:扇形图
|
||||||
|
item {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "成员分布",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp)
|
||||||
|
)
|
||||||
|
CategoryPieChart(
|
||||||
|
categoryData = memberStats.map { Pair(it.category, it.percentage.toFloat()) },
|
||||||
|
memberData = emptyList(),
|
||||||
|
currentViewMode = false,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp),
|
||||||
|
onCategoryClick = { memberName ->
|
||||||
|
if (records.isNotEmpty() && records.first().type == TransactionType.EXPENSE) {
|
||||||
|
onNavigateToMemberDetail(memberName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三部分:详细信息
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(total),
|
text = "详细记录",
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
// 按日期分组的记录列表
|
||||||
CategoryPieChart(
|
|
||||||
categoryData = memberStats.map { Pair(it.category, it.percentage.toFloat()) },
|
|
||||||
memberData = emptyList(),
|
|
||||||
currentViewMode = false,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(16.dp),
|
|
||||||
onCategoryClick = { memberName -> onNavigateToMemberDetail(memberName) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按日期分组记录
|
|
||||||
val groupedRecords = records.groupBy { record ->
|
val groupedRecords = records.groupBy { record ->
|
||||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(record.date)
|
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(record.date)
|
||||||
}.toSortedMap(compareByDescending { it })
|
}.toSortedMap(compareByDescending { it })
|
||||||
|
@ -1,39 +1,61 @@
|
|||||||
package com.yovinchen.bookkeeping.ui.screen
|
package com.yovinchen.bookkeeping.ui.screen
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
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.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.yovinchen.bookkeeping.data.Record
|
import com.yovinchen.bookkeeping.data.Record
|
||||||
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
import com.yovinchen.bookkeeping.ui.components.RecordItem
|
import com.yovinchen.bookkeeping.ui.components.RecordItem
|
||||||
import com.yovinchen.bookkeeping.viewmodel.MemberDetailViewModel
|
import com.yovinchen.bookkeeping.viewmodel.MemberDetailViewModel
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
import java.time.format.DateTimeFormatter
|
import java.util.Locale
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MemberDetailScreen(
|
fun MemberDetailScreen(
|
||||||
memberName: String,
|
memberName: String,
|
||||||
category: String,
|
|
||||||
yearMonth: YearMonth,
|
yearMonth: YearMonth,
|
||||||
|
category: String = "",
|
||||||
|
analysisType: AnalysisType = AnalysisType.EXPENSE,
|
||||||
onNavigateBack: () -> Unit,
|
onNavigateBack: () -> Unit,
|
||||||
viewModel: MemberDetailViewModel = viewModel()
|
viewModel: MemberDetailViewModel = viewModel()
|
||||||
) {
|
) {
|
||||||
val records by viewModel.memberRecords.collectAsState(initial = emptyList())
|
val records by viewModel.memberRecords.collectAsState(initial = emptyList())
|
||||||
val totalAmount by viewModel.totalAmount.collectAsState(initial = 0.0)
|
val totalAmount by viewModel.totalAmount.collectAsState(initial = 0.0)
|
||||||
|
|
||||||
LaunchedEffect(memberName, category, yearMonth) {
|
LaunchedEffect(memberName, category, yearMonth, analysisType) {
|
||||||
viewModel.loadMemberRecords(memberName, category, yearMonth)
|
viewModel.loadMemberRecords(memberName, category, yearMonth, analysisType)
|
||||||
}
|
}
|
||||||
|
|
||||||
val groupedRecords = remember(records) {
|
val groupedRecords = remember(records) {
|
||||||
@ -46,11 +68,11 @@ fun MemberDetailScreen(
|
|||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text("$category - $memberName")
|
Text(memberName)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = onNavigateBack) {
|
IconButton(onClick = onNavigateBack) {
|
||||||
Icon(Icons.Default.ArrowBack, "返回")
|
Icon(Icons.AutoMirrored.Filled.ArrowBack, "返回")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -75,7 +97,7 @@ fun MemberDetailScreen(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "当前分类总支出",
|
text = if (records.isNotEmpty() && records.first().type == TransactionType.INCOME) "总收入" else "总支出",
|
||||||
style = MaterialTheme.typography.titleMedium
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
@ -5,6 +5,8 @@ import androidx.lifecycle.AndroidViewModel
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||||
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
import com.yovinchen.bookkeeping.model.BookkeepingRecord
|
||||||
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -22,7 +24,7 @@ class MemberDetailViewModel(application: Application) : AndroidViewModel(applica
|
|||||||
private val _totalAmount = MutableStateFlow(0.0)
|
private val _totalAmount = MutableStateFlow(0.0)
|
||||||
val totalAmount: StateFlow<Double> = _totalAmount
|
val totalAmount: StateFlow<Double> = _totalAmount
|
||||||
|
|
||||||
fun loadMemberRecords(memberName: String, category: String, yearMonth: YearMonth) {
|
fun loadMemberRecords(memberName: String, category: String, yearMonth: YearMonth, analysisType: AnalysisType) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val startDate = yearMonth.atDay(1).atStartOfDay()
|
val startDate = yearMonth.atDay(1).atStartOfDay()
|
||||||
.atZone(ZoneId.systemDefault())
|
.atZone(ZoneId.systemDefault())
|
||||||
@ -34,12 +36,28 @@ class MemberDetailViewModel(application: Application) : AndroidViewModel(applica
|
|||||||
.toInstant()
|
.toInstant()
|
||||||
.let { Date.from(it) }
|
.let { Date.from(it) }
|
||||||
|
|
||||||
val records = recordDao.getRecordsByMemberAndCategory(
|
val transactionType = when (analysisType) {
|
||||||
memberName = memberName,
|
AnalysisType.INCOME -> TransactionType.INCOME
|
||||||
category = category,
|
AnalysisType.EXPENSE -> TransactionType.EXPENSE
|
||||||
startDate = startDate,
|
else -> null
|
||||||
endDate = endDate
|
}
|
||||||
)
|
|
||||||
|
val records = if (category.isEmpty()) {
|
||||||
|
recordDao.getRecordsByMember(
|
||||||
|
memberName = memberName,
|
||||||
|
startDate = startDate,
|
||||||
|
endDate = endDate,
|
||||||
|
transactionType = transactionType
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
recordDao.getRecordsByMemberAndCategory(
|
||||||
|
memberName = memberName,
|
||||||
|
category = category,
|
||||||
|
startDate = startDate,
|
||||||
|
endDate = endDate,
|
||||||
|
transactionType = transactionType
|
||||||
|
)
|
||||||
|
}
|
||||||
_memberRecords.value = records
|
_memberRecords.value = records
|
||||||
_totalAmount.value = records.sumOf { it.amount }
|
_totalAmount.value = records.sumOf { it.amount }
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user