19 Commits

Author SHA1 Message Date
soraefir d78ef0b38b Update version name 2026-07-09 20:22:50 +02:00
soraefir 30eb5657ce Fix projection selection 2026-07-09 20:22:49 +02:00
soraefir 4085149e88 Improve settings and regional stats 2026-07-09 20:22:49 +02:00
bot 1788d734d2 Merge pull request 'Update dependency mapshaper to v0.7.41' (#548) from renovate/mapshaper-0.x-lockfile into main 2026-07-09 04:01:18 +02:00
Renovate Bot b53215e4ba Update dependency mapshaper to v0.7.41 2026-07-09 02:01:09 +00:00
bot df19f9640f Merge pull request 'Update dependency mapshaper to v0.7.40' (#547) from renovate/mapshaper-0.x-lockfile into main 2026-07-08 04:01:15 +02:00
Renovate Bot 31aa9630d0 Update dependency mapshaper to v0.7.40 2026-07-08 02:01:04 +00:00
bot b1019aeb0b Merge pull request 'Update dependency mapshaper to v0.7.39' (#546) from renovate/mapshaper-0.x-lockfile into main 2026-07-07 04:01:17 +02:00
Renovate Bot 63aa14e31a Update dependency mapshaper to v0.7.39 2026-07-07 02:01:05 +00:00
soraefir 2d87507fa4 Merge branch '9' 2026-07-06 21:52:27 +02:00
Ady bb8dc15554 fix app icon adapted to the user theme colors 2026-07-06 13:51:41 -04:00
Ady b3369f6390 fix StatsActivity title 2026-07-06 13:50:08 -04:00
bot 46df3b5dbe Merge pull request 'Update dependency mapshaper to v0.7.38' (#545) from renovate/mapshaper-0.x-lockfile into main 2026-07-06 04:01:48 +02:00
Renovate Bot 3ab93c7342 Update dependency mapshaper to v0.7.38 2026-07-06 02:01:35 +00:00
sora a8c4529ee8 Update app/build.gradle 2026-07-05 15:04:10 +02:00
bot e7a3f09547 Merge pull request 'Update dependency mapshaper to v0.7.37' (#544) from renovate/mapshaper-0.x-lockfile into main 2026-07-05 04:01:31 +02:00
Renovate Bot 579cf75ca2 Update dependency mapshaper to v0.7.37 2026-07-05 02:01:19 +00:00
bot 121b34c034 Merge pull request 'Lock file maintenance' (#543) from renovate/lock-file-maintenance into main 2026-07-04 04:01:37 +02:00
Renovate Bot 97014e9454 Lock file maintenance 2026-07-04 02:01:35 +00:00
9 changed files with 346 additions and 232 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ android {
applicationId 'net.helcel.beans' applicationId 'net.helcel.beans'
minSdk = 28 minSdk = 28
targetSdk = 37 targetSdk = 37
versionName "1.3b" versionName "1.5"
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1 versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
} }
signingConfigs { signingConfigs {
@@ -14,7 +14,6 @@ import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@@ -22,10 +21,11 @@ import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Percent import androidx.compose.material.icons.filled.Percent
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
@@ -43,8 +43,8 @@ import net.helcel.beans.svg.SVGWrapper
class MainScreen : ComponentActivity() { class MainScreen : ComponentActivity() {
private lateinit var psvg: SVGWrapper private var psvg by mutableStateOf<SVGWrapper?>(null)
private lateinit var css: CSSWrapper private var css by mutableStateOf<CSSWrapper?>(null)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -53,14 +53,19 @@ class MainScreen : ComponentActivity() {
Data.loadData(this, Int.MIN_VALUE) Data.loadData(this, Int.MIN_VALUE)
GeoLocImporter.importStates(this) GeoLocImporter.importStates(this)
refreshProjection()
setContent { setContent {
SysTheme { SysTheme {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) { val currentPsvg = psvg
AppNavHost(psvg, css) val currentCss = css
if (currentPsvg != null && currentCss != null) {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) {
AppNavHost(currentPsvg, currentCss)
}
} }
} }
} }
refreshProjection()
} }
@Composable @Composable
@@ -105,19 +110,25 @@ class MainScreen : ComponentActivity() {
@Composable @Composable
fun MapScreen(psvg: SVGWrapper, css: CSSWrapper) { fun MapScreen(psvg: SVGWrapper, css: CSSWrapper) {
Box { Box {
val opt: RenderOptions = RenderOptions.create() val cssContent = css.get()
opt.css(css.get()) val drawable = remember(psvg, css, cssContent) {
val drawable = remember(psvg, css) { val opt: RenderOptions = RenderOptions.create()
opt.css(cssContent)
PictureDrawable(psvg.get()?.renderToPicture(opt)) PictureDrawable(psvg.get()?.renderToPicture(opt))
} }
AndroidView(factory = { ctx -> AndroidView(
PhotoView(ctx).apply { factory = { ctx ->
setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null) PhotoView(ctx).apply {
setImageDrawable(drawable) setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
maximumScale = 32f maximumScale = 64f
scaleType = ImageView.ScaleType.FIT_CENTER scaleType = ImageView.ScaleType.FIT_CENTER
} }
}, modifier = Modifier.fillMaxSize()) },
update = { view ->
view.setImageDrawable(drawable)
},
modifier = Modifier.fillMaxSize()
)
} }
} }
@@ -48,23 +48,22 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.preference.PreferenceManager
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.Settings
import androidx.core.content.edit import androidx.core.content.edit
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.preference.PreferenceManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.helcel.beans.R
import net.helcel.beans.activity.sub.AboutScreen import net.helcel.beans.activity.sub.AboutScreen
import net.helcel.beans.activity.sub.EditPlaceColorDialog import net.helcel.beans.activity.sub.EditPlaceColorDialog
import net.helcel.beans.activity.sub.EditPlaceDialog
import net.helcel.beans.activity.sub.LicenseScreen import net.helcel.beans.activity.sub.LicenseScreen
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.Data import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Settings
@Composable @Composable
fun SysTheme( fun SysTheme(
@@ -156,154 +155,36 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
val defaultTheme = stringResource(R.string.system) val defaultTheme = stringResource(R.string.system)
val keyProjection = stringResource(R.string.key_projection) val keyProjection = stringResource(R.string.key_projection)
val keyGroup = stringResource(R.string.key_group) val keyGroup = stringResource(R.string.key_group)
val keyRegional = stringResource(R.string.key_regional)
val keyRegionalStats = stringResource(R.string.key_regional_stats)
val offString = stringResource(R.string.off) val offString = stringResource(R.string.off)
val onString = stringResource(R.string.on)
var showEdit by remember { mutableStateOf(false) }
var theme by remember { mutableStateOf(prefs.getString(keyTheme, defaultTheme)!!) } var theme by remember { mutableStateOf(prefs.getString(keyTheme, defaultTheme)!!) }
var projection by remember { mutableStateOf(prefs.getString(keyProjection, "default")!!) } var projection by remember { mutableStateOf(prefs.getString(keyProjection, "default")!!) }
var groups by remember { mutableStateOf(prefs.getString(keyGroup,offString)!!) } 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)!!) }
if(showEdit) var showGroupDialog by remember { mutableStateOf(false) }
EditPlaceDialog(true) { var showRegionalDialog by remember { mutableStateOf(false) }
showEdit = false var showLoad by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
if (showGroupDialog) {
EditPlaceColorDialog(
deleteMode = true,
onDismiss = {
val g = Data.selected_group val g = Data.selected_group
if (it && g != null) { if (g != null) {
Data.visits.reassignAllVisitedToGroup(g.key) Data.visits.reassignAllVisitedToGroup(g.key)
Data.saveData() Data.saveData()
} }
} showGroupDialog = false
})
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.background(MaterialTheme.colors.background)
) {
item {
Text(
"Theme", style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
MultiPreference(arrayOf(stringResource(R.string.system),stringResource(R.string.light),stringResource(R.string.dark)), theme) { newTheme ->
theme = newTheme
prefs.edit { putString(keyTheme, newTheme) }
}
HorizontalDivider()
}
item {
Text(
"Map Projection",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
MultiPreference(arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)), projection) { newProj ->
projection = newProj
prefs.edit { putString(keyProjection, newProj) }
Settings.refreshProjection()
}
HorizontalDivider()
}
item {
Text(
"Groups",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
var showDialog by remember{mutableStateOf(false)}
if(showDialog){
EditPlaceColorDialog(
deleteMode = true,
onDismiss = {
val g = Data.selected_group
if (g != null) {
Data.visits.reassignAllVisitedToGroup(g.key)
Data.saveData()
}
showDialog = false
})
}
MultiPreference(
arrayOf(stringResource(R.string.on), stringResource(R.string.off)),
groups
) { key ->
if (key == offString) {
showDialog=true
}
groups = key
prefs.edit { putString(keyGroup, key) }
}
HorizontalDivider()
}
item {
Text(
text = "Regional",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier
.padding(top = 16.dp)
.clickable(onClick = {}),
)
RegionalScreen()
HorizontalDivider()
}
item{
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri -> Data.doImport(context, uri) }
)
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = {
launcher.launch(arrayOf("*/*"))
}, modifier = Modifier
.fillMaxWidth(fraction = 0.4f)
.padding(vertical = 8.dp)) {
Text("Import")
}
Spacer(
modifier = Modifier.fillMaxWidth(0.4f)
)
Button(onClick = {
Data.doExport(context)
}, modifier = Modifier
.fillMaxWidth(fraction = 1f)
.padding(vertical = 8.dp)) {
Text("Export")
}
}
HorizontalDivider()
}
item {
PreferenceButton("Licenses") {
if (navController.currentDestination?.route != "licenses")
navController.navigate("licenses")
}
PreferenceButton("About") {
if (navController.currentDestination?.route != "about")
navController.navigate("about")
}
}
}
} }
@Composable if (showRegionalDialog) {
fun RegionalScreen() {
val context = LocalContext.current
val keyRegional = stringResource(R.string.key_regional)
val offString = stringResource(R.string.off)
val onString = stringResource(R.string.on)
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var selected by remember { mutableStateOf(prefs.getString(keyRegional, offString)!!)}
var regional by remember{ mutableStateOf(prefs.getString(keyRegional, offString)!!)}
var showDialog by remember{mutableStateOf(false)}
var showLoad by remember{mutableStateOf(false)}
if(showDialog)
Dialog( Dialog(
content = { content = {
Column( Column(
@@ -312,26 +193,27 @@ fun RegionalScreen() {
MaterialTheme.colors.background, MaterialTheme.colors.background,
RoundedCornerShape(corner = CornerSize(16.dp)) RoundedCornerShape(corner = CornerSize(16.dp))
) )
.padding(16.dp),){ .padding(16.dp),
Text(style=MaterialTheme.typography.caption, text= stringResource(R.string.delete_regions)) ) {
Text(
style = MaterialTheme.typography.caption,
text = stringResource(R.string.delete_regions)
)
Button(onClick = { Button(onClick = {
GeoLocImporter.clearStates() GeoLocImporter.clearStates()
regional= selected regional = offString
prefs.edit { prefs.edit { putString(keyRegional, offString) }
putString( showRegionalDialog = false
keyRegional, }) {
regional Text(stringResource(R.string.ok))
)
}
showDialog=false
}){
Text(stringResource(R.string.ok))
} }
} }
}, },
onDismissRequest = { showDialog=false } onDismissRequest = { showRegionalDialog = false }
) )
if(showLoad){ }
if (showLoad) {
Dialog( Dialog(
content = { content = {
CircularProgressIndicator( CircularProgressIndicator(
@@ -343,41 +225,153 @@ fun RegionalScreen() {
onDismissRequest = {} onDismissRequest = {}
) )
} }
val scope = rememberCoroutineScope()
MultiPreference(arrayOf(stringResource(R.string.on),stringResource(R.string.off)),regional) { key -> LazyColumn(
when (key) { modifier = Modifier
offString -> { showDialog=true .fillMaxSize()
selected=key .padding(16.dp)
.background(MaterialTheme.colors.background)
) {
item {
Text(
stringResource(R.string.key_theme),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
MultiPreference(
arrayOf(
stringResource(R.string.system),
stringResource(R.string.light),
stringResource(R.string.dark)
), theme
) { newTheme ->
theme = newTheme
prefs.edit { putString(keyTheme, newTheme) }
}
HorizontalDivider()
}
item {
Text(
stringResource(R.string.key_projection),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
MultiPreference(
arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)),
projection
) { newProj ->
projection = newProj
prefs.edit { putString(keyProjection, newProj) }
Settings.refreshProjection()
}
HorizontalDivider()
}
item {
SettingSwitch(
label = stringResource(R.string.key_group),
subtitle = stringResource(R.string.key_group_desc),
isChecked = groups == onString,
onCheckedChange = { isChecked ->
if (!isChecked) {
showGroupDialog = true
} else {
groups = onString
prefs.edit { putString(keyGroup, onString) }
} }
onString -> { }
regional = key )
prefs.edit { putString(keyRegional, key) } HorizontalDivider()
showLoad=true }
item {
SettingSwitch(
label = stringResource(R.string.key_regional),
subtitle = stringResource(R.string.key_regional_desc),
isChecked = regional == onString,
onCheckedChange = { isChecked ->
if (isChecked) {
regional = onString
prefs.edit { putString(keyRegional, onString) }
showLoad = true
scope.launch { scope.launch {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
GeoLocImporter.importStates(context, true) GeoLocImporter.importStates(context, true)
} }
showLoad = false showLoad = false
} }
} else {
showRegionalDialog = true
} }
} }
)
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 {
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri -> Data.doImport(context, uri) }
)
Row(
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp)
) {
Button(
onClick = {
launcher.launch(arrayOf("*/*"))
}, modifier = Modifier
.weight(1f)
) {
Text(stringResource(R.string.action_import))
}
Spacer(modifier = Modifier.size(16.dp))
Button(
onClick = {
Data.doExport(context)
}, modifier = Modifier
.weight(1f)
) {
Text(stringResource(R.string.action_export))
}
}
HorizontalDivider()
}
item {
PreferenceButton(stringResource(R.string.licenses)) {
if (navController.currentDestination?.route != "licenses")
navController.navigate("licenses")
}
PreferenceButton(stringResource(R.string.about)) {
if (navController.currentDestination?.route != "about")
navController.navigate("about")
}
}
}
} }
@Composable @Composable
fun MultiPreference(list: Array<String>, selected: String, onSelected: (String) -> Unit) { fun MultiPreference(list: Array<String>, selected: String, onSelected: (String) -> Unit) {
Column(Modifier.padding(2.dp)) { Column(Modifier.padding(vertical = 2.dp)) {
list.forEach { value -> list.forEach { value ->
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(36.dp) .height(42.dp)
.clickable { onSelected(value) }) { .clickable { onSelected(value) }) {
RadioButton(selected = selected == value, onClick = { onSelected(value) }) RadioButton(selected = selected == value, onClick = { onSelected(value) })
Text( Text(
value, modifier = Modifier.padding(start = 8.dp), value, modifier = Modifier.padding(start = 12.dp),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground, color = MaterialTheme.colors.onBackground,
) )
} }
@@ -392,4 +386,39 @@ fun PreferenceButton(text: String, onClick: () -> Unit) {
.padding(vertical = 8.dp)) { .padding(vertical = 8.dp)) {
Text(text) Text(text)
} }
} }
@Composable
fun SettingSwitch(
label: String,
subtitle: String? = null,
isChecked: Boolean,
onCheckedChange: (Boolean) -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onCheckedChange(!isChecked) }
.padding(vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text=label,
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
if (subtitle != null) {
Text(
text = subtitle,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
}
}
androidx.compose.material.Switch(
checked = isChecked,
onCheckedChange = onCheckedChange
)
}
}
@@ -37,7 +37,7 @@ import net.helcel.beans.countries.World
import net.helcel.beans.helper.AUTO_GROUP import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Groups import net.helcel.beans.helper.Groups
import net.helcel.beans.helper.Settings.isRegional import net.helcel.beans.helper.Settings
import net.helcel.beans.helper.Theme.getContrastColor import net.helcel.beans.helper.Theme.getContrastColor
private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE) private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE)
@@ -46,7 +46,7 @@ private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE)
fun StatsScreen( fun StatsScreen(
onExit: ()-> Unit onExit: ()-> Unit
) { ) {
val modes = if (isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2) val modes = if (Settings.isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2)
var selectedTab by remember { mutableIntStateOf(0) } var selectedTab by remember { mutableIntStateOf(0) }
var countMode by remember { mutableStateOf(true) } var countMode by remember { mutableStateOf(true) }
@@ -57,7 +57,7 @@ fun StatsScreen(
TopAppBar( TopAppBar(
title = { title = {
Row(verticalAlignment = Alignment.CenterVertically){ Row(verticalAlignment = Alignment.CenterVertically){
Text(text=stringResource(R.string.action_edit), modifier = Modifier.weight(1f)) Text(text=stringResource(R.string.action_stat), modifier = Modifier.weight(1f))
Button(onClick = { countMode = !countMode }) { Button(onClick = { countMode = !countMode }) {
Text(if (countMode) "Count" else "Area") Text(if (countMode) "Count" else "Area")
} }
@@ -115,23 +115,78 @@ fun StatsList(activeMode: LocType, countMode: Boolean) {
@Composable @Composable
fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) { fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) {
val context = LocalContext.current
val isRegionalStats = remember { Settings.isRegionalStats(context) }
val visited = remember(group, activeMode) { val countries = remember { World.WWW.children.flatMap { it.children } }
Data.visits.getVisitedByValue(group.key) 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 count = when (activeMode) { val area = remember(group, activeMode, isRegionalStats) {
LocType.WORLD -> World.WWW.children.filter { it.code in visited }.size when (activeMode) {
LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.size LocType.WORLD -> continents.filter { continent ->
LocType.STATE -> World.WWW.children.flatMap { a->a.children.flatMap { b->b.children.filter { c->c.code in visited } } }.size if (group.key == AUTO_GROUP) {
else -> 0 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 }
val area = when (activeMode) { LocType.COUNTRY -> countries.filter { country ->
LocType.WORLD -> World.WWW.children.filter { it.code in visited }.sumOf { it.area } if (group.key == AUTO_GROUP) {
LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.sumOf { it.area } val isUncat = Data.visits.getVisited(country) == AUTO_GROUP || country.children.any { Data.visits.getVisited(it) == AUTO_GROUP }
LocType.STATE -> World.WWW.children.flatMap { a->a.children.flatMap { b->b.children.filter { c->c.code in visited } } }.sumOf { it.area } val isInRealGroup = Data.groups.groupsFlow.value.any { g ->
else -> 0 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 displayValue = if (countMode) count.toString() else stringResource(R.string.number_with_unit, area, "km²") val displayValue = if (countMode) count.toString() else stringResource(R.string.number_with_unit, area, "km²")
@@ -29,6 +29,13 @@ object Settings {
) )
} }
fun isRegionalStats(ctx: Context): Boolean {
return getBooleanValue(
ctx,
sp.getString(ctx.getString(R.string.key_regional_stats), ctx.getString(R.string.off))
)
}
fun getStatPref(ctx: Context): String? { fun getStatPref(ctx: Context): String? {
return sp.getString( return sp.getString(
ctx.getString(R.string.key_stats), ctx.getString(R.string.key_stats),
@@ -17,7 +17,6 @@
android:pathData="M36,36m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0" android:pathData="M36,36m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0"
android:strokeLineJoin="round" android:strokeLineJoin="round"
android:strokeWidth="2" android:strokeWidth="2"
android:fillColor="@color/blue"
android:strokeColor="#000000" android:strokeColor="#000000"
android:strokeLineCap="round"/> android:strokeLineCap="round"/>
@@ -2,5 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/> <background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/> <monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>
+14 -1
View File
@@ -15,8 +15,21 @@
<string name="percentages">Prefer percentages (%)</string> <string name="percentages">Prefer percentages (%)</string>
<string name="licenses">Licenses</string> <string name="licenses">Licenses</string>
<string name="key_group">Groups</string> <string name="key_group">Groups</string>
<string name="key_regional">Regional</string> <string name="key_regional">Regional mode</string>
<string name="key_regional_desc">Enable tracking of states and provinces</string>
<string name="key_regional_stats">key_regional_stats</string>
<string name="pref_regional_stats">Regional statistics</string>
<string name="pref_regional_stats_desc">Count countries with visited regions</string>
<string name="key_group_desc">Enable multiple colors</string>
<string name="about">About</string> <string name="about">About</string>
<string name="pref_category_appearance">Appearance</string>
<string name="pref_category_features">Features</string>
<string name="pref_category_data">Data</string>
<string name="action_import">Import</string>
<string name="action_export">Export</string>
<string name="import_desc">Load data from a previously exported file.</string>
<string name="export_desc">Save your current data to a file for backup or transfer.</string>
<string name="key_projection_desc">Select how the map is projected onto a 2D surface.</string>
<string name="beans_is_foss">Beans is free and open source software, licensed under the GNU General Public License (version 3 or later)</string> <string name="beans_is_foss">Beans is free and open source software, licensed under the GNU General Public License (version 3 or later)</string>
<string name="beans_repo_uri">https://github.com/helcel-net/beans</string> <string name="beans_repo_uri">https://github.com/helcel-net/beans</string>
<string name="beans_repo">Project repository: %1$s\n Feel free to report issues or contribute.</string> <string name="beans_repo">Project repository: %1$s\n Feel free to report issues or contribute.</string>
+23 -23
View File
@@ -70,9 +70,9 @@
integrity sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w== integrity sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==
"@csstools/css-syntax-patches-for-csstree@^1.1.3": "@csstools/css-syntax-patches-for-csstree@^1.1.3":
version "1.1.5" version "1.1.6"
resolved "https://registry.yarnpkg.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz#b8e26e0fe25e9a6ec607d045470cc46d2f62731e" resolved "https://registry.yarnpkg.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz#3a1e91cefb65b4a74ae7dd78c9b2dd60cdbf3ad4"
integrity sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A== integrity sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==
"@csstools/css-tokenizer@^4.0.0": "@csstools/css-tokenizer@^4.0.0":
version "4.0.0" version "4.0.0"
@@ -1988,9 +1988,9 @@
integrity sha512-tdJz7jaWFu4nR+8b2B+CdPZ6811ighYylWsu2hpsivapzW058yP0KdfZuNY89IiRe5jbKvBGXN3LQdN2KPXVdQ== integrity sha512-tdJz7jaWFu4nR+8b2B+CdPZ6811ighYylWsu2hpsivapzW058yP0KdfZuNY89IiRe5jbKvBGXN3LQdN2KPXVdQ==
"@types/node@*": "@types/node@*":
version "26.1.0" version "26.1.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.0.tgz#aa85f0727fc5611347091c478341c63650903439" resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.1.tgz#bad758d601e97d6cf457d204ee76a35fce7bd119"
integrity sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw== integrity sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==
dependencies: dependencies:
undici-types "~8.3.0" undici-types "~8.3.0"
@@ -2559,16 +2559,16 @@ iconv-lite@^0.6.3:
safer-buffer ">= 2.1.2 < 3.0.0" safer-buffer ">= 2.1.2 < 3.0.0"
iconv-lite@^0.7.2: iconv-lite@^0.7.2:
version "0.7.2" version "0.7.3"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.2.tgz#d0bdeac3f12b4835b7359c2ad89c422a4d1cc72e" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.7.3.tgz#84ee12f963e7de50bc01a13e160a078b3b0f415f"
integrity sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw== integrity sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==
dependencies: dependencies:
safer-buffer ">= 2.1.2 < 3.0.0" safer-buffer ">= 2.1.2 < 3.0.0"
idb-keyval@^6.2.0: idb-keyval@^6.2.0:
version "6.2.6" version "6.3.0"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.6.tgz#6a1204e20dca567f7b8fef2fac5fc52af02fd553" resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.3.0.tgz#3fa4c062fcaddd7e577c51b51f69924bccfb58e6"
integrity sha512-FY64UEhw+5liMzMQ1R9Mw6AF0+wyBrg1CIA1z4CjI/EvT5ty/SvQcWZgd8s9sgaNhX10Y8UzScTh89tEAls5nA== integrity sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==
ieee754@^1.1.13, ieee754@^1.2.1: ieee754@^1.1.13, ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
@@ -2708,9 +2708,9 @@ map-stream@0.0.7:
integrity sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ== integrity sha512-C0X0KQmGm3N2ftbTGBhSyuydQ+vV1LC3f3zPvT3RXHXNZrvfPZcoXp/N5DOa8vedX/rTMm2CjTtivFg2STJMRQ==
mapshaper@^0.7.22: mapshaper@^0.7.22:
version "0.7.35" version "0.7.41"
resolved "https://registry.yarnpkg.com/mapshaper/-/mapshaper-0.7.35.tgz#5de71cea5d322272a77c7fa1c0fac5f491403479" resolved "https://registry.yarnpkg.com/mapshaper/-/mapshaper-0.7.41.tgz#c03fa2aed375abbc92af47008dc81695a604ed23"
integrity sha512-+UuAIVcuRWr3ZR0Lv/weIqUmSn/NMeHxWdfgyc0P2Dbi1NE7PDJ2yUg4utH2wIUqVpMvGzfIG6Uk/mPj/KlGNw== integrity sha512-TC1KL8OtYZPRuq/Wl0AW2LQOic/q/AQbEEiPkCXlN5dGFXPIRj8+Qllc3EWBhN7aViHStI2IIthP2/L7BDzobQ==
dependencies: dependencies:
"@bokuweb/zstd-wasm" "^0.0.27" "@bokuweb/zstd-wasm" "^0.0.27"
"@ngageoint/geopackage" "^4.2.6" "@ngageoint/geopackage" "^4.2.6"
@@ -3293,17 +3293,17 @@ tinyqueue@^2.0.0, tinyqueue@^2.0.3:
resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA== integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
tldts-core@^7.4.4: tldts-core@^7.4.6:
version "7.4.4" version "7.4.6"
resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.4.tgz#a06de228c70a334e26d1597af7a82376e8588bce" resolved "https://registry.yarnpkg.com/tldts-core/-/tldts-core-7.4.6.tgz#4cc8ccdae554742f09e5999708cac5a7d83cb287"
integrity sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg== integrity sha512-TkQNGJIhlEphpHCjKodMTSe23egUZr/g+flI2qkLgiJ/maAzSgXypSLRTNH3nCmqgayEmtcJBiLcfODSAr1xoA==
tldts@^7.0.5: tldts@^7.0.5:
version "7.4.4" version "7.4.6"
resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.4.tgz#343191348abbff80118732a1a84e90fcf95b29df" resolved "https://registry.yarnpkg.com/tldts/-/tldts-7.4.6.tgz#3af5da04b35afd0e00cf0fe2827af30393a28202"
integrity sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g== integrity sha512-rbP0Gyx8b3Ae9yO//CU2wbSnQNoQ66m1nJdSbSHmnwKwzkkz/u8mERYU8T2rmlmy+bJvRNn84yNCW8gYqox44Q==
dependencies: dependencies:
tldts-core "^7.4.4" tldts-core "^7.4.6"
token-types@^4.1.1: token-types@^4.1.1:
version "4.2.1" version "4.2.1"