feat: 添加时间区间选择和数据统计改进
1. 添加 DateRangePicker 组件用于时间区间选择 2. 新增 MemberStat 模型用于成员统计 3. 重构 CategoryStatItem 以支持多类型统计数据 4. 更新 AnalysisViewModel 以支持时间区间统计 5. 改进分类和成员视图的切换逻辑
This commit is contained in:
parent
96d5fab40c
commit
c92cc18dde
@ -0,0 +1,8 @@
|
|||||||
|
package com.yovinchen.bookkeeping.model
|
||||||
|
|
||||||
|
data class MemberStat(
|
||||||
|
val member: String,
|
||||||
|
val amount: Double,
|
||||||
|
val count: Int,
|
||||||
|
val percentage: Double = 0.0
|
||||||
|
)
|
@ -1,70 +1,75 @@
|
|||||||
package com.yovinchen.bookkeeping.ui.components
|
package com.yovinchen.bookkeeping.ui.components
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.yovinchen.bookkeeping.model.CategoryStat
|
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||||
|
import com.yovinchen.bookkeeping.model.MemberStat
|
||||||
|
import java.text.NumberFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CategoryStatItem(
|
fun CategoryStatItem(
|
||||||
stat: CategoryStat,
|
stat: Any,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Column(
|
val name = when (stat) {
|
||||||
modifier = Modifier
|
is CategoryStat -> stat.category
|
||||||
|
is MemberStat -> stat.member
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
val amount = when (stat) {
|
||||||
|
is CategoryStat -> stat.amount
|
||||||
|
is MemberStat -> stat.amount
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
val count = when (stat) {
|
||||||
|
is CategoryStat -> stat.count
|
||||||
|
is MemberStat -> stat.count
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
val percentage = when (stat) {
|
||||||
|
is CategoryStat -> stat.percentage
|
||||||
|
is MemberStat -> stat.percentage
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
Card(
|
||||||
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||||
.padding(vertical = 8.dp)
|
.clickable(onClick = onClick),
|
||||||
|
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Text(
|
||||||
|
text = name,
|
||||||
|
style = MaterialTheme.typography.bodyLarge
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "${count}笔 · ${String.format("%.1f%%", percentage)}",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = stat.category,
|
text = NumberFormat.getCurrencyInstance(Locale.CHINA).format(amount),
|
||||||
style = MaterialTheme.typography.bodyLarge
|
style = MaterialTheme.typography.titleMedium
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = String.format("%.2f", stat.amount),
|
|
||||||
style = MaterialTheme.typography.bodyLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
LinearProgressIndicator(
|
|
||||||
progress = { stat.percentage.toFloat() / 100f },
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(8.dp)
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.surfaceVariant,
|
|
||||||
RoundedCornerShape(4.dp)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = String.format("%.1f%%", stat.percentage),
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.yovinchen.bookkeeping.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import java.time.YearMonth
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun DateRangePicker(
|
||||||
|
startMonth: YearMonth,
|
||||||
|
endMonth: YearMonth,
|
||||||
|
onStartMonthSelected: (YearMonth) -> Unit,
|
||||||
|
onEndMonthSelected: (YearMonth) -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
var showStartMonthPicker by remember { mutableStateOf(false) }
|
||||||
|
var showEndMonthPicker by remember { mutableStateOf(false) }
|
||||||
|
val formatter = DateTimeFormatter.ofPattern("yyyy年MM月")
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Button(onClick = { showStartMonthPicker = true }) {
|
||||||
|
Text(startMonth.format(formatter))
|
||||||
|
}
|
||||||
|
Text("至")
|
||||||
|
Button(onClick = { showEndMonthPicker = true }) {
|
||||||
|
Text(endMonth.format(formatter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showStartMonthPicker) {
|
||||||
|
MonthYearPicker(
|
||||||
|
selectedMonth = startMonth,
|
||||||
|
onMonthSelected = {
|
||||||
|
onStartMonthSelected(it)
|
||||||
|
showStartMonthPicker = false
|
||||||
|
},
|
||||||
|
onDismiss = { showStartMonthPicker = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showEndMonthPicker) {
|
||||||
|
MonthYearPicker(
|
||||||
|
selectedMonth = endMonth,
|
||||||
|
onMonthSelected = {
|
||||||
|
onEndMonthSelected(it)
|
||||||
|
showEndMonthPicker = false
|
||||||
|
},
|
||||||
|
onDismiss = { showEndMonthPicker = false }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -33,12 +33,13 @@ import androidx.compose.ui.Modifier
|
|||||||
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.model.AnalysisType
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
|
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||||
|
import com.yovinchen.bookkeeping.model.MemberStat
|
||||||
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
import com.yovinchen.bookkeeping.ui.components.CategoryPieChart
|
||||||
import com.yovinchen.bookkeeping.ui.components.CategoryStatItem
|
import com.yovinchen.bookkeeping.ui.components.CategoryStatItem
|
||||||
import com.yovinchen.bookkeeping.ui.components.MonthYearPicker
|
import com.yovinchen.bookkeeping.ui.components.DateRangePicker
|
||||||
import com.yovinchen.bookkeeping.viewmodel.AnalysisViewModel
|
import com.yovinchen.bookkeeping.viewmodel.AnalysisViewModel
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
import java.time.format.DateTimeFormatter
|
|
||||||
|
|
||||||
enum class ViewMode {
|
enum class ViewMode {
|
||||||
CATEGORY, MEMBER
|
CATEGORY, MEMBER
|
||||||
@ -51,12 +52,12 @@ fun AnalysisScreen(
|
|||||||
onNavigateToMemberDetail: (String, YearMonth, AnalysisType) -> Unit
|
onNavigateToMemberDetail: (String, YearMonth, AnalysisType) -> Unit
|
||||||
) {
|
) {
|
||||||
val viewModel: AnalysisViewModel = viewModel()
|
val viewModel: AnalysisViewModel = viewModel()
|
||||||
val selectedMonth by viewModel.selectedMonth.collectAsState()
|
val startMonth by viewModel.startMonth.collectAsState()
|
||||||
|
val endMonth by viewModel.endMonth.collectAsState()
|
||||||
val selectedAnalysisType by viewModel.selectedAnalysisType.collectAsState()
|
val selectedAnalysisType by viewModel.selectedAnalysisType.collectAsState()
|
||||||
val categoryStats by viewModel.categoryStats.collectAsState()
|
val categoryStats by viewModel.categoryStats.collectAsState()
|
||||||
val memberStats by viewModel.memberStats.collectAsState()
|
val memberStats by viewModel.memberStats.collectAsState()
|
||||||
|
|
||||||
var showMonthPicker by remember { mutableStateOf(false) }
|
|
||||||
var showViewModeMenu by remember { mutableStateOf(false) }
|
var showViewModeMenu by remember { mutableStateOf(false) }
|
||||||
var currentViewMode by rememberSaveable { mutableStateOf(ViewMode.CATEGORY) }
|
var currentViewMode by rememberSaveable { mutableStateOf(ViewMode.CATEGORY) }
|
||||||
|
|
||||||
@ -66,18 +67,13 @@ fun AnalysisScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding)
|
.padding(padding)
|
||||||
) {
|
) {
|
||||||
// 时间选择按钮行
|
// 时间区间选择
|
||||||
Row(
|
DateRangePicker(
|
||||||
modifier = Modifier
|
startMonth = startMonth,
|
||||||
.fillMaxWidth()
|
endMonth = endMonth,
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
onStartMonthSelected = viewModel::setStartMonth,
|
||||||
horizontalArrangement = Arrangement.End,
|
onEndMonthSelected = viewModel::setEndMonth
|
||||||
verticalAlignment = Alignment.CenterVertically
|
)
|
||||||
) {
|
|
||||||
Button(onClick = { showMonthPicker = true }) {
|
|
||||||
Text(selectedMonth.format(DateTimeFormatter.ofPattern("yyyy年MM月")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分析类型和视图模式选择行
|
// 分析类型和视图模式选择行
|
||||||
Row(
|
Row(
|
||||||
@ -147,7 +143,7 @@ fun AnalysisScreen(
|
|||||||
item {
|
item {
|
||||||
CategoryPieChart(
|
CategoryPieChart(
|
||||||
categoryData = categoryStats.map { Pair(it.category, it.percentage.toFloat()) },
|
categoryData = categoryStats.map { Pair(it.category, it.percentage.toFloat()) },
|
||||||
memberData = memberStats.map { Pair(it.category, it.percentage.toFloat()) },
|
memberData = memberStats.map { Pair(it.member, it.percentage.toFloat()) },
|
||||||
currentViewMode = currentViewMode == ViewMode.MEMBER,
|
currentViewMode = currentViewMode == ViewMode.MEMBER,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@ -155,9 +151,9 @@ fun AnalysisScreen(
|
|||||||
.padding(bottom = 16.dp),
|
.padding(bottom = 16.dp),
|
||||||
onCategoryClick = { category ->
|
onCategoryClick = { category ->
|
||||||
if (currentViewMode == ViewMode.CATEGORY) {
|
if (currentViewMode == ViewMode.CATEGORY) {
|
||||||
onNavigateToCategoryDetail(category, selectedMonth)
|
onNavigateToCategoryDetail(category, startMonth)
|
||||||
} else {
|
} else {
|
||||||
onNavigateToMemberDetail(category, selectedMonth, selectedAnalysisType)
|
onNavigateToMemberDetail(category, startMonth, selectedAnalysisType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -166,29 +162,21 @@ fun AnalysisScreen(
|
|||||||
|
|
||||||
// 添加统计列表项目
|
// 添加统计列表项目
|
||||||
items(if (currentViewMode == ViewMode.CATEGORY) categoryStats else memberStats) { stat ->
|
items(if (currentViewMode == ViewMode.CATEGORY) categoryStats else memberStats) { stat ->
|
||||||
|
val category = if (stat is CategoryStat) stat.category else null
|
||||||
|
val member = if (stat is MemberStat) stat.member else null
|
||||||
|
|
||||||
CategoryStatItem(
|
CategoryStatItem(
|
||||||
stat = stat,
|
stat = stat,
|
||||||
onClick = {
|
onClick = {
|
||||||
if (currentViewMode == ViewMode.CATEGORY) {
|
if (currentViewMode == ViewMode.CATEGORY && category != null) {
|
||||||
onNavigateToCategoryDetail(stat.category, selectedMonth)
|
onNavigateToCategoryDetail(category, startMonth)
|
||||||
} else {
|
} else if (currentViewMode == ViewMode.MEMBER && member != null) {
|
||||||
onNavigateToMemberDetail(stat.category, selectedMonth, selectedAnalysisType)
|
onNavigateToMemberDetail(member, startMonth, selectedAnalysisType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showMonthPicker) {
|
|
||||||
MonthYearPicker(
|
|
||||||
selectedMonth = selectedMonth,
|
|
||||||
onMonthSelected = { month ->
|
|
||||||
viewModel.setSelectedMonth(month)
|
|
||||||
showMonthPicker = false
|
|
||||||
},
|
|
||||||
onDismiss = { showMonthPicker = false }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,67 +6,65 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
import com.yovinchen.bookkeeping.data.BookkeepingDatabase
|
||||||
import com.yovinchen.bookkeeping.model.AnalysisType
|
import com.yovinchen.bookkeeping.model.AnalysisType
|
||||||
import com.yovinchen.bookkeeping.model.CategoryStat
|
import com.yovinchen.bookkeeping.model.CategoryStat
|
||||||
|
import com.yovinchen.bookkeeping.model.MemberStat
|
||||||
import com.yovinchen.bookkeeping.model.TransactionType
|
import com.yovinchen.bookkeeping.model.TransactionType
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.YearMonth
|
import java.time.YearMonth
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.util.Date
|
import java.util.*
|
||||||
|
|
||||||
class AnalysisViewModel(application: Application) : AndroidViewModel(application) {
|
class AnalysisViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
private val recordDao = BookkeepingDatabase.getDatabase(application).bookkeepingDao()
|
||||||
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
private val memberDao = BookkeepingDatabase.getDatabase(application).memberDao()
|
||||||
|
|
||||||
private val _selectedMonth = MutableStateFlow(YearMonth.now())
|
private val _startMonth = MutableStateFlow(YearMonth.now())
|
||||||
val selectedMonth = _selectedMonth.asStateFlow()
|
val startMonth: StateFlow<YearMonth> = _startMonth.asStateFlow()
|
||||||
|
|
||||||
|
private val _endMonth = MutableStateFlow(YearMonth.now())
|
||||||
|
val endMonth: StateFlow<YearMonth> = _endMonth.asStateFlow()
|
||||||
|
|
||||||
private val _selectedAnalysisType = MutableStateFlow(AnalysisType.EXPENSE)
|
private val _selectedAnalysisType = MutableStateFlow(AnalysisType.EXPENSE)
|
||||||
val selectedAnalysisType = _selectedAnalysisType.asStateFlow()
|
val selectedAnalysisType: StateFlow<AnalysisType> = _selectedAnalysisType.asStateFlow()
|
||||||
|
|
||||||
private val members = memberDao.getAllMembers()
|
private val _categoryStats = MutableStateFlow<List<CategoryStat>>(emptyList())
|
||||||
|
val categoryStats: StateFlow<List<CategoryStat>> = _categoryStats.asStateFlow()
|
||||||
|
|
||||||
val memberStats = combine(selectedMonth, selectedAnalysisType, members) { month, type, membersList ->
|
private val _memberStats = MutableStateFlow<List<MemberStat>>(emptyList())
|
||||||
val records = recordDao.getAllRecords().first()
|
val memberStats: StateFlow<List<MemberStat>> = _memberStats.asStateFlow()
|
||||||
val monthRecords = records.filter {
|
|
||||||
val recordDate = Date(it.date.time)
|
init {
|
||||||
val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault())
|
viewModelScope.launch {
|
||||||
YearMonth.from(localDateTime) == month && it.type == when(type) {
|
combine(startMonth, endMonth, selectedAnalysisType) { start, end, type ->
|
||||||
AnalysisType.EXPENSE -> TransactionType.EXPENSE
|
Triple(start, end, type)
|
||||||
AnalysisType.INCOME -> TransactionType.INCOME
|
}.collect { (start, end, type) ->
|
||||||
else -> null
|
updateStats(start, end, type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 按成员统计
|
fun setStartMonth(month: YearMonth) {
|
||||||
val memberMap = monthRecords.groupBy { record ->
|
_startMonth.value = month
|
||||||
membersList.find { it.id == record.memberId }?.name ?: "未分配"
|
}
|
||||||
}
|
|
||||||
|
|
||||||
val stats = memberMap.map { (memberName, records) ->
|
fun setEndMonth(month: YearMonth) {
|
||||||
CategoryStat(
|
_endMonth.value = month
|
||||||
category = memberName,
|
}
|
||||||
amount = records.sumOf { it.amount },
|
|
||||||
count = records.size
|
|
||||||
)
|
|
||||||
}.sortedByDescending { it.amount }
|
|
||||||
|
|
||||||
// 计算总额
|
fun setAnalysisType(type: AnalysisType) {
|
||||||
val total = stats.sumOf { it.amount }
|
_selectedAnalysisType.value = type
|
||||||
|
}
|
||||||
|
|
||||||
// 计算百分比
|
private suspend fun updateStats(startMonth: YearMonth, endMonth: YearMonth, type: AnalysisType) {
|
||||||
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 records = recordDao.getAllRecords().first()
|
||||||
val monthRecords = records.filter {
|
val monthRecords = records.filter {
|
||||||
val recordDate = Date(it.date.time)
|
val recordDate = Date(it.date.time)
|
||||||
val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault())
|
val localDateTime = LocalDateTime.ofInstant(recordDate.toInstant(), ZoneId.systemDefault())
|
||||||
YearMonth.from(localDateTime) == month && it.type == when(type) {
|
val yearMonth = YearMonth.from(localDateTime)
|
||||||
|
yearMonth.isAfter(startMonth.minusMonths(1)) &&
|
||||||
|
yearMonth.isBefore(endMonth.plusMonths(1)) &&
|
||||||
|
it.type == when(type) {
|
||||||
AnalysisType.EXPENSE -> TransactionType.EXPENSE
|
AnalysisType.EXPENSE -> TransactionType.EXPENSE
|
||||||
AnalysisType.INCOME -> TransactionType.INCOME
|
AnalysisType.INCOME -> TransactionType.INCOME
|
||||||
else -> null
|
else -> null
|
||||||
@ -75,7 +73,7 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application
|
|||||||
|
|
||||||
// 按分类统计
|
// 按分类统计
|
||||||
val categoryMap = monthRecords.groupBy { it.category }
|
val categoryMap = monthRecords.groupBy { it.category }
|
||||||
val stats = categoryMap.map { (category, records) ->
|
val categoryStats = categoryMap.map { (category, records) ->
|
||||||
CategoryStat(
|
CategoryStat(
|
||||||
category = category,
|
category = category,
|
||||||
amount = records.sumOf { it.amount },
|
amount = records.sumOf { it.amount },
|
||||||
@ -83,22 +81,33 @@ class AnalysisViewModel(application: Application) : AndroidViewModel(application
|
|||||||
)
|
)
|
||||||
}.sortedByDescending { it.amount }
|
}.sortedByDescending { it.amount }
|
||||||
|
|
||||||
// 计算总额
|
// 计算分类总额和百分比
|
||||||
val total = stats.sumOf { it.amount }
|
val categoryTotal = categoryStats.sumOf { it.amount }
|
||||||
|
val categoryStatsWithPercentage = categoryStats.map {
|
||||||
|
it.copy(percentage = if (categoryTotal > 0) it.amount / categoryTotal * 100 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
// 计算百分比
|
// 按成员统计
|
||||||
stats.map { it.copy(percentage = if (total > 0) it.amount / total * 100 else 0.0) }
|
val members = memberDao.getAllMembers().first()
|
||||||
}.stateIn(
|
val memberMap = monthRecords.groupBy { record ->
|
||||||
scope = viewModelScope,
|
members.find { it.id == record.memberId }?.name ?: "未分配"
|
||||||
started = SharingStarted.WhileSubscribed(5000),
|
}
|
||||||
initialValue = emptyList()
|
|
||||||
)
|
|
||||||
|
|
||||||
fun setSelectedMonth(month: YearMonth) {
|
val memberStats = memberMap.map { (memberName, records) ->
|
||||||
_selectedMonth.value = month
|
MemberStat(
|
||||||
}
|
member = memberName,
|
||||||
|
amount = records.sumOf { it.amount },
|
||||||
|
count = records.size
|
||||||
|
)
|
||||||
|
}.sortedByDescending { it.amount }
|
||||||
|
|
||||||
fun setAnalysisType(type: AnalysisType) {
|
// 计算成员总额和百分比
|
||||||
_selectedAnalysisType.value = type
|
val memberTotal = memberStats.sumOf { it.amount }
|
||||||
|
val memberStatsWithPercentage = memberStats.map {
|
||||||
|
it.copy(percentage = if (memberTotal > 0) it.amount / memberTotal * 100 else 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
_categoryStats.value = categoryStatsWithPercentage
|
||||||
|
_memberStats.value = memberStatsWithPercentage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user