37 Commits
1.2 ... 1.3b

Author SHA1 Message Date
18725261e3 Update app/build.gradle 2026-06-06 19:34:25 +02:00
soraefir
cc1a0c1aca Fix region saving bug 2026-06-06 19:30:18 +02:00
soraefir
f2a5efcec5 Update saving logic 2026-06-06 18:54:09 +02:00
soraefir
cfa784991b Updated SVGs and improved scripting
# Conflicts:
#	yarn.lock
2026-06-06 18:23:41 +02:00
soraefir
14f8543d3d Fix zoom and topbar 2026-06-06 18:23:26 +02:00
soraefir
5705749d12 Fix Caspian Sea 2026-06-06 18:23:26 +02:00
bot
1d0b814e77 Merge pull request 'Lock file maintenance' (#513) from renovate/lock-file-maintenance into main 2026-06-06 04:01:52 +02:00
Renovate Bot
ecc26f811a Lock file maintenance 2026-06-06 02:01:48 +00:00
bot
8cd93d866b Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.4.0' (#512) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2026-06-05 04:05:29 +02:00
Renovate Bot
6bf4034e1e Update plugin org.jetbrains.kotlin.plugin.serialization to v2.4.0 2026-06-05 02:02:00 +00:00
bot
977321ddb5 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.4.0' (#511) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2026-06-04 04:11:32 +02:00
bot
bee96b5d9b Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.4.0' (#510) from renovate/org.jetbrains.kotlin.android-2.x into main 2026-06-04 04:11:19 +02:00
Renovate Bot
6d21f0da6d Update plugin org.jetbrains.kotlin.plugin.compose to v2.4.0 2026-06-04 02:11:18 +00:00
Renovate Bot
445ccbcac0 Update plugin org.jetbrains.kotlin.android to v2.4.0 2026-06-04 02:02:01 +00:00
bot
225e5bfdc8 Merge pull request 'Lock file maintenance' (#509) from renovate/lock-file-maintenance into main 2026-05-31 04:02:13 +02:00
Renovate Bot
0c446f0d59 Lock file maintenance 2026-05-31 02:02:04 +00:00
bot
7c0b9bda80 Merge pull request 'Update dependency mapshaper to v0.7.22' (#508) from renovate/mapshaper-0.x-lockfile into main 2026-05-30 04:02:10 +02:00
Renovate Bot
eaa0278764 Update dependency mapshaper to v0.7.22 2026-05-30 02:02:02 +00:00
bot
eeac01f638 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.1' (#504) from renovate/com.mikepenz-aboutlibraries-compose-m3-14.x into main 2026-05-30 04:01:32 +02:00
Renovate Bot
9f8d5aaa18 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.1 2026-05-30 02:01:26 +00:00
cdb9d51257 Update README.md 2026-05-29 19:01:20 +02:00
bot
2d6b895f57 Merge pull request 'Update dependency mapshaper to v0.7.21' (#507) from renovate/mapshaper-0.x-lockfile into main 2026-05-28 04:02:20 +02:00
Renovate Bot
80e191a50d Update dependency mapshaper to v0.7.21 2026-05-28 02:02:07 +00:00
bot
4114185a73 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v14.2.1' (#506) from renovate/com.mikepenz.aboutlibraries.plugin-14.x into main 2026-05-26 04:01:40 +02:00
bot
24fe56c02a Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v14.2.1' (#505) from renovate/com.mikepenz-aboutlibraries-core-14.x into main 2026-05-26 04:01:35 +02:00
Renovate Bot
d83234cf5b Update plugin com.mikepenz.aboutlibraries.plugin to v14.2.1 2026-05-26 02:01:33 +00:00
Renovate Bot
e608b22c0c Update dependency com.mikepenz:aboutlibraries-core to v14.2.1 2026-05-26 02:01:31 +00:00
soraefir
f8ed30cd73 Fix Top bar padding on android16+ 2026-05-25 15:20:41 +02:00
soraefir
c50933908c Fix crash due to rename 2026-05-25 14:14:42 +02:00
bot
77edae3585 Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v14.2.1' (#503) from renovate/com.mikepenz-aboutlibraries-14.x into main 2026-05-25 04:01:29 +02:00
Renovate Bot
50c152084e Update dependency com.mikepenz:aboutlibraries to v14.2.1 2026-05-25 02:01:22 +00:00
b0a107c749 Update README.md 2026-05-24 11:58:56 +02:00
bot
91050826b2 Merge pull request 'Lock file maintenance' (#502) from renovate/lock-file-maintenance into main 2026-05-24 04:04:58 +02:00
Renovate Bot
1004119fed Lock file maintenance 2026-05-24 02:04:55 +00:00
d262331d1b Update .github/workflows/build.yml 2026-05-23 14:45:03 +02:00
7440e85987 Update app/build.gradle 2026-05-23 14:42:40 +02:00
2351512af8 Update app/build.gradle 2026-05-23 14:42:30 +02:00
22 changed files with 3437 additions and 16422 deletions

View File

@@ -24,6 +24,8 @@ jobs:
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: set up secrets
run: |
@@ -49,7 +51,9 @@ jobs:
uses: gradle/actions/setup-gradle@v6
- name: Build APK
run: ./gradlew assembleSignedRelease
run: |
VERSION_CODE=$(git rev-list --count HEAD)
./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE
- name: Release
uses: softprops/action-gh-release@v3

View File

@@ -5,6 +5,8 @@
<p>A virtual scratchmap of the world</p>
<a href="https://ko-fi.com/I2I615VP5M"><img src="https://ko-fi.com/img/githubbutton_sm.svg" alt="ko-fi"></a>
<br>
<img src="https://forthebadge.com/images/badges/built-for-android.svg" alt="Built for Android">
<img src="https://forthebadge.com/images/badges/built-with-love.svg" alt="Built with love">
<br>
@@ -71,7 +73,7 @@ Thanks to all contributors, the developers of our dependencies, and our users.
## 📝 License
```
Copyright (C) 2024 Helcel & MYDOLI
Copyright (C) 2026 Helcel & MYDOLI
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by

View File

@@ -1,21 +1,8 @@
def getCommitCount() {
try {
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'git', 'rev-list', '--count', 'HEAD'
standardOutput = stdout
}
return stdout.toString().trim().toInteger()
} catch (ignored) {
return 1
}
}
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.3.21'
id 'org.jetbrains.kotlin.plugin.compose' version '2.3.21'
id 'com.mikepenz.aboutlibraries.plugin' version '14.2.0'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.0'
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.0'
id 'com.mikepenz.aboutlibraries.plugin' version '14.2.1'
}
android {
@@ -28,8 +15,8 @@ android {
applicationId 'net.helcel.beans'
minSdk = 28
targetSdk = 37
versionName "1.2"
versionCode getCommitCount()
versionName "1.3b"
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
}
signingConfigs {
register("release") {
@@ -118,11 +105,17 @@ dependencies {
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:aboutlibraries:14.2.0'
implementation 'com.mikepenz:aboutlibraries-compose-m3:14.2.0'
implementation 'com.mikepenz:aboutlibraries-core:14.2.0'
implementation 'com.mikepenz:aboutlibraries:14.2.1'
implementation 'com.mikepenz:aboutlibraries-compose-m3:14.2.1'
implementation 'com.mikepenz:aboutlibraries-core:14.2.1'
implementation platform('androidx.compose:compose-bom:2026.05.01')
debugImplementation 'androidx.compose.ui:ui-tooling:1.11.2'
}
tasks.configureEach { task ->
if (task.name.startsWith("merge") && task.name.endsWith("Assets")) {
task.outputs.upToDateWhen { false }
}
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 3.9 MiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.0 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 MiB

After

Width:  |  Height:  |  Size: 4.0 MiB

View File

@@ -9,10 +9,12 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
@@ -22,6 +24,8 @@ import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.navigation.NavHostController
import androidx.navigation.compose.NavHost
@@ -46,12 +50,12 @@ class MainScreen : ComponentActivity() {
super.onCreate(savedInstanceState)
actionBar?.hide()
Settings.start(this)
GeoLocImporter.importStates(this)
Data.loadData(this, Int.MIN_VALUE)
GeoLocImporter.importStates(this)
setContent {
SysTheme {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.primary).statusBarsPadding(),) {
AppNavHost(psvg, css)
}
}
@@ -110,6 +114,7 @@ class MainScreen : ComponentActivity() {
PhotoView(ctx).apply {
setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
setImageDrawable(drawable)
maximumScale = 32f
scaleType = ImageView.ScaleType.FIT_CENTER
}
}, modifier = Modifier.fillMaxSize())

View File

@@ -168,8 +168,10 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
EditPlaceDialog(true) {
showEdit = false
val g = Data.selected_group
if (it && g != null)
if (it && g != null) {
Data.visits.reassignAllVisitedToGroup(g.key)
Data.saveData()
}
}
LazyColumn(
@@ -216,8 +218,10 @@ fun SettingsScreen(navController: NavHostController = settingsNav()) {
deleteMode = true,
onDismiss = {
val g = Data.selected_group
if (g != null)
if (g != null) {
Data.visits.reassignAllVisitedToGroup(g.key)
Data.saveData()
}
showDialog = false
})
}

View File

@@ -54,27 +54,51 @@ fun EditPlaceScreenPreview(){
EditPlaceScreen(Group.EEE)
}
fun syncVisited(loc: GeoLoc?=World.WWW){
fun syncVisited(loc: GeoLoc?=World.WWW): Boolean {
var changed = false
loc?.children?.forEach { tt ->
tt.children.forEach {itc->
if(Data.visits.getVisited(itc) in listOf(AUTO_GROUP,NO_GROUP)) {
if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP })
Data.visits.setVisited(itc, AUTO_GROUP)
val newState = if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP })
AUTO_GROUP
else
Data.visits.setVisited(itc, NO_GROUP)
NO_GROUP
if (Data.visits.getVisited(itc) != newState) {
Data.visits.setVisited(itc, newState)
changed = true
}
}
}
if(Data.visits.getVisited(tt) in listOf(AUTO_GROUP,NO_GROUP)) {
if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
Data.visits.setVisited(tt, AUTO_GROUP)
val newState = if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
AUTO_GROUP
else
Data.visits.setVisited(tt, NO_GROUP)
NO_GROUP
if (Data.visits.getVisited(tt) != newState) {
Data.visits.setVisited(tt, newState)
changed = true
}
}
}
// Sync World from Continents
if (loc != null && Data.visits.getVisited(loc) in listOf(AUTO_GROUP, NO_GROUP)) {
val newState = if(loc.children.any { Data.visits.getVisited(it) != NO_GROUP })
AUTO_GROUP
else
NO_GROUP
if (Data.visits.getVisited(loc) != newState) {
Data.visits.setVisited(loc, newState)
changed = true
}
}
return changed
}
@Composable
fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
val visits by Data.visits.visitsFlow.collectAsState()
var showEdit by remember { mutableStateOf(false) }
val tabs : SnapshotStateList<GeoLoc> = remember { mutableStateListOf(loc) }
val ctx = LocalContext.current
@@ -84,7 +108,12 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
selectedTab = tabs.lastIndex
}
SideEffect {
syncVisited()
// visits is used to trigger sync whenever data changes
if (visits.isNotEmpty() || true) {
if (syncVisited()) {
Data.saveData()
}
}
}
BackHandler {
if (tabs.size > 1) tabs.removeAt(tabs.lastIndex)
@@ -95,6 +124,7 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
showEdit = false
if (it) {
Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
syncVisited()
Data.saveData()
if (Data.selected_geoloc!=null && Data.selected_geoloc!!.children.any { itc-> Data.visits.getVisited(itc) != NO_GROUP }) {
@@ -103,6 +133,7 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
}
if (Data.selected_group != null && Data.selected_geoloc != null) {
Data.visits.setVisited(Data.selected_geoloc, Data.selected_group!!.key)
syncVisited()
Data.saveData()
}
Data.selected_geoloc = null
@@ -144,12 +175,14 @@ fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
Data.visits.getVisited(itc)!= NO_GROUP } == true) AUTO_GROUP
else NO_GROUP
)
Data.saveData()
Data.selected_group = null
} else {
Data.selected_group = null
showEdit=true
}
syncVisited()
Data.saveData()
})
}

View File

@@ -43,14 +43,14 @@ object Data {
if (groups.size() == 0) {
groups.setGroup(DEFAULT_GROUP, "Visited",
ContextCompat.getColor(ctx, R.color.blue).toDrawable())
saveData()
}
saveData()
}
fun saveData() {
if(groups.id != visits.id) return
val id = groups.id
sharedPreferences.edit {
sharedPreferences.edit(commit=true) {
putString("groups_$id", groupsSerial.writeTo(groups))
putString("visits_$id", visitsSerial.writeTo(visits))
}

View File

@@ -3,6 +3,7 @@ package net.helcel.beans.helper
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.json.Json
@@ -24,19 +25,25 @@ const val AUTO_GROUP = -1
@Serializable
class Groups(val id: Int, private val groups: HashMap<Int, Group>) {
class Groups(val id: Int, @SerialName("grps") private val groups: HashMap<Int, Group>) {
@kotlinx.serialization.Transient
private val _groupsFlow = MutableStateFlow<List<Group>>(groups.values.toList())
@kotlinx.serialization.Transient
val groupsFlow: StateFlow<List<Group>> = _groupsFlow.asStateFlow()
fun setGroup(key: Int, name: String, col: ColorDrawable) {
val old = groups[key]
if (old != null && old.name == name && old.color.color == col.color) return
groups[key] = Group(key, name, col)
_groupsFlow.value = groups.values.toList()
updateFlow()
}
fun deleteGroup(key: Int) {
groups.remove(key)
updateFlow()
}
private fun updateFlow() {
_groupsFlow.value = groups.values.toList()
}

View File

@@ -19,24 +19,25 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
val visitsFlow: StateFlow<Map<String,Int>> = _visitsFlow
fun setVisited(key: GeoLoc?, b: Int) {
if (key == null)
if (key == null || locs[key.code] == b)
return
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
this[key.code] = b
}
locs[key.code] = b
updateFlow()
}
private fun updateFlow() {
_visitsFlow.value = locs.toMap()
}
fun deleteVisited(key: Int) {
val keysToDelete = locs
.filter { it.value == key }
.map { it.key }
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
keysToDelete.forEach { this.remove(it)}
}
if (keysToDelete.isEmpty()) return
keysToDelete.forEach {
locs.remove(it)
}
updateFlow()
}
fun getVisited(key: GeoLoc): Int {
@@ -60,13 +61,19 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
}
fun reassignAllVisitedToGroup(group: Int) {
var changed = false
val keys = locs.filter { (_, grp) ->
grp !in listOf(NO_GROUP, AUTO_GROUP)
}.keys
keys.forEach {
if (locs[it] != group) {
locs[it] = group
changed = true
}
}
if (changed) {
updateFlow()
}
_visitsFlow.value = locs
}
@OptIn(ExperimentalSerializationApi::class)

View File

@@ -0,0 +1,50 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="128"
android:viewportHeight="128">
<group android:scaleX="0.8918919"
android:scaleY="0.8918919"
android:translateX="6.918919"
android:translateY="6.918919">
<group
android:scaleX="1.5"
android:scaleY="1.5"
android:translateX="10"
android:translateY="10">
<path
android:pathData="M36,36m-28,0a28,28 0,1 1,56 0a28,28 0,1 1,-56 0"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="@color/blue"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
<group
android:scaleX="1.25"
android:scaleY="1.25"
android:translateX="18"
android:translateY="18">
<path
android:pathData="M56.803,45.75c0.599,-0.299 1.157,-0.672 1.663,-1.11 0.804,-0.698 1.463,-1.546 1.94,-2.497 0.599,-1.303 0.883,-2.728 0.829,-4.161 0.034,-1.498 -0.154,-2.994 -0.558,-4.437 -0.451,-1.645 -1.104,-3.229 -1.944,-4.714 -0.967,-2.141 -2.28,-4.108 -3.887,-5.822 -1.242,-1.326 -2.757,-2.366 -4.44,-3.048 -0.877,-0.372 -1.821,-0.56 -2.774,-0.553 -1.402,0.074 -2.729,0.697 -3.882,1.389 -1.285,0.81 -2.244,1.735 -2.771,3.053 -0.739,2.362 0.827,4.821 1.113,5.269 0.003,0.005 0.187,0.281 0.555,0.832 1.512,2.264 1.589,2.358 1.666,2.495 0.646,1.221 1.114,2.528 1.389,3.882 1.043,4.001 1.565,6.001 2.223,6.932 1.208,1.71 3.455,3.414 5.826,3.325 1.059,-0.088 2.093,-0.371 3.05,-0.835Z"
android:strokeWidth="2"
android:fillColor="@color/white"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M22,41c-1.54,0.554 -2.83,1.642 -3.636,3.066 -0.714,1.365 -0.957,2.928 -0.693,4.445 0.178,1.247 0.632,2.439 1.329,3.488 2.032,3.228 5.383,4.423 7,5 1.613,0.561 3.295,0.897 5,1 2.387,0.205 6.923,0.535 11,-2 1.287,-0.665 2.335,-1.713 3,-3 0.661,-1.601 0.661,-3.399 0,-5 -0.389,-1.156 -1.121,-2.165 -2.099,-2.894 -0.919,-0.599 -1.974,-0.956 -3.069,-1.039 -2.057,-0.313 -2.756,0.139 -5.014,0.057 -1.271,-0.019 -2.534,-0.214 -3.753,-0.577 -3.036,-0.95 -3.33,-2.457 -5.889,-2.829 -1.066,-0.158 -2.155,-0.061 -3.177,0.282Z"
android:strokeWidth="2"
android:fillColor="@color/white"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M12.7,26.975c1.758,-3.604 5.311,-3.191 9.58,-7.091 2.701,-2.469 2.858,-4.078 5.613,-5.202 1.407,-0.644 2.984,-0.819 4.498,-0.5 2.053,0.475 3.761,1.891 4.61,3.819 1.116,2.742 -0.314,5.437 -1.398,7.482 -0.951,1.689 -2.167,3.215 -3.602,4.518 -1.479,1.552 -3.161,2.898 -5,4 -2.629,1.541 -6.332,3.711 -9.989,2.43 -1.481,-0.548 -2.768,-1.521 -3.699,-2.796 -1.496,-1.906 -1.735,-4.512 -0.612,-6.659Z"
android:strokeWidth="2"
android:fillColor="@color/white"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</group>
</vector>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Status bar color in dark mode -->
<item name="android:statusBarColor">@color/blue</item>
<!-- Window background in dark mode -->
<item name="android:windowBackground">@color/darkgray</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="colorPrimary">@color/blue</item>
<item name="colorPrimaryDark">@color/blue</item>
<item name="colorAccent">@color/blue</item>
</style>
</resources>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@color/blue</item>
<item name="android:windowBackground">@color/blue</item>
</style>
</resources>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Status bar color -->
<item name="android:statusBarColor">@color/blue</item>
<!-- Window background - shows through statusBarsPadding() area -->
<item name="android:windowBackground">@color/blue</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<!-- Optional: Primary colors for the theme -->
<item name="colorPrimary">@color/blue</item>
<item name="colorPrimaryDark">@color/blue</item>
<item name="colorAccent">@color/blue</item>
</style>
</resources>

View File

@@ -2,5 +2,5 @@
plugins {
id 'com.android.application' version '9.2.1' apply false
id 'com.android.library' version '9.2.1' apply false
id 'org.jetbrains.kotlin.android' version '2.3.21' apply false
id 'org.jetbrains.kotlin.android' version '2.4.0' apply false
}

View File

@@ -1,8 +1,10 @@
#!/bin/node
import {readFileSync, existsSync} from 'fs'
import {readFileSync,writeFileSync,existsSync} from 'fs'
import area from '@turf/area'
import * as turf from '@turf/turf'
import * as path from 'path';
import { JSDOM } from 'jsdom';
const countries =
@@ -24,7 +26,7 @@ const countries =
const groups = {
"EEE":["ALB","AND","AUT","BLR","BEL","BIH","BGR","HRV","CYP","CZE","DNK","EST","FIN","FRA","DEU","GRC","HUN","ISL","IRL","ITA","KAZ","XKO","LVA","LIE","LTU","LUX","MLT","MDA","MCO","MNE","NLD","MKD","NOR","POL","PRT","ROU","RUS","SMR","SRB","SVK","SVN","ESP","SWE","CHE","UKR","GBR","VAT","XAD"],
"EEE":["ALA","ALB","AND","AUT","BLR","BEL","BIH","BGR","HRV","CYP","CZE","DNK","EST","FIN","FRA","DEU","GIB","GGY","GRC","HUN","ISL","IRL","IMN","JEY","ITA","KAZ","XKO","LVA","LIE","LTU","LUX","MLT","MDA","MCO","MNE","NLD","MKD","NOR","SJM","POL","PRT","ROU","RUS","SMR","SRB","SVK","SVN","ESP","SWE","CHE","UKR","GBR","VAT","XAD"],
"ABB":["AFG","ARM","AZE","BHR","BGD","BTN","BRN","KHM","CHN","GEO","HKG","IND","IDN","IRN","IRQ","ISR","JPN","JOR","KWT","KGZ","LAO","LBN","MAC","MYS","MDV","MNG","MMR","NPL","PRK","OMN","PAK","PSE","PHL","QAT","SAU","SGP","KOR","LKA","SYR","TWN","TJK","THA","TLS","TUR","TKM","ARE","UZB","VNM","YEM","ZNC"],
"FFF":["DZA","AGO","BDI","BEN","BWA","BFA","BDI","CPV","CMR","CAF","TCD","COM","COG","COD","CIV","DJI","EGY","GNQ","ERI","SWZ","ETH","GAB","GMB","GHA","GIN","GNB","KEN","LSO","LBR","LBY","MDG","MWI","MLI","MRT","MUS","MYT","MAR","MOZ","NAM","NER","NGA","COD","REU","RWA","STP","SEN","SYC","SLE","SOM","ZAF","SSD","SHN","SDN","TZA","TGO","TUN","UGA","COD","ZMB","ZWE","ESH"],
"NNN":["ABW","AIA","ATG","BHS","BRB","BLZ","BMU","VGB","CAN","CYM","CRI","CUB","CUW","DMA","DOM","SLV","GRL","GRD","GLP","GTM","HTI","HND","JAM","MTQ","MEX","MSR","ANT","CUW","NIC","PAN","PRI","KNA","LCA","MAF","SPM","VCT","TTO","TCA","USA","XCL"],
@@ -32,7 +34,6 @@ const groups = {
"UUU":["ASM","AUS","COK","FJI","PYF","GUM","KIR","MHL","FSM","NRU","NCL","NZL","NIU","NFK","MNP","PLW","PNG","PCN","SLB","TKL","TON","TUV","VUT","WLF"],
"XXX":[
"ATA", // Antarctica: not in any other region
"ALA",// Åland Islands: an autonomous region of Finland, but not a member of the EU or UN
"BES",// Bonaire, Sint Eustatius and Saba: special municipalities of the Netherlands in the Caribbean
"BVT",// Bouvet Island: an uninhabited territory of Norway in the South Atlantic
"IOT",// British Indian Ocean Territory: a British overseas territory in the Indian Ocean
@@ -40,16 +41,11 @@ const groups = {
"CCK",// Cocos (Keeling) Islands: an Australian external territory in the Indian Ocean
"FRO",// Faroe Islands: an autonomous region of Denmark
"ATF",// French Southern and Antarctic Lands: a territory of France located in the southern Indian Ocean
"GIB",// Gibraltar: a British overseas territory located at the southern tip of the Iberian Peninsula
"GGY",// Guernsey: a British Crown dependency in the English Channel
"HMD",// Heard Island and McDonald Islands: an uninhabited Australian external territory in the southern Indian Ocean
"IMN",// Isle of Man: a British Crown dependency located in the Irish Sea
"JEY",// Jersey: a British Crown dependency located in the English Channel
"BLM",// Saint Barthélemy: an overseas collectivity of France in the Caribbean
"WSM",// Samoa: an independent island nation in the South Pacific
"SXM",// Sint Maarten: a constituent country of the Kingdom of the Netherlands in the Caribbean
"SGS",// South Georgia and the South Sandwich Islands: a British overseas territory in the southern Atlantic Ocean
"SJM",// Svalbard and Jan Mayen: an archipelago administered by Norway
"UMI",// United States Minor Outlying Islands: a collection of nine insular areas of the United States
"VIR",// United States Virgin Islands: an unincorporated territory of the United States in the Caribbean
]
@@ -166,4 +162,52 @@ async function run(){
}
}
function fixSvg(svgPath) {
const countryToRegion = {};
for (const [region, countries] of Object.entries(groups)) {
countries.forEach(country => countryToRegion[country] = region);
}
const absoluteInputPath = path.resolve(svgPath);
if (!existsSync(absoluteInputPath)) {
throw new Error(`Input file not found at: ${absoluteInputPath}`);
}
const svgContent = readFileSync(absoluteInputPath, 'utf8');
const dom = new JSDOM(svgContent, { contentType: 'image/svg+xml' });
const document = dom.window.document;
const svgRoot = document.querySelector('svg');
if (!svgRoot) {
throw new Error("Invalid or empty SVG structure encountered.");
}
if (svgRoot.getAttribute('data-processed') === 'true') {
console.log(`Skipping: File at "${svgPath}" has already been processed.`);
return;
}
const elementGroups = Array.from(svgRoot.querySelectorAll('g'));
elementGroups.forEach(group => {
const currentId = group.getAttribute('id') || '';
const baseIsoCode = currentId.replace(/\d+$/, '');
const regionKey = countryToRegion[baseIsoCode] || 'XXXX';
let regionGroup = svgRoot.querySelector(`g[id="${regionKey}"]`);
if (!regionGroup) {
regionGroup = document.createElementNS('http://w3.org', 'g');
regionGroup.setAttribute('id', regionKey);
svgRoot.appendChild(regionGroup);
}
regionGroup.appendChild(group);
});
svgRoot.setAttribute('data-processed', 'true');
const absoluteOutputPath = path.resolve(svgPath);
let cleanXmlString = svgRoot.outerHTML;
cleanXmlString = cleanXmlString.replace(/[\r\n]+/g, '');
cleanXmlString = cleanXmlString.replace(/xmlns="http:\/\/w3\.org"\s?/g, '');
cleanXmlString = cleanXmlString.replace(/xmlns="http:\/\/www\.w3\.org\/2000\/svg"\s?/g, '');
cleanXmlString = cleanXmlString.replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
writeFileSync(absoluteOutputPath, cleanXmlString, 'utf8');
}
run()
fixSvg("./app/src/main/assets/loxim01.svg")
fixSvg("./app/src/main/assets/webmercator01.svg")
fixSvg("./app/src/main/assets/aeqd01.svg")

View File

@@ -8,10 +8,11 @@ GADM_BASEPATH="https://geodata.ucdavis.edu/gadm"
mapshaper="./node_modules/mapshaper/bin/mapshaper"
ATA_URL="https://media.githubusercontent.com/media/wmgeolab/geoBoundaries/905b0baf5f4fb3b9ccf45293647dcacdb2b799d4/releaseData/gbOpen/ATA/ADM0/geoBoundaries-ATA-ADM0_simplified.geojson"
# Caspian Sea: "XCA"
countries=(
"AFG" "XAD" "ALA" "ALB" "DZA" "ASM" "AND" "AGO" "AIA" "ATG" "ARG" "ARM" "ABW" "AUS" "AUT" "AZE"
"BHS" "BHR" "BGD" "BRB" "BLR" "BEL" "BLZ" "BEN" "BMU" "BTN" "BOL" "BES" "BIH" "BWA" "BVT" "BRA" "IOT" "VGB" "BRN" "BGR" "BFA" "BDI" "KHM"
"CMR" "CAN" "CPV" "XCA" "CYM" "CAF" "TCD" "CHL" "CHN" "CXR" "XCL" "CCK" "COL" "COM" "COK" "CRI" "CIV" "HRV" "CUB" "CUW" "CYP" "CZE" "COD"
"CMR" "CAN" "CPV" "CYM" "CAF" "TCD" "CHL" "CHN" "CXR" "XCL" "CCK" "COL" "COM" "COK" "CRI" "CIV" "HRV" "CUB" "CUW" "CYP" "CZE" "COD"
"DNK" "DJI" "DMA" "DOM" "ECU" "EGY" "SLV" "GNQ" "ERI" "EST" "ETH" "FLK" "FRO" "FJI" "FIN" "FRA" "GUF" "PYF" "ATF"
"GAB" "GMB" "GEO" "DEU" "GHA" "GIB" "GRC" "GRL" "GRD" "GLP" "GUM" "GTM" "GGY" "GIN" "GNB" "GUY" "HTI" "HMD" "HND" "HUN"
"ISL" "IND" "IDN" "IRN" "IRQ" "IRL" "IMN" "ISR" "ITA" "JAM" "JPN" "JEY" "JOR" "KAZ" "KEN" "KIR" "XKO" "KWT" "KGZ"
@@ -115,6 +116,47 @@ toSVG_1() {
"$mapshaper" -i combine-files ${input_files[@]} -proj aeqd +lat_0=90 -simplify 0.005 weighted keep-shapes resolution=1200x1200 -o ./app/src/main/assets/aeqd1.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID
}
generate_svg_map() {
local OUT_FILE="$1" # First argument: Output destination path
local PROJ_ARGS="$2" # Second argument: Projection parameters
shift 2 # Remove the first two arguments, leaving only the files
local FILES_TO_RUN=("$@") # Capture all remaining arguments as the file array
echo "Generating: $OUT_FILE using projection [$PROJ_ARGS]"
echo "Processing ${#FILES_TO_RUN[@]} files..."
local JS_PIPELINE_LOGIC="
const conflicts = {
'Z01': 'IND', 'Z04': 'IND', 'Z05': 'IND', 'Z07': 'IND', 'Z09': 'IND',
'Z02': 'CHN', 'Z03': 'CHN','Z08': 'CHN',
'Z06': 'PAK'
};
let rawCode = GID_0 || 'UNK';
let cCode = conflicts[rawCode] ? conflicts[rawCode] : rawCode;
let isGidMissing = (!GID || GID === 'undefined' || GID === 'null' || GID === '');
if (isGidMissing) {
COUNTRY_GROUP = cCode+'2';
} else {
COUNTRY_GROUP = cCode+'1';
}
"
"$mapshaper" -i "${FILES_TO_RUN[@]}" combine-files \
-snap \
-merge-layers force\
-proj $PROJ_ARGS densify \
-simplify 2% weighted keep-shapes \
-filter-islands min-area=0 \
-sort 'this.area' ascending \
-each "$JS_PIPELINE_LOGIC" \
-split COUNTRY_GROUP \
-o "$OUT_FILE" \
format=svg \
id-field=GID \
precision=0.1 \
target=*
}
toSVG_01() {
input_files=()
@@ -136,9 +178,9 @@ toSVG_01() {
fi
done
"$mapshaper" -i combine-files ${input_files[@]} snap -proj loxim densify -simplify 0.001 weighted keep-shapes -o ./app/src/main/assets/loxim01.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID
"$mapshaper" -i combine-files ${input_files[@]} snap -proj webmercator densify -simplify 0.001 weighted keep-shapes -o ./app/src/main/assets/webmercator01.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID
"$mapshaper" -i combine-files ${input_files[@]} snap -proj aeqd +lat_0=90 densify -simplify 0.001 weighted keep-shapes -o ./app/src/main/assets/aeqd01.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID
generate_svg_map "./app/src/main/assets/loxim01.svg" "loxim" "${input_files[@]}"
generate_svg_map "./app/src/main/assets/webmercator01.svg" "webmercator" "${input_files[@]}"
generate_svg_map "./app/src/main/assets/aeqd01.svg" "aeqd +lat_0=90" "${input_files[@]}"
}
do_1() {
@@ -153,10 +195,12 @@ do_0() {
do
download_0 "$country"
done
wget -q -O "./temp/1/ATA.json" "$ATA_URL"
wget -q -O "./temp/0/ATA.json" "$ATA_URL"
}
# do_0
# do_1
# toSVG_0
# toSVG_1
toSVG_01
#CUW, CCK, XCL, CXR, IOT, BVT, ABW, FLK, GIB, HMD, KIR, SXM, MDV, MCO, NIU, NFK, PCN, MAF, SGS, VAT

View File

@@ -3,7 +3,7 @@
"@turf/area": "^7.3.5",
"@turf/turf": "^7.3.5",
"jsdom": "^29.1.1",
"mapshaper": "^0.7.19"
"mapshaper": "^0.7.22"
},
"type": "module"
}

View File

@@ -3061,9 +3061,9 @@ __metadata:
linkType: hard
"hyparquet@npm:^1.25.6":
version: 1.25.8
resolution: "hyparquet@npm:1.25.8"
checksum: 10c0/79037cbc8f943af5e95cc56677a94dd2667e73d51feca02b58ec416026e0dfd05e9e71db2a754f8e7be542779e951e3eef88e226a62de4daa21f6dfc8b090a31
version: 1.26.0
resolution: "hyparquet@npm:1.26.0"
checksum: 10c0/fbb8043436e8cd58897c853152816397548a5c5d3962a70b5e2c6f04745e574f4c4c556e535ea56042e6a7310f5eac64f3c32a551e416cb5b56243dc14abea9a
languageName: node
linkType: hard
@@ -3315,9 +3315,9 @@ __metadata:
languageName: node
linkType: hard
"mapshaper@npm:^0.7.19":
version: 0.7.19
resolution: "mapshaper@npm:0.7.19"
"mapshaper@npm:^0.7.22":
version: 0.7.22
resolution: "mapshaper@npm:0.7.22"
dependencies:
"@bokuweb/zstd-wasm": "npm:^0.0.27"
"@ngageoint/geopackage": "npm:^4.2.6"
@@ -3347,7 +3347,7 @@ __metadata:
idb-keyval: "npm:^6.2.0"
jpeg-js: "npm:^0.4.4"
kdbush: "npm:^3.0.0"
mproj: "npm:0.1.2"
mproj: "npm:0.1.3"
msgpackr: "npm:^1.10.1"
open: "npm:^11.0.0"
pngjs: "npm:^7.0.0"
@@ -3360,7 +3360,7 @@ __metadata:
mapshaper: bin/mapshaper
mapshaper-gui: bin/mapshaper-gui
mapshaper-xl: bin/mapshaper-xl
checksum: 10c0/124f409e3235ee4a38208c06fe77dfdd9cc32e23836b56fb7adc5b3a995cc0ef5dca18dc569b7169e5033ba2662b43dee3f8f411b9b1419c96b4200b9b051b9a
checksum: 10c0/cdb6a5c2b342ebfcb56fefa7141023f956253874d0db2adaf496a17f46162c3ef7a0ff057746fdeb68f52d795c571c4e35c2df191d570ac7107edba46dad7f42
languageName: node
linkType: hard
@@ -3422,16 +3422,16 @@ __metadata:
languageName: node
linkType: hard
"mproj@npm:0.1.2":
version: 0.1.2
resolution: "mproj@npm:0.1.2"
"mproj@npm:0.1.3":
version: 0.1.3
resolution: "mproj@npm:0.1.3"
dependencies:
geographiclib: "npm:1.48.0"
rw: "npm:~1.3.2"
bin:
mcs2cs: bin/mcs2cs
mproj: bin/mproj
checksum: 10c0/014fb9963a843f761930172fad9b82d202480d1f058def951982acfcef85e7fb65edf315260a881ba1961728e8352d92c6ba37b343d2fd6b62d75ebc876b75e8
checksum: 10c0/b3209f0d19f70c907ba51d91e1dc94785c768ca0bf5b43f218c51fde9a4471e6e9888acef1877209c40a742a9800fbe60dcbdb703a89a99d72473e8c5f52f977
languageName: node
linkType: hard
@@ -3515,8 +3515,8 @@ __metadata:
linkType: hard
"node-gyp@npm:latest":
version: 12.3.0
resolution: "node-gyp@npm:12.3.0"
version: 12.4.0
resolution: "node-gyp@npm:12.4.0"
dependencies:
env-paths: "npm:^2.2.0"
exponential-backoff: "npm:^3.1.1"
@@ -3530,7 +3530,7 @@ __metadata:
which: "npm:^6.0.0"
bin:
node-gyp: bin/node-gyp.js
checksum: 10c0/9d9032b405cbe42f72a105259d9eb679376470c102df4a2dbaa51e07d59bf741dcffb85897087ea9d8318b9cabb824a8978af51508ae142f0239ae1e6a3c2329
checksum: 10c0/9acb7c798e124275a6f9c1f7eb64b5abd6196bb885a3945fb44ee0dccf435514e88cdfb0f228ee7ff76ef25107c1f39ff37a067bf92fd00b9aff9234db29ff9e
languageName: node
linkType: hard
@@ -3974,7 +3974,7 @@ __metadata:
"@turf/area": "npm:^7.3.5"
"@turf/turf": "npm:^7.3.5"
jsdom: "npm:^29.1.1"
mapshaper: "npm:^0.7.19"
mapshaper: "npm:^0.7.22"
languageName: unknown
linkType: soft
@@ -4230,15 +4230,15 @@ __metadata:
linkType: hard
"tar@npm:^7.5.4":
version: 7.5.15
resolution: "tar@npm:7.5.15"
version: 7.5.16
resolution: "tar@npm:7.5.16"
dependencies:
"@isaacs/fs-minipass": "npm:^4.0.0"
chownr: "npm:^3.0.0"
minipass: "npm:^7.1.2"
minizlib: "npm:^3.1.0"
yallist: "npm:^5.0.0"
checksum: 10c0/8f039edb1d12fdd7df6c6f9877d125afe9f3da3f5f9317df326fdd090d48793d6998cede1506a1471f3e3a250db270a89dace28005eb5e99c5a9132d704ac956
checksum: 10c0/4f37f3c4bd2ca2755fd736a5df1d573c1a868ec1b1e893346aeafa95ac510f9e2fd1469420bd866cc7904799e5bd4ac62b5d4f03fe27747d6e1e373b44505c5c
languageName: node
linkType: hard
@@ -4250,12 +4250,12 @@ __metadata:
linkType: hard
"tinyglobby@npm:^0.2.12":
version: 0.2.16
resolution: "tinyglobby@npm:0.2.16"
version: 0.2.17
resolution: "tinyglobby@npm:0.2.17"
dependencies:
fdir: "npm:^6.5.0"
picomatch: "npm:^4.0.4"
checksum: 10c0/f2e09fd93dd95c41e522113b686ff6f7c13020962f8698a864a257f3d7737599afc47722b7ab726e12f8a813f779906187911ff8ee6701ede65072671a7e934b
checksum: 10c0/7f7bb0f197c88bc4b20c231e0deca4240ca3bf313a88f5a7fee93a872b84966a4d50220947c0455ad07a60b3b360961c5b7fd979222aeb716a9f99b412002e4c
languageName: node
linkType: hard
@@ -4273,21 +4273,21 @@ __metadata:
languageName: node
linkType: hard
"tldts-core@npm:^7.0.30":
version: 7.0.30
resolution: "tldts-core@npm:7.0.30"
checksum: 10c0/e3cd730e96b0e9c0332fcaab44d0257b668f9089644508e4f6f870d37bbf5c218243b7e83aa39690c87b386d1b0ad577772a5994969c4c81cc25a476f783ccd7
"tldts-core@npm:^7.1.0":
version: 7.1.0
resolution: "tldts-core@npm:7.1.0"
checksum: 10c0/e380f601e0b73105ef2add60f296fd001543e93e4c70cebf4f779717fae609680a8a0f77f1bec72043ce7597acab5f1d54769e8b3d07d20996d84c124d8e90f1
languageName: node
linkType: hard
"tldts@npm:^7.0.5":
version: 7.0.30
resolution: "tldts@npm:7.0.30"
version: 7.1.0
resolution: "tldts@npm:7.1.0"
dependencies:
tldts-core: "npm:^7.0.30"
tldts-core: "npm:^7.1.0"
bin:
tldts: bin/cli.js
checksum: 10c0/c36f7b480f09128303158e4738a82426c33e8da9f77d4fb57a2d5ef5896c803d7a3c1d53ade965712f9cb4946935139b6d192a18698665556ca504493c7c265e
checksum: 10c0/ff3b87a16ef96fde6e6eac745078cb87f0614cd2b854322245b98595efafb63fafbef23ea1f719105a77f7ee8c8d51ca21a42c030d4e9d5a50fdd5bca4da57a6
languageName: node
linkType: hard
@@ -4404,9 +4404,9 @@ __metadata:
linkType: hard
"undici@npm:^6.25.0":
version: 6.25.0
resolution: "undici@npm:6.25.0"
checksum: 10c0/2597cc6689bdb02c210c557b1f85febbfda65becae6e6fc1061508e2f33734d25207f81cd8af56ada9956329eb3a7bd7431e87dcfeceba20ee87059b57dcf985
version: 6.26.0
resolution: "undici@npm:6.26.0"
checksum: 10c0/cf2b4caf58c33d6582970991290cc7a6486d6e738845f25dcdd16952d708ec844815c6d30362919764fcaf30f719891289341f1ada496f003ce2700310453a47
languageName: node
linkType: hard