From cf9b1cc6e24235f0feacbb2cd880f92085b35067 Mon Sep 17 00:00:00 2001 From: soraefir Date: Thu, 9 Jul 2026 20:52:27 +0200 Subject: [PATCH] Better cascading stats --- .../net/helcel/beans/activity/MainScreen.kt | 20 +-- .../helcel/beans/activity/SettingsScreen.kt | 31 ++-- .../helcel/beans/activity/StatsActivity.kt | 138 +++++++++--------- .../java/net/helcel/beans/helper/Groups.kt | 7 + .../java/net/helcel/beans/helper/Settings.kt | 4 +- app/src/main/res/values/en.xml | 6 +- 6 files changed, 113 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/net/helcel/beans/activity/MainScreen.kt b/app/src/main/java/net/helcel/beans/activity/MainScreen.kt index 3c466ce..2f2e6aa 100644 --- a/app/src/main/java/net/helcel/beans/activity/MainScreen.kt +++ b/app/src/main/java/net/helcel/beans/activity/MainScreen.kt @@ -57,23 +57,25 @@ class MainScreen : ComponentActivity() { setContent { SysTheme { - val currentPsvg = psvg - val currentCss = css - if (currentPsvg != null && currentCss != null) { - Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) { - AppNavHost(currentPsvg, currentCss) - } + Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) { + AppNavHost() } } } } @Composable - fun AppNavHost(psvg: SVGWrapper, css: CSSWrapper) { + fun AppNavHost() { val navController = rememberNavController() NavHost(navController, startDestination = "main") { - composable("main") { MainScreenC(psvg,css, navController) } - composable("settings") { SettingsMainScreen { navController.navigate("main")} } + composable("main") { + val currentPsvg = psvg + val currentCss = css + if (currentPsvg != null && currentCss != null) { + MainScreenC(currentPsvg, currentCss, navController) + } + } + composable("settings") { SettingsMainScreen { navController.navigate("main") } } composable("edit") { EditScreen { navController.navigate("main") } } composable("stats") { StatsScreen { navController.navigate("main") } } } diff --git a/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt b/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt index 6047637..b2c98fd 100644 --- a/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt +++ b/app/src/main/java/net/helcel/beans/activity/SettingsScreen.kt @@ -156,7 +156,7 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) { val keyProjection = stringResource(R.string.key_projection) val keyGroup = stringResource(R.string.key_group) val keyRegional = stringResource(R.string.key_regional) - val keyRegionalStats = stringResource(R.string.key_regional_stats) + val keyCascadeStats = stringResource(R.string.key_cascade_stats) val offString = stringResource(R.string.off) val onString = stringResource(R.string.on) @@ -164,7 +164,7 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) { var projection by remember { mutableStateOf(prefs.getString(keyProjection, "default")!!) } var groups by remember { mutableStateOf(prefs.getString(keyGroup, offString)!!) } var regional by remember { mutableStateOf(prefs.getString(keyRegional, offString)!!) } - var regionalStats by remember { mutableStateOf(prefs.getString(keyRegionalStats, offString)!!) } + var cascadeStats by remember { mutableStateOf(prefs.getString(keyCascadeStats, offString)!!) } var showGroupDialog by remember { mutableStateOf(false) } var showRegionalDialog by remember { mutableStateOf(false) } @@ -178,7 +178,10 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) { val g = Data.selected_group if (g != null) { Data.visits.reassignAllVisitedToGroup(g.key) + Data.groups.keepOnly(g.key) Data.saveData() + groups = offString + prefs.edit { putString(keyGroup, offString) } } showGroupDialog = false }) @@ -274,6 +277,7 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) { isChecked = groups == onString, onCheckedChange = { isChecked -> if (!isChecked) { + Data.selected_group = null showGroupDialog = true } else { groups = onString @@ -304,17 +308,18 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) { } } ) - if (regional == onString) { - SettingSwitch( - label = stringResource(R.string.pref_regional_stats), - subtitle = stringResource(R.string.pref_regional_stats_desc), - isChecked = regionalStats == onString, - onCheckedChange = { isChecked -> - regionalStats = if (isChecked) onString else offString - prefs.edit { putString(keyRegionalStats, regionalStats) } - } - ) - } + HorizontalDivider() + } + item { + SettingSwitch( + label = stringResource(R.string.pref_cascade_stats), + subtitle = stringResource(R.string.pref_cascade_stats_desc), + isChecked = cascadeStats == onString, + onCheckedChange = { isChecked -> + cascadeStats = if (isChecked) onString else offString + prefs.edit { putString(keyCascadeStats, cascadeStats) } + } + ) HorizontalDivider() } item { diff --git a/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt b/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt index 087317c..053e535 100644 --- a/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt +++ b/app/src/main/java/net/helcel/beans/activity/StatsActivity.kt @@ -20,6 +20,7 @@ import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf @@ -32,11 +33,14 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import net.helcel.beans.R +import net.helcel.beans.countries.Country +import net.helcel.beans.countries.GeoLoc import net.helcel.beans.countries.GeoLoc.LocType import net.helcel.beans.countries.World import net.helcel.beans.helper.AUTO_GROUP import net.helcel.beans.helper.Data import net.helcel.beans.helper.Groups +import net.helcel.beans.helper.NO_GROUP import net.helcel.beans.helper.Settings import net.helcel.beans.helper.Theme.getContrastColor @@ -116,80 +120,82 @@ fun StatsList(activeMode: LocType, countMode: Boolean) { @Composable fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) { val context = LocalContext.current - val isRegionalStats = remember { Settings.isRegionalStats(context) } + val isCascadeStats = remember { Settings.isCascadeStats(context) } + val visits by Data.visits.visitsFlow.collectAsState() - val countries = remember { World.WWW.children.flatMap { it.children } } val continents = remember { World.WWW.children.toList() } - - val count = remember(group, activeMode, isRegionalStats) { - when (activeMode) { - LocType.WORLD -> continents.filter { continent -> - if (group.key == AUTO_GROUP) { - val isUncat = Data.visits.getVisited(continent) == AUTO_GROUP || continent.children.any { Data.visits.getVisited(it) == AUTO_GROUP } - val isInRealGroup = Data.groups.groupsFlow.value.any { g -> - Data.visits.getVisited(continent) == g.key || continent.children.any { Data.visits.getVisited(it) == g.key } - } - isUncat && (!isRegionalStats || !isInRealGroup) - } else { - Data.visits.getVisited(continent) == group.key || (isRegionalStats && continent.children.any { Data.visits.getVisited(it) == group.key }) - } - }.size - - LocType.COUNTRY -> countries.filter { country -> - if (group.key == AUTO_GROUP) { - val isUncat = Data.visits.getVisited(country) == AUTO_GROUP || country.children.any { Data.visits.getVisited(it) == AUTO_GROUP } - val isInRealGroup = Data.groups.groupsFlow.value.any { g -> - Data.visits.getVisited(country) == g.key || country.children.any { Data.visits.getVisited(it) == g.key } - } - isUncat && (!isRegionalStats || !isInRealGroup) - } else { - Data.visits.getVisited(country) == group.key || (isRegionalStats && country.children.any { Data.visits.getVisited(it) == group.key }) - } - }.size - - LocType.STATE -> countries.flatMap { it.children }.filter { region -> - Data.visits.getVisited(region) == group.key - }.size - - else -> 0 + val countries = remember { + World.WWW.children.flatMap { + if (it is Country) listOf(it) else it.children } } - val area = remember(group, activeMode, isRegionalStats) { - when (activeMode) { - LocType.WORLD -> continents.filter { continent -> - if (group.key == AUTO_GROUP) { - val isUncat = Data.visits.getVisited(continent) == AUTO_GROUP || continent.children.any { Data.visits.getVisited(it) == AUTO_GROUP } - val isInRealGroup = Data.groups.groupsFlow.value.any { g -> - Data.visits.getVisited(continent) == g.key || continent.children.any { Data.visits.getVisited(it) == g.key } - } - isUncat && (!isRegionalStats || !isInRealGroup) - } else { - Data.visits.getVisited(continent) == group.key || (isRegionalStats && continent.children.any { Data.visits.getVisited(it) == group.key }) - } - }.sumOf { it.area } - - LocType.COUNTRY -> countries.filter { country -> - if (group.key == AUTO_GROUP) { - val isUncat = Data.visits.getVisited(country) == AUTO_GROUP || country.children.any { Data.visits.getVisited(it) == AUTO_GROUP } - val isInRealGroup = Data.groups.groupsFlow.value.any { g -> - Data.visits.getVisited(country) == g.key || country.children.any { Data.visits.getVisited(it) == g.key } - } - isUncat && (!isRegionalStats || !isInRealGroup) - } else { - Data.visits.getVisited(country) == group.key || (isRegionalStats && country.children.any { Data.visits.getVisited(it) == group.key }) - } - }.sumOf { it.area } - - LocType.STATE -> countries.flatMap { it.children }.filter { region -> - Data.visits.getVisited(region) == group.key - }.sumOf { it.area } - - else -> 0 + val visitedItems = remember(group, activeMode, isCascadeStats, visits) { + val list = when (activeMode) { + LocType.WORLD -> continents + LocType.COUNTRY -> countries + LocType.STATE -> countries.flatMap { it.children } + else -> emptyList() } + + fun getExplicitColor(l: GeoLoc): Int? { + val c = visits.getOrDefault(l.code, 0) + return if (c != NO_GROUP && c != AUTO_GROUP) c else null + } + + fun getEffectiveGroup(l: GeoLoc): Int { + getExplicitColor(l)?.let { return it } + + if (isCascadeStats) { + // Parent -> Child (closest explicit ancestor) + val country = countries.find { it.children.contains(l) } + val countryColor = country?.let { getExplicitColor(it) } + if (countryColor != null) return countryColor + + val continent = continents.find { + it.children.contains(l) || (country != null && it.children.contains(country)) + } + val continentColor = continent?.let { getExplicitColor(it) } + if (continentColor != null) return continentColor + + val worldColor = getExplicitColor(World.WWW) + if (worldColor != null) return worldColor + + // Child -> Parent (most common explicit descendant) + val descendants = mutableListOf() + fun collect(curr: GeoLoc) { + curr.children.forEach { + descendants.add(it) + collect(it) + } + } + collect(l) + val descendantColors = descendants.mapNotNull { getExplicitColor(it) } + if (descendantColors.isNotEmpty()) { + return descendantColors.groupBy { it }.maxByOrNull { it.value.size }?.key ?: AUTO_GROUP + } + } else { + if (l.type == LocType.WORLD || l.type == LocType.COUNTRY) { + val descendants = mutableListOf() + fun collect(curr: GeoLoc) { + curr.children.forEach { descendants.add(it); collect(it) } + } + collect(l) + val dc = descendants.mapNotNull { getExplicitColor(it) } + if (dc.isNotEmpty()) return dc.groupBy { it }.maxByOrNull { it.value.size }?.key ?: AUTO_GROUP + } + } + + return AUTO_GROUP + } + + list.filter { getEffectiveGroup(it) == group.key } } - val displayValue = if (countMode) count.toString() else stringResource(R.string.number_with_unit, area, "km²") + val count = visitedItems.size + val area = visitedItems.sumOf { it.area.toLong() } + + val displayValue = if (countMode) count.toString() else stringResource(R.string.number_with_unit, area.toInt(), "km²") val backgroundColor = group.color.color val textColor = getContrastColor(backgroundColor) diff --git a/app/src/main/java/net/helcel/beans/helper/Groups.kt b/app/src/main/java/net/helcel/beans/helper/Groups.kt index 6b1534b..1d88055 100644 --- a/app/src/main/java/net/helcel/beans/helper/Groups.kt +++ b/app/src/main/java/net/helcel/beans/helper/Groups.kt @@ -43,6 +43,13 @@ class Groups(val id: Int, @SerialName("grps") private val groups: HashMapGroups Regional mode Enable tracking of states and provinces - key_regional_stats - Regional statistics - Count countries with visited regions + key_cascade_stats + Cascade statistics + Include parents and children in the counts Enable multiple colors About Appearance