Fix stats render and add 'disabled-member' setting

This commit is contained in:
2026-09-06 18:59:08 +02:00
parent 8d28ee2d6c
commit 2ee7c14e77
4 changed files with 38 additions and 7 deletions
@@ -31,6 +31,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.AccountCircle import androidx.compose.material.icons.filled.AccountCircle
import androidx.compose.material.icons.filled.Archive import androidx.compose.material.icons.filled.Archive
import androidx.compose.material.icons.filled.Brightness2 import androidx.compose.material.icons.filled.Brightness2
import androidx.compose.material.icons.filled.Group
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Palette import androidx.compose.material.icons.filled.Palette
import androidx.compose.material.icons.filled.Sync import androidx.compose.material.icons.filled.Sync
@@ -84,6 +85,7 @@ fun SettingsScreen(
val keyOfflineMode = stringResource(R.string.pref_key_offline_mode) val keyOfflineMode = stringResource(R.string.pref_key_offline_mode)
val keyShowArchived = stringResource(R.string.pref_key_show_archived) val keyShowArchived = stringResource(R.string.pref_key_show_archived)
val keyBetaFeatures = stringResource(R.string.pref_key_beta_features) val keyBetaFeatures = stringResource(R.string.pref_key_beta_features)
val keyStatsIncludeDeactivated = stringResource(R.string.pref_key_stats_include_deactivated)
val keyAutoSyncOnOpen = stringResource(R.string.pref_key_auto_sync_on_open) val keyAutoSyncOnOpen = stringResource(R.string.pref_key_auto_sync_on_open)
val keyFillNewBillFromLast = stringResource(R.string.pref_key_fill_new_bill_from_last) val keyFillNewBillFromLast = stringResource(R.string.pref_key_fill_new_bill_from_last)
val keyLastAccountSync = stringResource(R.string.pref_key_last_account_sync_timestamp) val keyLastAccountSync = stringResource(R.string.pref_key_last_account_sync_timestamp)
@@ -132,6 +134,9 @@ fun SettingsScreen(
var fillNewBillFromLast by remember(keyFillNewBillFromLast) { var fillNewBillFromLast by remember(keyFillNewBillFromLast) {
mutableStateOf(sharedPreferences.getBoolean(keyFillNewBillFromLast, false)) mutableStateOf(sharedPreferences.getBoolean(keyFillNewBillFromLast, false))
} }
var statsIncludeDeactivated by remember(keyStatsIncludeDeactivated) {
mutableStateOf(sharedPreferences.getBoolean(keyStatsIncludeDeactivated, false))
}
val syncIntervals = SyncSettings.INTERVAL_CHOICES_MINUTES val syncIntervals = SyncSettings.INTERVAL_CHOICES_MINUTES
val syncIntervalLabels = listOf( val syncIntervalLabels = listOf(
@@ -304,6 +309,18 @@ fun SettingsScreen(
} }
) )
SettingsSwitchPreference(
title = stringResource(R.string.settings_stats_include_deactivated),
icon = Icons.Default.Group,
checked = statsIncludeDeactivated,
onCheckedChange = {
statsIncludeDeactivated = it
sharedPreferences.edit {
putBoolean(keyStatsIncludeDeactivated, it)
}
}
)
SettingsSliderPreference( SettingsSliderPreference(
title = stringResource(R.string.settings_auto_sync_on_open), title = stringResource(R.string.settings_auto_sync_on_open),
summary = stringResource(R.string.settings_auto_sync_on_open_summary), summary = stringResource(R.string.settings_auto_sync_on_open_summary),
@@ -121,17 +121,19 @@ fun ProjectSankeyDiagram(
val catMap = mutableMapOf<Long, Double>() val catMap = mutableMapOf<Long, Double>()
activeBills.forEach { bill -> activeBills.forEach { bill ->
val totalWeight = bill.billOwers.sumOf { membersMap[it.memberId]?.weight ?: 1.0 } val totalWeight = bill.billOwers.sumOf { membersMap[it.memberId]?.weight ?: 0.0 }
if (totalWeight > 0) { if (totalWeight > 0) {
if (selectedMemberId == -1L) { if (selectedMemberId == -1L) {
catMap[bill.categoryId] = (catMap[bill.categoryId] ?: 0.0) + bill.amount catMap[bill.categoryId] = (catMap[bill.categoryId] ?: 0.0) + bill.amount
bill.billOwers.forEach { bo -> bill.billOwers.forEach { bo ->
val weight = membersMap[bo.memberId]?.weight ?: 1.0 if (membersMap.containsKey(bo.memberId)) {
val weight = membersMap[bo.memberId]?.weight ?: 0.0
spentMap[bo.memberId] = (spentMap[bo.memberId] ?: 0.0) + (bill.amount / totalWeight) * weight spentMap[bo.memberId] = (spentMap[bo.memberId] ?: 0.0) + (bill.amount / totalWeight) * weight
} }
}
} else { } else {
bill.billOwers.find { it.memberId == selectedMemberId }?.let { bo -> bill.billOwers.find { it.memberId == selectedMemberId }?.let { bo ->
val weight = membersMap[bo.memberId]?.weight ?: 1.0 val weight = membersMap[bo.memberId]?.weight ?: 0.0
catMap[bill.categoryId] = (catMap[bill.categoryId] ?: 0.0) + (bill.amount / totalWeight) * weight catMap[bill.categoryId] = (catMap[bill.categoryId] ?: 0.0) + (bill.amount / totalWeight) * weight
spentMap[selectedMemberId] = (spentMap[selectedMemberId] ?: 0.0) + (bill.amount / totalWeight) * weight spentMap[selectedMemberId] = (spentMap[selectedMemberId] ?: 0.0) + (bill.amount / totalWeight) * weight
} }
@@ -344,7 +346,9 @@ private fun SankeyContent(
scope.launch { topFocalIndex.animateTo(index.toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)) } scope.launch { topFocalIndex.animateTo(index.toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)) }
} else Modifier), contentAlignment = Alignment.Center) { } else Modifier), contentAlignment = Alignment.Center) {
Column(modifier = Modifier.fillMaxSize().background(color = member?.let { Color(it.r ?: 128, it.g ?: 128, it.b ?: 128) } ?: Color.Gray, shape = RoundedCornerShape(10.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { Column(modifier = Modifier.fillMaxSize().background(color = member?.let { Color(it.r ?: 128, it.g ?: 128, it.b ?: 128) } ?: Color.Gray, shape = RoundedCornerShape(10.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
if (wDp >= 32.dp) {
Text(text = member?.name ?: "???", color = Color.White, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier.wrapContentWidth(unbounded = true)) Text(text = member?.name ?: "???", color = Color.White, fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Center, modifier = Modifier.wrapContentWidth(unbounded = true))
}
if (wDp >= 40.dp) Text(text = formatShortValue(amount), fontSize = 12.sp, color = Color.White.copy(alpha = 0.9f), textAlign = TextAlign.Center) if (wDp >= 40.dp) Text(text = formatShortValue(amount), fontSize = 12.sp, color = Color.White.copy(alpha = 0.9f), textAlign = TextAlign.Center)
} }
} }
@@ -382,7 +386,9 @@ private fun SankeyContent(
scope.launch { bottomFocalIndex.animateTo(index.toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)) } scope.launch { bottomFocalIndex.animateTo(index.toFloat(), spring(Spring.DampingRatioLowBouncy, Spring.StiffnessLow)) }
} else Modifier), contentAlignment = Alignment.Center) { } else Modifier), contentAlignment = Alignment.Center) {
Column(modifier = Modifier.fillMaxSize().background(color = category?.color?.let { try { Color(it.toColorInt()) } catch (_: Exception) { Color(0xFF999999) } } ?: Color(0xFF999999), shape = RoundedCornerShape(10.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) { Column(modifier = Modifier.fillMaxSize().background(color = category?.color?.let { try { Color(it.toColorInt()) } catch (_: Exception) { Color(0xFF999999) } } ?: Color(0xFF999999), shape = RoundedCornerShape(10.dp)), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
if (wDp >= 24.dp) {
Text(text = category?.icon ?: "", fontSize = 20.sp, textAlign = TextAlign.Center, modifier = Modifier.wrapContentWidth(unbounded = true)) Text(text = category?.icon ?: "", fontSize = 20.sp, textAlign = TextAlign.Center, modifier = Modifier.wrapContentWidth(unbounded = true))
}
if (wDp >= 40.dp) Text(text = formatShortValue(amount), fontSize = 12.sp, color = Color.White.copy(alpha = 0.9f), textAlign = TextAlign.Center) if (wDp >= 40.dp) Text(text = formatShortValue(amount), fontSize = 12.sp, color = Color.White.copy(alpha = 0.9f), textAlign = TextAlign.Center)
} }
} }
@@ -40,6 +40,8 @@ fun ProjectStatisticsScreen(
"Sankey" "Sankey"
) )
val keyStatsIncludeDeactivated = stringResource(R.string.pref_key_stats_include_deactivated)
Scaffold( Scaffold(
topBar = { topBar = {
TopAppBar( TopAppBar(
@@ -103,7 +105,11 @@ fun ProjectStatisticsScreen(
val bills = db.getBillsOfProject(proj.id) val bills = db.getBillsOfProject(proj.id)
val categories = db.getCategories(proj.id) val categories = db.getCategories(proj.id)
val paymentModes = db.getPaymentModes(proj.id) val paymentModes = db.getPaymentModes(proj.id)
StatisticsData(members, bills, categories, paymentModes)
val includeDeactivated = prefs.getBoolean(keyStatsIncludeDeactivated, false)
val filteredMembers = if (includeDeactivated) members else members.filter { it.isActivated }
StatisticsData(filteredMembers, bills, categories, paymentModes)
} }
} }
+2
View File
@@ -143,6 +143,7 @@
<string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string> <string name="settings_beta_features_summary">Enable experimental features. Use at your own risk.</string>
<string name="settings_fill_new_bill_from_last">Auto-fill from last bill</string> <string name="settings_fill_new_bill_from_last">Auto-fill from last bill</string>
<string name="settings_fill_new_bill_from_last_summary">Pre-fill payer, category, mode and owers from the last bill created in the project.</string> <string name="settings_fill_new_bill_from_last_summary">Pre-fill payer, category, mode and owers from the last bill created in the project.</string>
<string name="settings_stats_include_deactivated">Include deactivated members in stats</string>
<string name="settings_auto_sync_on_open">Sync interval</string> <string name="settings_auto_sync_on_open">Sync interval</string>
<string name="settings_auto_sync_on_open_summary">How often to refresh the account and all projects when opening the app.</string> <string name="settings_auto_sync_on_open_summary">How often to refresh the account and all projects when opening the app.</string>
<string name="pref_value_sync_1m">1 minute</string> <string name="pref_value_sync_1m">1 minute</string>
@@ -170,6 +171,7 @@
<string name="pref_key_offline_mode" translatable="false">offlineMode</string> <string name="pref_key_offline_mode" translatable="false">offlineMode</string>
<string name="pref_key_show_archived" translatable="false">showArchived</string> <string name="pref_key_show_archived" translatable="false">showArchived</string>
<string name="pref_key_beta_features" translatable="false">betaFeatures</string> <string name="pref_key_beta_features" translatable="false">betaFeatures</string>
<string name="pref_key_stats_include_deactivated" translatable="false">statsIncludeDeactivated</string>
<string name="pref_key_auto_sync_on_open" translatable="false">autoSyncOnOpen</string> <string name="pref_key_auto_sync_on_open" translatable="false">autoSyncOnOpen</string>
<string name="pref_key_last_account_sync_timestamp" translatable="false">lastAccountSyncTimestamp</string> <string name="pref_key_last_account_sync_timestamp" translatable="false">lastAccountSyncTimestamp</string>
<string name="pref_key_fill_new_bill_from_last" translatable="false">fillNewBillFromLast</string> <string name="pref_key_fill_new_bill_from_last" translatable="false">fillNewBillFromLast</string>