Author SHA1 Message Date
Renovate Bot 9738fbe52e Update plugin com.android.application to v9.4.0 2026-09-07 02:01:03 +00:00
sora 64e0d5de03 Fix monochrome (no circle for now) 2026-09-06 20:40:28 +02:00
sora 2cf32128ba Cleanup and revert AGP to 9.3.2 2026-09-06 20:33:39 +02:00
sora 2b900afc30 Update CI/Build 2026-09-06 20:30:36 +02:00
sora d059d031bd Settings revamp 2026-09-06 20:28:38 +02:00
sora caff1a07cc Cleanup 2026-09-06 20:04:13 +02:00
sora fe766c8820 Custom Map Renderer 2026-09-06 19:53:57 +02:00
sora 1e6a374e96 BMAP handling 2026-09-06 19:44:08 +02:00
sora 02b92308d0 Migrate JS from SVG to BMAP 2026-09-06 19:40:50 +02:00
bot a9c8a89aac Merge pull request 'Update dependency mapshaper to v0.7.59' (#605) from renovate/mapshaper-0.x-lockfile into main 2026-09-06 04:01:33 +02:00
Renovate Bot a69d5644a5 Update dependency mapshaper to v0.7.59 2026-09-06 02:01:23 +00:00
bot eab348d187 Merge pull request 'Lock file maintenance' (#604) from renovate/lock-file-maintenance into main 2026-09-05 04:01:51 +02:00
Renovate Bot 1877455c70 Lock file maintenance 2026-09-05 02:01:49 +00:00
bot 0b6074aaba Merge pull request 'Update dependency mapshaper to v0.7.58' (#603) from renovate/mapshaper-0.x-lockfile into main 2026-09-05 04:01:30 +02:00
Renovate Bot 875d809189 Update dependency mapshaper to v0.7.58 2026-09-05 02:01:21 +00:00
bot 3960f5cb5d Merge pull request 'Update plugin com.android.application to v9.4.0' (#601) from renovate/com.android.application-9.x into main 2026-09-03 04:01:08 +02:00
Renovate Bot 07368a408c Update plugin com.android.application to v9.4.0 2026-09-03 02:01:05 +00:00
sora 844da3ac93 Update gradle/wrapper/gradle-wrapper.properties 2026-09-03 02:13:24 +02:00
bot af27e53150 Merge pull request 'Update plugin com.android.library to v9.4.0' (#602) from renovate/com.android.library-9.x into main 2026-09-02 04:02:55 +02:00
Renovate Bot 9bced78550 Update plugin com.android.library to v9.4.0 2026-09-02 02:02:24 +00:00
bot 0129758f74 Merge pull request 'Update dependency mapshaper to v0.7.56' (#600) from renovate/mapshaper-0.x-lockfile into main 2026-08-31 04:01:39 +02:00
Renovate Bot 97657eeb0d Update dependency mapshaper to v0.7.56 2026-08-31 02:01:23 +00:00
bot 99db62cada Merge pull request 'Lock file maintenance' (#599) from renovate/lock-file-maintenance into main 2026-08-30 04:02:42 +02:00
Renovate Bot 606ffe2aff Lock file maintenance 2026-08-30 02:02:36 +00:00
bot d3cf4d69d6 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v15.2.0' (#598) from renovate/com.mikepenz.aboutlibraries.plugin-15.x into main 2026-08-30 04:01:59 +02:00
Renovate Bot 1e4e24eef8 Update plugin com.mikepenz.aboutlibraries.plugin to v15.2.0 2026-08-30 02:01:53 +00:00
bot 3036ffcc76 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v15.2.0' (#597) from renovate/com.mikepenz-aboutlibraries-core-15.x into main 2026-08-29 04:01:26 +02:00
Renovate Bot 1a98af2844 Update dependency com.mikepenz:aboutlibraries-core to v15.2.0 2026-08-29 02:01:24 +00:00
bot 7ecd655a53 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.2.0' (#596) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-08-29 04:01:15 +02:00
Renovate Bot d7f8939750 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.2.0 2026-08-29 02:01:08 +00:00
bot e3be95449e Merge pull request 'Update dependency androidx.navigation:navigation-compose to v2.10.0' (#595) from renovate/androidx.navigation-navigation-compose-2.x into main 2026-08-27 04:01:11 +02:00
Renovate Bot 61a8ee5439 Update dependency androidx.navigation:navigation-compose to v2.10.0 2026-08-27 02:01:07 +00:00
bot f0da771089 Merge pull request 'Update plugin com.android.library to v9.3.2' (#593) from renovate/com.android.library-9.x into main 2026-08-26 04:01:38 +02:00
bot d39a750597 Merge pull request 'Update actions/setup-java action to v6' (#594) from renovate/actions-setup-java-6.x into main 2026-08-26 04:01:37 +02:00
Renovate Bot e32715554c Update actions/setup-java action to v6 2026-08-26 02:01:34 +00:00
Renovate Bot 731f84bf58 Update plugin com.android.library to v9.3.2 2026-08-26 02:01:32 +00:00
bot 1c3d2b62c6 Merge pull request 'Update plugin com.android.application to v9.3.2' (#592) from renovate/com.android.application-9.x into main 2026-08-25 04:01:12 +02:00
Renovate Bot 66a8b9a1a9 Update plugin com.android.application to v9.3.2 2026-08-25 02:01:09 +00:00
bot c165eacdeb Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v15.1.1' (#591) from renovate/com.mikepenz.aboutlibraries.plugin-15.x into main 2026-08-23 04:01:27 +02:00
Renovate Bot 36cfb7dc05 Update plugin com.mikepenz.aboutlibraries.plugin to v15.1.1 2026-08-23 02:01:25 +00:00
bot 3ad9dfe8f4 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v15.1.1' (#590) from renovate/com.mikepenz-aboutlibraries-core-15.x into main 2026-08-23 04:01:16 +02:00
Renovate Bot 82065515e6 Update dependency com.mikepenz:aboutlibraries-core to v15.1.1 2026-08-23 02:01:08 +00:00
bot 74b278e972 Merge pull request 'Update dependency mapshaper to v0.7.55' (#589) from renovate/mapshaper-0.x-lockfile into main 2026-08-22 04:01:59 +02:00
Renovate Bot f9ac6c35e5 Update dependency mapshaper to v0.7.55 2026-08-22 02:01:51 +00:00
bot 975c60df63 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.1.1' (#588) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-08-22 04:01:26 +02:00
Renovate Bot 8fed759a8a Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.1.1 2026-08-22 02:01:19 +00:00
bot ca4f4b0833 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.1.0' (#587) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-08-21 04:01:40 +02:00
bot 68f9501e3b Merge pull request 'Update dependency mapshaper to v0.7.54' (#586) from renovate/mapshaper-0.x-lockfile into main 2026-08-21 04:01:34 +02:00
Renovate Bot 76efae59cc Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.1.0 2026-08-21 02:01:32 +00:00
Renovate Bot 48c7516a66 Update dependency mapshaper to v0.7.54 2026-08-21 02:01:23 +00:00
bot cee26154b8 Merge pull request 'Update dependency gradle to v9.7.1' (#585) from renovate/gradle-9.x into main 2026-08-20 04:01:19 +02:00
Renovate Bot 48eba6c069 Update dependency gradle to v9.7.1 2026-08-20 02:01:07 +00:00
bot ab599e7189 Merge pull request 'Lock file maintenance' (#584) from renovate/lock-file-maintenance into main 2026-08-16 04:01:47 +02:00
Renovate Bot 8b498bdc99 Lock file maintenance 2026-08-16 02:01:45 +00:00
bot 52b589959f Merge pull request 'Update dependency mapshaper to v0.7.53' (#583) from renovate/mapshaper-0.x-lockfile into main 2026-08-16 04:01:27 +02:00
Renovate Bot 7ce8a93c9f Update dependency mapshaper to v0.7.53 2026-08-16 02:01:18 +00:00
bot 49a9bad969 Merge pull request 'Lock file maintenance' (#582) from renovate/lock-file-maintenance into main 2026-08-15 04:01:55 +02:00
Renovate Bot 92c41a264d Lock file maintenance 2026-08-15 02:01:51 +00:00
bot 9bdc5c9bdc Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.12.0' (#581) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-08-14 04:01:22 +02:00
Renovate Bot f2047a62f4 Update dependency androidx.compose.ui:ui-tooling to v1.12.0 2026-08-14 02:01:17 +00:00
bot af8af0db31 Merge pull request 'Update dependency androidx.compose.material:material to v1.12.0' (#580) from renovate/androidx.compose.material-material-1.x into main 2026-08-13 04:01:14 +02:00
bot 3d312b7f6d Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.08.00' (#579) from renovate/androidx.compose-compose-bom-2026.x into main 2026-08-13 04:01:11 +02:00
Renovate Bot 788686fd59 Update dependency androidx.compose.material:material to v1.12.0 2026-08-13 02:01:10 +00:00
Renovate Bot e1368b9e16 Update dependency androidx.compose:compose-bom to v2026.08.00 2026-08-13 02:01:08 +00:00
bot 3900e74953 Merge pull request 'Update dependency mapshaper to v0.7.52' (#578) from renovate/mapshaper-0.x-lockfile into main 2026-08-12 04:02:10 +02:00
Renovate Bot 42f9b66ed0 Update dependency mapshaper to v0.7.52 2026-08-12 02:01:59 +00:00
bot fb162ff48f Merge pull request 'Lock file maintenance' (#577) from renovate/lock-file-maintenance into main 2026-08-09 04:01:24 +02:00
Renovate Bot 7c8b2f0f7d Lock file maintenance 2026-08-09 02:01:22 +00:00
bot c8f5e4d8e1 Merge pull request 'Lock file maintenance' (#576) from renovate/lock-file-maintenance into main 2026-08-08 04:01:57 +02:00
Renovate Bot a227f86d36 Lock file maintenance 2026-08-08 02:01:55 +00:00
bot e3c84a795c Merge pull request 'Update dependency @turf/turf to v7.4.0' (#573) from renovate/turf-turf-7.x-lockfile into main 2026-08-08 04:01:39 +02:00
Renovate Bot 32b62b5a22 Update dependency @turf/turf to v7.4.0 2026-08-08 02:01:34 +00:00
bot 6f747d9f85 Merge pull request 'Update dependency gradle to v9.7.0' (#575) from renovate/gradle-9.x into main 2026-08-07 04:01:37 +02:00
Renovate Bot 7de915832d Update dependency gradle to v9.7.0 2026-08-07 02:01:26 +00:00
bot 725a7d6282 Merge pull request 'Update dependency mapshaper to v0.7.51' (#574) from renovate/mapshaper-0.x-lockfile into main 2026-08-05 04:01:29 +02:00
Renovate Bot 65c0ed5d7a Update dependency mapshaper to v0.7.51 2026-08-05 02:01:18 +00:00
bot ed16b7dd21 Merge pull request 'Update dependency @turf/area to v7.4.0' (#572) from renovate/turf-area-7.x-lockfile into main 2026-08-04 04:01:20 +02:00
Renovate Bot 2e425ad97f Update dependency @turf/area to v7.4.0 2026-08-04 02:01:13 +00:00
bot 1399b98a73 Merge pull request 'Update dependency mapshaper to v0.7.50' (#571) from renovate/mapshaper-0.x-lockfile into main 2026-08-03 04:02:11 +02:00
Renovate Bot f404c70998 Update dependency mapshaper to v0.7.50 2026-08-03 02:01:54 +00:00
bot 91bbfc2db8 Merge pull request 'Lock file maintenance' (#570) from renovate/lock-file-maintenance into main 2026-08-01 04:13:49 +02:00
Renovate Bot 59d51fc35f Lock file maintenance 2026-08-01 02:03:24 +00:00
bot fd5e163055 Merge pull request 'Update dependency jsdom to v30.0.1' (#569) from renovate/jsdom-30.x-lockfile into main 2026-07-30 04:01:04 +02:00
Renovate Bot 5820e164b0 Update dependency jsdom to v30.0.1 2026-07-30 02:00:57 +00:00
bot 3a4f8b58ff Merge pull request 'Update dependency mapshaper to v0.7.49' (#568) from renovate/mapshaper-0.x-lockfile into main 2026-07-29 04:01:14 +02:00
Renovate Bot aa83c53db7 Update dependency mapshaper to v0.7.49 2026-07-29 02:01:04 +00:00
bot ceced8bfe5 Merge pull request 'Update dependency jsdom to v30' (#567) from renovate/jsdom-30.x into main 2026-07-28 04:01:03 +02:00
Renovate Bot b23f9f774c Update dependency jsdom to v30 2026-07-28 02:00:54 +00:00
bot 572bdaf9a7 Merge pull request 'Lock file maintenance' (#566) from renovate/lock-file-maintenance into main 2026-07-26 04:00:55 +02:00
Renovate Bot a481d20f8e Lock file maintenance 2026-07-26 02:00:53 +00:00
bot 74812d5528 Merge pull request 'Update plugin com.android.library to v9.3.1' (#565) from renovate/com.android.library-9.x into main 2026-07-25 04:01:09 +02:00
Renovate Bot 3e3c028fb8 Update plugin com.android.library to v9.3.1 2026-07-25 02:01:07 +00:00
bot 8373aec5a4 Merge pull request 'Update dependency mapshaper to v0.7.48' (#564) from renovate/mapshaper-0.x-lockfile into main 2026-07-25 04:00:57 +02:00
Renovate Bot 618e9693ae Update dependency mapshaper to v0.7.48 2026-07-25 02:00:48 +00:00
bot 3b2b18713f Merge pull request 'Update plugin com.android.application to v9.3.1' (#563) from renovate/com.android.application-9.x into main 2026-07-24 04:01:50 +02:00
bot 66d035546d Merge pull request 'Update dependency mapshaper to v0.7.47' (#562) from renovate/mapshaper-0.x-lockfile into main 2026-07-24 04:01:47 +02:00
Renovate Bot d725eeea7f Update plugin com.android.application to v9.3.1 2026-07-24 02:01:45 +00:00
Renovate Bot 0ceaa500e5 Update dependency mapshaper to v0.7.47 2026-07-24 02:01:28 +00:00
bot 6fc3b37076 Merge pull request 'Update dependency mapshaper to v0.7.46' (#561) from renovate/mapshaper-0.x-lockfile into main 2026-07-20 04:01:19 +02:00
Renovate Bot 90e7805363 Update dependency mapshaper to v0.7.46 2026-07-20 02:01:09 +00:00
bot de9b372543 Merge pull request 'Lock file maintenance' (#560) from renovate/lock-file-maintenance into main 2026-07-19 04:01:11 +02:00
Renovate Bot 0fa80e015b Lock file maintenance 2026-07-19 02:01:07 +00:00
bot a1e55680e6 Merge pull request 'Update plugin com.android.library to v9.3.0' (#559) from renovate/com.android.library-9.x into main 2026-07-18 04:01:06 +02:00
bot 2ea4481422 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v15.0.4' (#558) from renovate/com.mikepenz.aboutlibraries.plugin-15.x into main 2026-07-18 04:01:04 +02:00
Renovate Bot d0226d9bc8 Update plugin com.android.library to v9.3.0 2026-07-18 02:01:04 +00:00
bot 3ca6f59e0b Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v15.0.4' (#557) from renovate/com.mikepenz-aboutlibraries-core-15.x into main 2026-07-18 04:01:03 +02:00
Renovate Bot 21841da60d Update plugin com.mikepenz.aboutlibraries.plugin to v15.0.4 2026-07-18 02:01:02 +00:00
Renovate Bot c78df673a8 Update dependency com.mikepenz:aboutlibraries-core to v15.0.4 2026-07-18 02:00:55 +00:00
bot 7bec552612 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.0.4' (#556) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-07-17 04:01:02 +02:00
Renovate Bot a2d2a8edd3 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.0.4 2026-07-17 02:00:53 +00:00
bot 0876ae5955 Merge pull request 'Update plugin com.android.application to v9.3.0' (#555) from renovate/com.android.application-9.x into main 2026-07-16 04:01:01 +02:00
bot ab9e8f5063 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.4.10' (#554) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2026-07-16 04:00:58 +02:00
Renovate Bot 864fe26c95 Update plugin com.android.application to v9.3.0 2026-07-16 02:00:58 +00:00
Renovate Bot 79ebc617a4 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.4.10 2026-07-16 02:00:54 +00:00
bot ff84cce5ec Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.4.10' (#552) from renovate/org.jetbrains.kotlin.android-2.x into main 2026-07-15 04:01:13 +02:00
bot 953ce75b26 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.4.10' (#553) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2026-07-15 04:01:07 +02:00
Renovate Bot 7eb68a1436 Update plugin org.jetbrains.kotlin.plugin.compose to v2.4.10 2026-07-15 02:01:05 +00:00
Renovate Bot ed6b4f99ee Update plugin org.jetbrains.kotlin.android to v2.4.10 2026-07-15 02:01:03 +00:00
bot c29590c51e Merge pull request 'Update dependency mapshaper to v0.7.45' (#551) from renovate/mapshaper-0.x-lockfile into main 2026-07-13 04:01:21 +02:00
Renovate Bot 544ffa82fd Update dependency mapshaper to v0.7.45 2026-07-13 02:01:09 +00:00
bot 133f0992f9 Merge pull request 'Lock file maintenance' (#550) from renovate/lock-file-maintenance into main 2026-07-11 04:01:54 +02:00
Renovate Bot deb2fd536f Lock file maintenance 2026-07-11 02:01:52 +00:00
bot 46586dc2e4 Merge pull request 'Update dependency mapshaper to v0.7.44' (#549) from renovate/mapshaper-0.x-lockfile into main 2026-07-11 04:01:32 +02:00
Renovate Bot b255ef907a Update dependency mapshaper to v0.7.44 2026-07-11 02:01:21 +00:00
soraefir cf9b1cc6e2 Better cascading stats 2026-07-09 20:52:27 +02:00
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
bot 674106923f Merge pull request 'Update dependency mapshaper to v0.7.35' (#542) from renovate/mapshaper-0.x-lockfile into main 2026-07-03 04:01:22 +02:00
Renovate Bot e21dad8ed7 Update dependency mapshaper to v0.7.35 2026-07-03 02:01:13 +00:00
bot b2cca2332b Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.11.4' (#541) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-07-03 04:01:00 +02:00
Renovate Bot 13ad82df95 Update dependency androidx.compose.ui:ui-tooling to v1.11.4 2026-07-03 02:00:56 +00:00
bot 2befea2325 Merge pull request 'Update dependency androidx.compose.material:material to v1.11.4' (#540) from renovate/androidx.compose.material-material-1.x into main 2026-07-02 04:00:59 +02:00
bot f45cc57049 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.06.01' (#539) from renovate/androidx.compose-compose-bom-2026.x into main 2026-07-02 04:00:57 +02:00
Renovate Bot a7f05d5d80 Update dependency androidx.compose.material:material to v1.11.4 2026-07-02 02:00:56 +00:00
Renovate Bot 269e844a4c Update dependency androidx.compose:compose-bom to v2026.06.01 2026-07-02 02:00:54 +00:00
bot 818fc49e3d Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v15.0.3' (#538) from renovate/com.mikepenz.aboutlibraries.plugin-15.x into main 2026-07-01 04:01:06 +02:00
bot 8a3f7f7621 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v15.0.3' (#537) from renovate/com.mikepenz-aboutlibraries-core-15.x into main 2026-07-01 04:01:01 +02:00
Renovate Bot 8c50670a9d Update plugin com.mikepenz.aboutlibraries.plugin to v15.0.3 2026-07-01 02:01:00 +00:00
Renovate Bot 141ea67d2e Update dependency com.mikepenz:aboutlibraries-core to v15.0.3 2026-07-01 02:00:54 +00:00
soraefir 291fae7aec Fix hash 2026-06-30 07:48:24 +02:00
bot 7a04440eca Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.0.3' (#536) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-06-30 04:01:17 +02:00
Renovate Bot 691d2e4ea1 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.0.3 2026-06-30 02:01:09 +00:00
bot dd5c9387f9 Merge pull request 'Lock file maintenance' (#535) from renovate/lock-file-maintenance into main 2026-06-28 04:01:16 +02:00
Renovate Bot 6038d132ec Lock file maintenance 2026-06-28 02:01:11 +00:00
bot 6a0ac3e5c7 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v15' (#534) from renovate/com.mikepenz.aboutlibraries.plugin-15.x into main 2026-06-27 04:01:17 +02:00
Renovate Bot 327a6550b4 Update plugin com.mikepenz.aboutlibraries.plugin to v15 2026-06-27 02:01:15 +00:00
bot 7027d0ffb1 Merge pull request 'Update dependency gradle to v9.6.1' (#533) from renovate/gradle-9.x into main 2026-06-27 04:01:07 +02:00
Renovate Bot 15b33b59ed Update dependency gradle to v9.6.1 2026-06-27 02:00:56 +00:00
bot 36787717ff Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v15.0.2' (#532) from renovate/com.mikepenz-aboutlibraries-core-15.x into main 2026-06-27 04:00:54 +02:00
Renovate Bot c27aef23c0 Update dependency com.mikepenz:aboutlibraries-core to v15.0.2 2026-06-27 02:00:49 +00:00
bot a9e567d299 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.0.2' (#531) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-06-26 04:00:52 +02:00
Renovate Bot 468fb4ab13 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15.0.2 2026-06-26 02:00:43 +00:00
bot e952de3c17 Merge pull request 'Update dependency mapshaper to v0.7.33' (#530) from renovate/mapshaper-0.x-lockfile into main 2026-06-24 04:01:11 +02:00
Renovate Bot da1793bc21 Update dependency mapshaper to v0.7.33 2026-06-24 02:01:02 +00:00
bot 650a59b1ac Merge pull request 'Update dependency mapshaper to v0.7.32' (#529) from renovate/mapshaper-0.x-lockfile into main 2026-06-23 04:01:22 +02:00
Renovate Bot 5a9284749c Update dependency mapshaper to v0.7.32 2026-06-23 02:01:11 +00:00
bot 981fc5ebf4 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v15' (#528) from renovate/com.mikepenz-aboutlibraries-core-15.x into main 2026-06-22 04:01:27 +02:00
Renovate Bot 78f5fe7c35 Update dependency com.mikepenz:aboutlibraries-core to v15 2026-06-22 02:01:18 +00:00
bot bdf2911a1c Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15' (#527) from renovate/com.mikepenz-aboutlibraries-compose-m3-15.x into main 2026-06-21 04:01:41 +02:00
Renovate Bot 40b2040f25 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v15 2026-06-21 02:01:35 +00:00
bot 48c88d85a4 Merge pull request 'Update actions/checkout action to v7' (#526) from renovate/actions-checkout-7.x into main 2026-06-21 04:01:27 +02:00
Renovate Bot 727c828128 Update actions/checkout action to v7 2026-06-21 02:01:23 +00:00
bot cce6719158 Merge pull request 'Update dependency gradle to v9.6.0' (#525) from renovate/gradle-9.x into main 2026-06-20 04:02:45 +02:00
Renovate Bot 13a79baa2b Update dependency gradle to v9.6.0 2026-06-20 02:02:30 +00:00
bot c956fb9876 Merge pull request 'Update dependency mapshaper to v0.7.29' (#524) from renovate/mapshaper-0.x-lockfile into main 2026-06-20 04:01:49 +02:00
Renovate Bot 339ac468f3 Update dependency mapshaper to v0.7.29 2026-06-20 02:01:40 +00:00
bot f1eacee394 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.06.00' (#523) from renovate/androidx.compose-compose-bom-2026.x into main 2026-06-19 04:02:06 +02:00
bot a000fb4a3d Merge pull request 'Update dependency mapshaper to v0.7.28' (#522) from renovate/mapshaper-0.x-lockfile into main 2026-06-19 04:02:03 +02:00
Renovate Bot 16ff2e1bc8 Update dependency androidx.compose:compose-bom to v2026.06.00 2026-06-19 02:02:03 +00:00
Renovate Bot 6ee16f02ab Update dependency mapshaper to v0.7.28 2026-06-19 02:01:54 +00:00
bot fd05d9b6f5 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.11.3' (#521) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-06-18 04:01:30 +02:00
bot eece41d0ba Merge pull request 'Update dependency androidx.compose.material:material to v1.11.3' (#520) from renovate/androidx.compose.material-material-1.x into main 2026-06-18 04:01:29 +02:00
Renovate Bot b609704cc1 Update dependency androidx.compose.ui:ui-tooling to v1.11.3 2026-06-18 02:01:27 +00:00
Renovate Bot 39436f473a Update dependency androidx.compose.material:material to v1.11.3 2026-06-18 02:01:23 +00:00
bot ce2721025a Merge pull request 'Update dependency mapshaper to v0.7.26' (#519) from renovate/mapshaper-0.x-lockfile into main 2026-06-16 04:02:04 +02:00
Renovate Bot 371a2aa35f Update dependency mapshaper to v0.7.26 2026-06-16 02:01:52 +00:00
bot 0f986616ae Merge pull request 'Update dependency mapshaper to v0.7.24' (#518) from renovate/mapshaper-0.x-lockfile into main 2026-06-15 04:01:52 +02:00
Renovate Bot eb948c2e59 Update dependency mapshaper to v0.7.24 2026-06-15 02:01:40 +00:00
bot 4fe43c5fc6 Merge pull request 'Lock file maintenance' (#517) from renovate/lock-file-maintenance into main 2026-06-14 04:02:19 +02:00
Renovate Bot eed23b35d4 Lock file maintenance 2026-06-14 02:02:17 +00:00
bot e9d46d0824 Merge pull request 'Update dependency mapshaper to v0.7.23' (#516) from renovate/mapshaper-0.x-lockfile into main 2026-06-14 04:01:58 +02:00
Renovate Bot ccd18f3717 Update dependency mapshaper to v0.7.23 2026-06-14 02:01:46 +00:00
bot 1e78f269c1 Merge pull request 'Lock file maintenance' (#515) from renovate/lock-file-maintenance into main 2026-06-13 04:02:05 +02:00
Renovate Bot 0740c00014 Lock file maintenance 2026-06-13 02:02:01 +00:00
bot 740526104b Merge pull request 'Lock file maintenance' (#514) from renovate/lock-file-maintenance into main 2026-06-07 04:04:22 +02:00
Renovate Bot f807fa62d2 Lock file maintenance 2026-06-07 02:04:13 +00:00
sora 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
sora 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
sora 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
sora d262331d1b Update .github/workflows/build.yml 2026-05-23 14:45:03 +02:00
sora 7440e85987 Update app/build.gradle 2026-05-23 14:42:40 +02:00
sora 2351512af8 Update app/build.gradle 2026-05-23 14:42:30 +02:00
sora ae2cde3545 Update app/build.gradle 2026-05-23 14:06:23 +02:00
soraefir 1000319b8f Bump java version workflow to 21 2026-05-23 11:45:32 +02:00
soraefir 8c4c5e16e0 Fix build error with backuprules 2026-05-23 11:42:03 +02:00
soraefir 0ce1e604a8 Update node deps 2026-05-23 11:39:04 +02:00
soraefir 757783534c Fix build, errors and spring cleaning 2026-05-23 11:35:02 +02:00
soraefir 3eba48ea7d WIP
# Conflicts:
#	build.gradle
#	gradle/wrapper/gradle-wrapper.properties
2026-05-23 10:53:36 +02:00
bot cf886b9581 Merge pull request 'Lock file maintenance' (#501) from renovate/lock-file-maintenance into main 2026-05-23 04:01:46 +02:00
Renovate Bot f2ff152c07 Lock file maintenance 2026-05-23 02:01:43 +00:00
bot 19b152db8c Merge pull request 'Update dependency mapshaper to v0.7.19' (#500) from renovate/mapshaper-0.x-lockfile into main 2026-05-21 04:01:48 +02:00
Renovate Bot 18dff78e5b Update dependency mapshaper to v0.7.19 2026-05-21 02:01:40 +00:00
bot 9b16a30c9a Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.11.2' (#499) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-05-21 04:01:25 +02:00
Renovate Bot 62bf040093 Update dependency androidx.compose.ui:ui-tooling to v1.11.2 2026-05-21 02:01:22 +00:00
bot 60bafc6798 Merge pull request 'Update dependency androidx.compose.material:material to v1.11.2' (#498) from renovate/androidx.compose.material-material-1.x into main 2026-05-20 04:01:24 +02:00
bot bf206efc1f Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.05.01' (#497) from renovate/androidx.compose-compose-bom-2026.x into main 2026-05-20 04:01:22 +02:00
Renovate Bot c42f72a856 Update dependency androidx.compose.material:material to v1.11.2 2026-05-20 02:01:21 +00:00
Renovate Bot c6a890aee3 Update dependency androidx.compose:compose-bom to v2026.05.01 2026-05-20 02:01:19 +00:00
bot 59450cf480 Merge pull request 'Update dependency mapshaper to v0.7.18' (#496) from renovate/mapshaper-0.x-lockfile into main 2026-05-19 04:01:44 +02:00
Renovate Bot 7f068ea10f Update dependency mapshaper to v0.7.18 2026-05-19 02:01:35 +00:00
bot f9763d7b53 Merge pull request 'Lock file maintenance' (#495) from renovate/lock-file-maintenance into main 2026-05-16 04:02:06 +02:00
Renovate Bot f2e9f44dee Lock file maintenance 2026-05-16 02:02:05 +00:00
bot 1039e220fb Merge pull request 'Update dependency mapshaper to v0.7.17' (#494) from renovate/mapshaper-0.x-lockfile into main 2026-05-16 04:01:45 +02:00
Renovate Bot a810345e2b Update dependency mapshaper to v0.7.17 2026-05-16 02:01:36 +00:00
bot 4dc5213e6f Merge pull request 'Update dependency com.google.android.material:material to v1.14.0' (#493) from renovate/com.google.android.material-material-1.x into main 2026-05-14 04:02:04 +02:00
bot 5f313a20d5 Merge pull request 'Update dependency mapshaper to v0.7.16' (#492) from renovate/mapshaper-0.x-lockfile into main 2026-05-14 04:01:59 +02:00
Renovate Bot 01eae6daf9 Update dependency com.google.android.material:material to v1.14.0 2026-05-14 02:01:58 +00:00
Renovate Bot 7ae7ebb9b3 Update dependency mapshaper to v0.7.16 2026-05-14 02:01:50 +00:00
bot a4e64e43f3 Merge pull request 'Update dependency mapshaper to v0.7.15' (#491) from renovate/mapshaper-0.x-lockfile into main 2026-05-13 04:02:32 +02:00
Renovate Bot a1e73f5844 Update dependency mapshaper to v0.7.15 2026-05-13 02:02:24 +00:00
bot a6189c77ef Merge pull request 'Update dependency gradle to v9.5.1' (#490) from renovate/gradle-9.x into main 2026-05-13 04:02:08 +02:00
Renovate Bot 1b36ae84d7 Update dependency gradle to v9.5.1 2026-05-13 02:01:54 +00:00
bot c609cccdc6 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v14.2.0' (#489) from renovate/com.mikepenz.aboutlibraries.plugin-14.x into main 2026-05-11 04:02:16 +02:00
bot 1360c5eeef Merge pull request 'Update dependency mapshaper to v0.7.13' (#488) from renovate/mapshaper-0.x-lockfile into main 2026-05-11 04:02:10 +02:00
Renovate Bot d087eddc29 Update plugin com.mikepenz.aboutlibraries.plugin to v14.2.0 2026-05-11 02:02:09 +00:00
Renovate Bot 1910a8672b Update dependency mapshaper to v0.7.13 2026-05-11 02:02:00 +00:00
bot 787175ee37 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v14.2.0' (#487) from renovate/com.mikepenz-aboutlibraries-core-14.x into main 2026-05-10 04:02:13 +02:00
Renovate Bot 90285d5efa Update dependency com.mikepenz:aboutlibraries-core to v14.2.0 2026-05-10 02:02:09 +00:00
bot 66b06d608d Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.0' (#486) from renovate/com.mikepenz-aboutlibraries-compose-m3-14.x into main 2026-05-10 04:01:53 +02:00
Renovate Bot cabaa02f59 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.2.0 2026-05-10 02:01:45 +00:00
bot ee60dffa17 Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v14.2.0' (#485) from renovate/com.mikepenz-aboutlibraries-14.x into main 2026-05-09 04:01:57 +02:00
Renovate Bot 98a2558ad2 Update dependency com.mikepenz:aboutlibraries to v14.2.0 2026-05-09 02:01:51 +00:00
bot d820c0e5fa Merge pull request 'Update dependency mapshaper to v0.7.12' (#484) from renovate/mapshaper-0.x-lockfile into main 2026-05-09 04:01:42 +02:00
Renovate Bot 61bba0c099 Update dependency mapshaper to v0.7.12 2026-05-09 02:01:34 +00:00
bot c58f1359cd Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.05.00' (#483) from renovate/androidx.compose-compose-bom-2026.x into main 2026-05-08 04:01:49 +02:00
bot 942e3a6235 Merge pull request 'Update dependency mapshaper to v0.7.10' (#482) from renovate/mapshaper-0.x-lockfile into main 2026-05-08 04:01:45 +02:00
Renovate Bot d43535f772 Update dependency androidx.compose:compose-bom to v2026.05.00 2026-05-08 02:01:44 +00:00
Renovate Bot e12c7194e6 Update dependency mapshaper to v0.7.10 2026-05-08 02:01:36 +00:00
bot 47277d3609 Merge pull request 'Update plugin com.android.library to v9.2.1' (#479) from renovate/com.android.library-9.x into main 2026-05-07 04:01:42 +02:00
bot 2a8bb6d1b4 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.11.1' (#481) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-05-07 04:01:41 +02:00
Renovate Bot 7ad2eff27b Update plugin com.android.library to v9.2.1 2026-05-07 02:01:37 +00:00
bot b52c16c242 Merge pull request 'Update dependency androidx.compose.material:material to v1.11.1' (#480) from renovate/androidx.compose.material-material-1.x into main 2026-05-07 04:01:36 +02:00
Renovate Bot caeb0f69a5 Update dependency androidx.compose.ui:ui-tooling to v1.11.1 2026-05-07 02:01:35 +00:00
Renovate Bot 74b4fe4ac4 Update dependency androidx.compose.material:material to v1.11.1 2026-05-07 02:01:32 +00:00
bot 504ae87174 Merge pull request 'Update plugin com.android.application to v9.2.1' (#478) from renovate/com.android.application-9.x into main 2026-05-06 10:00:18 +02:00
Renovate Bot 06bba3ceaf Update plugin com.android.application to v9.2.1 2026-05-06 08:00:13 +00:00
bot 70defaf916 Merge pull request 'Update dependency mapshaper to v0.7.9' (#477) from renovate/mapshaper-0.x-lockfile into main 2026-05-05 04:02:04 +02:00
Renovate Bot dfcebe327f Update dependency mapshaper to v0.7.9 2026-05-05 02:01:54 +00:00
bot 3b27613e99 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v14.1.0' (#476) from renovate/com.mikepenz-aboutlibraries-core-14.x into main 2026-05-04 04:01:25 +02:00
Renovate Bot bff39001a4 Update dependency com.mikepenz:aboutlibraries-core to v14.1.0 2026-05-04 02:01:17 +00:00
bot e77c8dc49e Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.1.0' (#475) from renovate/com.mikepenz-aboutlibraries-compose-m3-14.x into main 2026-05-03 04:02:23 +02:00
Renovate Bot c4da784b95 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.1.0 2026-05-03 02:02:16 +00:00
bot cbf1871cec Merge pull request 'Update dependency mapshaper to v0.7.8' (#474) from renovate/mapshaper-0.x-lockfile into main 2026-05-03 04:02:02 +02:00
Renovate Bot 9ed40a67bc Update dependency mapshaper to v0.7.8 2026-05-03 02:01:52 +00:00
bot 8e699d0b22 Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v14.1.0' (#473) from renovate/com.mikepenz-aboutlibraries-14.x into main 2026-05-02 04:01:52 +02:00
Renovate Bot 9424517eb4 Update dependency com.mikepenz:aboutlibraries to v14.1.0 2026-05-02 02:01:47 +00:00
bot acd6753112 Merge pull request 'Update dependency mapshaper to v0.7.7' (#472) from renovate/mapshaper-0.x-lockfile into main 2026-05-02 04:01:39 +02:00
Renovate Bot 6fa02d117b Update dependency mapshaper to v0.7.7 2026-05-02 02:01:31 +00:00
bot f83ff7aca1 Merge pull request 'Update dependency mapshaper to v0.7.6' (#471) from renovate/mapshaper-0.x-lockfile into main 2026-05-01 04:01:52 +02:00
Renovate Bot 89bd79b542 Update dependency mapshaper to v0.7.6 2026-05-01 02:01:43 +00:00
bot 4db4757701 Merge pull request 'Update dependency jsdom to v29.1.1' (#470) from renovate/jsdom-29.x-lockfile into main 2026-05-01 04:01:37 +02:00
Renovate Bot f57f29cda6 Update dependency jsdom to v29.1.1 2026-05-01 02:01:28 +00:00
bot 51ff021d37 Merge pull request 'Update dependency mapshaper to v0.7.5' (#469) from renovate/mapshaper-0.x-lockfile into main 2026-04-30 04:01:45 +02:00
Renovate Bot c6f9bf7fc5 Update dependency mapshaper to v0.7.5 2026-04-30 02:01:35 +00:00
bot 9385442542 Merge pull request 'Update dependency gradle to v9.5.0' (#468) from renovate/gradle-9.x into main 2026-04-29 04:02:22 +02:00
Renovate Bot 19a185a6c3 Update dependency gradle to v9.5.0 2026-04-29 02:02:12 +00:00
bot 725de6c7b3 Merge pull request 'Update dependency mapshaper to v0.7.4' (#467) from renovate/mapshaper-0.x-lockfile into main 2026-04-29 04:01:39 +02:00
Renovate Bot 5228ac2508 Update dependency mapshaper to v0.7.4 2026-04-29 02:01:31 +00:00
bot 80d6693ef3 Merge pull request 'Update dependency jsdom to v29.1.0' (#466) from renovate/jsdom-29.x-lockfile into main 2026-04-28 04:01:42 +02:00
Renovate Bot 9d3ce7410a Update dependency jsdom to v29.1.0 2026-04-28 02:01:36 +00:00
bot 989fd1cea0 Merge pull request 'Update dependency androidx.compose.material:material to v1.11.0' (#464) from renovate/androidx.compose.material-material-1.x into main 2026-04-27 04:01:33 +02:00
bot d3b11fbb02 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.11.0' (#465) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-04-27 04:01:30 +02:00
Renovate Bot 9a8c13e148 Update dependency androidx.compose.ui:ui-tooling to v1.11.0 2026-04-27 02:01:28 +00:00
Renovate Bot 4f65a86302 Update dependency androidx.compose.material:material to v1.11.0 2026-04-27 02:01:25 +00:00
bot 8c559f73f7 Merge pull request 'Update dependency @turf/turf to v7.3.5' (#453) from renovate/turf-turf-7.x-lockfile into main 2026-04-26 04:02:09 +02:00
Renovate Bot 1e77399412 Update dependency @turf/turf to v7.3.5 2026-04-26 02:02:08 +00:00
bot 6f2d9cacc4 Merge pull request 'Update dependency mapshaper to v0.7.3' (#463) from renovate/mapshaper-0.x-lockfile into main 2026-04-26 04:01:55 +02:00
Renovate Bot f6209562ff Update dependency mapshaper to v0.7.3 2026-04-26 02:01:48 +00:00
bot 9e692dae6a Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.21' (#462) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2026-04-25 04:10:56 +02:00
Renovate Bot 16458b014f Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.21 2026-04-25 02:03:25 +00:00
bot af262b0ad2 Merge pull request 'Update dependency mapshaper to v0.7.1' (#461) from renovate/mapshaper-0.x-lockfile into main 2026-04-25 04:02:56 +02:00
Renovate Bot 5a81ae5170 Update dependency mapshaper to v0.7.1 2026-04-25 02:02:46 +00:00
bot 89931b9c32 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.21' (#460) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2026-04-24 04:06:03 +02:00
bot 5a07720457 Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.3.21' (#459) from renovate/org.jetbrains.kotlin.android-2.x into main 2026-04-24 04:06:01 +02:00
Renovate Bot 7dd4049f89 Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.21 2026-04-24 02:06:00 +00:00
Renovate Bot 6aa1253ab6 Update plugin org.jetbrains.kotlin.android to v2.3.21 2026-04-24 02:01:44 +00:00
bot 3391985d20 Merge pull request 'Update plugin com.android.library to v9.2.0' (#456) from renovate/com.android.library-9.x into main 2026-04-23 04:01:56 +02:00
bot 28bb0ae611 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.04.01' (#458) from renovate/androidx.compose-compose-bom-2026.x into main 2026-04-23 04:01:52 +02:00
Renovate Bot d7a3f5389e Update plugin com.android.library to v9.2.0 2026-04-23 02:01:51 +00:00
bot 0552dc33c0 Merge pull request 'Update dependency androidx.navigation:navigation-compose to v2.9.8' (#457) from renovate/androidx.navigation-navigation-compose-2.x into main 2026-04-23 04:01:49 +02:00
Renovate Bot b0799070eb Update dependency androidx.compose:compose-bom to v2026.04.01 2026-04-23 02:01:49 +00:00
Renovate Bot b000b2e921 Update dependency androidx.navigation:navigation-compose to v2.9.8 2026-04-23 02:01:47 +00:00
bot 10c5f9538f Merge pull request 'Update plugin com.android.application to v9.2.0' (#455) from renovate/com.android.application-9.x into main 2026-04-22 04:01:49 +02:00
Renovate Bot 5a47c70a6f Update plugin com.android.application to v9.2.0 2026-04-22 02:01:46 +00:00
bot f768306651 Merge pull request 'Update dependency mapshaper to ^0.7.0' (#454) from renovate/mapshaper-0.x into main 2026-04-21 04:02:00 +02:00
Renovate Bot ff22ea6355 Update dependency mapshaper to ^0.7.0 2026-04-21 02:01:52 +00:00
bot f22ff6eb34 Merge pull request 'Update dependency @turf/area to v7.3.5' (#452) from renovate/turf-area-7.x-lockfile into main 2026-04-20 04:01:43 +02:00
Renovate Bot 12812dcc2f Update dependency @turf/area to v7.3.5 2026-04-20 02:01:38 +00:00
bot c348a2c8a6 Merge pull request 'Lock file maintenance' (#451) from renovate/lock-file-maintenance into main 2026-04-19 04:02:10 +02:00
Renovate Bot 417f53860d Lock file maintenance 2026-04-19 02:02:09 +00:00
bot 7dc7381075 Merge pull request 'Update dependency mapshaper to v0.6.121' (#450) from renovate/mapshaper-0.x-lockfile into main 2026-04-19 04:01:47 +02:00
Renovate Bot b86c1aeebe Update dependency mapshaper to v0.6.121 2026-04-19 02:01:38 +00:00
bot 1d32687848 Merge pull request 'Lock file maintenance' (#449) from renovate/lock-file-maintenance into main 2026-04-18 04:02:18 +02:00
Renovate Bot 80738118f0 Lock file maintenance 2026-04-18 02:02:15 +00:00
bot 45776a2f9b Merge pull request 'Update dependency mapshaper to v0.6.119' (#448) from renovate/mapshaper-0.x-lockfile into main 2026-04-18 04:01:47 +02:00
Renovate Bot b303bc6aa6 Update dependency mapshaper to v0.6.119 2026-04-18 02:01:28 +00:00
bot 476dcddaa9 Merge pull request 'Update dependency mapshaper to v0.6.118' (#447) from renovate/mapshaper-0.x-lockfile into main 2026-04-17 04:01:28 +02:00
Renovate Bot 5229507771 Update dependency mapshaper to v0.6.118 2026-04-17 02:01:18 +00:00
bot 9a16f1b2ce Merge pull request 'Update dependency mapshaper to v0.6.117' (#446) from renovate/mapshaper-0.x-lockfile into main 2026-04-16 04:01:15 +02:00
Renovate Bot 633a7b5d06 Update dependency mapshaper to v0.6.117 2026-04-16 02:01:05 +00:00
bot 80ff0458a1 Merge pull request 'Update plugin com.android.library to v9.1.1' (#445) from renovate/com.android.library-9.x into main 2026-04-15 04:01:03 +02:00
Renovate Bot 5d5d6c6962 Update plugin com.android.library to v9.1.1 2026-04-15 02:00:58 +00:00
bot 2c94f7871b Merge pull request 'Update plugin com.android.application to v9.1.1' (#444) from renovate/com.android.application-9.x into main 2026-04-14 04:01:07 +02:00
Renovate Bot 26a4aae265 Update plugin com.android.application to v9.1.1 2026-04-14 02:01:01 +00:00
bot 477a8c94fd Merge pull request 'Update softprops/action-gh-release action to v3' (#443) from renovate/softprops-action-gh-release-3.x into main 2026-04-13 04:01:11 +02:00
Renovate Bot c00078e766 Update softprops/action-gh-release action to v3 2026-04-13 02:01:06 +00:00
bot 09ba61f3c5 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v14' (#438) from renovate/com.mikepenz-aboutlibraries-core-14.x into main 2026-04-12 04:01:15 +02:00
Renovate Bot 71d48d87be Update dependency com.mikepenz:aboutlibraries-core to v14 2026-04-12 02:01:13 +00:00
bot 9695f69e70 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v14' (#442) from renovate/com.mikepenz.aboutlibraries.plugin-14.x into main 2026-04-12 04:01:02 +02:00
Renovate Bot 992116fcfe Update plugin com.mikepenz.aboutlibraries.plugin to v14 2026-04-12 02:00:59 +00:00
bot 270946c082 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.0.1' (#440) from renovate/com.mikepenz-aboutlibraries-compose-m3-14.x into main 2026-04-11 04:01:19 +02:00
Renovate Bot 6832f3ebee Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14.0.1 2026-04-11 02:01:17 +00:00
bot ce47bff4f5 Merge pull request 'Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.11.0' (#441) from renovate/org.jetbrains.kotlinx-kotlinx-serialization-json-1.x into main 2026-04-11 04:01:03 +02:00
Renovate Bot 47c8cf7812 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.11.0 2026-04-11 02:00:59 +00:00
bot 5cd1b5f9db Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v14.0.1' (#439) from renovate/com.mikepenz-aboutlibraries-14.x into main 2026-04-10 04:01:08 +02:00
Renovate Bot e3a6b11c67 Update dependency com.mikepenz:aboutlibraries to v14.0.1 2026-04-10 02:01:00 +00:00
bot 8fb08205f1 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14' (#436) from renovate/com.mikepenz-aboutlibraries-compose-m3-14.x into main 2026-04-08 04:01:23 +02:00
bot 863bba990a Merge pull request 'Update dependency jsdom to v29.0.2' (#437) from renovate/jsdom-29.x-lockfile into main 2026-04-08 04:01:18 +02:00
Renovate Bot cc7f0f6a47 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v14 2026-04-08 02:01:16 +00:00
Renovate Bot c4432e6494 Update dependency jsdom to v29.0.2 2026-04-08 02:01:09 +00:00
bot a4237d102e Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v14' (#435) from renovate/com.mikepenz-aboutlibraries-14.x into main 2026-04-07 04:01:05 +02:00
Renovate Bot 5050468192 Update dependency com.mikepenz:aboutlibraries to v14 2026-04-07 02:00:56 +00:00
bot e526ea9974 Merge pull request 'Lock file maintenance' (#434) from renovate/lock-file-maintenance into main 2026-04-05 04:01:19 +02:00
Renovate Bot 36cd230e16 Lock file maintenance 2026-04-05 02:01:15 +00:00
sora b55b834e35 Update .github/workflows/build.yml 2026-04-04 12:09:22 +02:00
sora f60ff9e9e6 Update .github/workflows/build.yml 2026-04-04 12:05:17 +02:00
bot b8618319b0 Merge pull request 'Lock file maintenance' (#433) from renovate/lock-file-maintenance into main 2026-04-04 04:01:15 +02:00
Renovate Bot 92e4fbeb7a Lock file maintenance 2026-04-04 02:01:13 +00:00
bot 0d1ff771dd Merge pull request 'Lock file maintenance' (#432) from renovate/lock-file-maintenance into main 2026-03-28 03:01:13 +01:00
Renovate Bot 486ccd2a63 Lock file maintenance 2026-03-28 02:01:10 +00:00
bot 560005a943 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.6' (#431) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-03-27 03:00:46 +01:00
Renovate Bot 40fdf3f536 Update dependency androidx.compose.ui:ui-tooling to v1.10.6 2026-03-27 02:00:43 +00:00
bot 07172fded1 Merge pull request 'Update dependency androidx.compose.material:material to v1.10.6' (#430) from renovate/androidx.compose.material-material-1.x into main 2026-03-26 03:00:53 +01:00
bot f7f499c4c7 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.03.01' (#429) from renovate/androidx.compose-compose-bom-2026.x into main 2026-03-26 03:00:50 +01:00
Renovate Bot 3ed7eeebcd Update dependency androidx.compose.material:material to v1.10.6 2026-03-26 02:00:48 +00:00
Renovate Bot 5b7e497bc6 Update dependency androidx.compose:compose-bom to v2026.03.01 2026-03-26 02:00:46 +00:00
bot 5749aca451 Merge pull request 'Lock file maintenance' (#428) from renovate/lock-file-maintenance into main 2026-03-22 03:01:20 +01:00
Renovate Bot fd57d75063 Lock file maintenance 2026-03-22 02:01:16 +00:00
bot 17be04d259 Merge pull request 'Lock file maintenance' (#427) from renovate/lock-file-maintenance into main 2026-03-21 03:01:37 +01:00
Renovate Bot 08fb6902db Lock file maintenance 2026-03-21 02:01:36 +00:00
bot 4cf95f81e3 Merge pull request 'Update dependency jsdom to v29.0.1' (#426) from renovate/jsdom-29.x-lockfile into main 2026-03-21 03:01:11 +01:00
Renovate Bot 11ab88e25c Update dependency jsdom to v29.0.1 2026-03-21 02:01:05 +00:00
bot 28e9eb20b9 Merge pull request 'Update dependency gradle to v9.4.1' (#425) from renovate/gradle-9.x into main 2026-03-20 03:01:43 +01:00
Renovate Bot c728e49731 Update dependency gradle to v9.4.1 2026-03-20 02:01:26 +00:00
bot 4cb421ec7a Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.20' (#424) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2026-03-18 03:08:32 +01:00
Renovate Bot ffe7f6c36d Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.20 2026-03-18 02:00:52 +00:00
bot e65475aca7 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.20' (#423) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2026-03-17 03:08:33 +01:00
bot 1ae85ab23c Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.3.20' (#422) from renovate/org.jetbrains.kotlin.android-2.x into main 2026-03-17 03:08:23 +01:00
Renovate Bot 2eb83d6e3d Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.20 2026-03-17 02:08:22 +00:00
Renovate Bot fbebc001f6 Update plugin org.jetbrains.kotlin.android to v2.3.20 2026-03-17 02:00:47 +00:00
bot e3105a920b Merge pull request 'Update dependency jsdom to v29' (#421) from renovate/jsdom-29.x into main 2026-03-16 03:01:10 +01:00
Renovate Bot 791cc5df34 Update dependency jsdom to v29 2026-03-16 02:01:00 +00:00
bot 92c6c26d49 Merge pull request 'Lock file maintenance' (#420) from renovate/lock-file-maintenance into main 2026-03-15 03:01:17 +01:00
Renovate Bot db86caa4e4 Lock file maintenance 2026-03-15 02:01:14 +00:00
bot 17ce5da002 Merge pull request 'Lock file maintenance' (#419) from renovate/lock-file-maintenance into main 2026-03-14 03:01:22 +01:00
Renovate Bot d514362719 Lock file maintenance 2026-03-14 02:01:18 +00:00
bot 71865d28f5 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.03.00' (#418) from renovate/androidx.compose-compose-bom-2026.x into main 2026-03-13 03:00:55 +01:00
bot 5b9ac2b00b Merge pull request 'Update dependency androidx.activity:activity-ktx to v1.13.0' (#417) from renovate/androidx.activity-activity-ktx-1.x into main 2026-03-13 03:00:50 +01:00
Renovate Bot 41cf46f182 Update dependency androidx.compose:compose-bom to v2026.03.00 2026-03-13 02:00:49 +00:00
Renovate Bot 007361fa98 Update dependency androidx.activity:activity-ktx to v1.13.0 2026-03-13 02:00:46 +00:00
bot 94fbad41e8 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.5' (#416) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-03-12 03:00:57 +01:00
bot f525b5fb68 Merge pull request 'Update dependency androidx.compose.material:material to v1.10.5' (#415) from renovate/androidx.compose.material-material-1.x into main 2026-03-12 03:00:55 +01:00
Renovate Bot 39dd9f76c5 Update dependency androidx.compose.ui:ui-tooling to v1.10.5 2026-03-12 02:00:53 +00:00
Renovate Bot d7980ee737 Update dependency androidx.compose.material:material to v1.10.5 2026-03-12 02:00:51 +00:00
bot 08ad167c12 Merge pull request 'Lock file maintenance' (#414) from renovate/lock-file-maintenance into main 2026-03-07 03:01:23 +01:00
Renovate Bot e713357246 Lock file maintenance 2026-03-07 02:01:21 +00:00
bot 99b82b5d6e Merge pull request 'Update plugin com.android.library to v9.1.0' (#412) from renovate/com.android.library-9.x into main 2026-03-07 03:00:48 +01:00
Renovate Bot d7f12fdacb Update plugin com.android.library to v9.1.0 2026-03-06 02:00:47 +00:00
bot 759645e31e Merge pull request 'Update dependency gradle to v9.4.0' (#413) from renovate/gradle-9.x into main 2026-03-05 03:01:39 +01:00
Renovate Bot 5e50f5733c Update dependency gradle to v9.4.0 2026-03-05 02:01:24 +00:00
bot db523412c5 Merge pull request 'Update plugin com.android.application to v9.1.0' (#411) from renovate/com.android.application-9.x into main 2026-03-04 03:00:50 +01:00
Renovate Bot 75a772f4fe Update plugin com.android.application to v9.1.0 2026-03-04 02:00:46 +00:00
bot e4fa5430ba Merge pull request 'Lock file maintenance' (#410) from renovate/lock-file-maintenance into main 2026-03-01 03:01:21 +01:00
Renovate Bot db321c4d74 Lock file maintenance 2026-03-01 02:01:15 +00:00
bot 14bda79c07 Merge pull request 'Lock file maintenance' (#409) from renovate/lock-file-maintenance into main 2026-02-28 03:01:26 +01:00
Renovate Bot 0cf5511aad Lock file maintenance 2026-02-28 02:01:24 +00:00
bot 2e9aa07b71 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.02.01' (#408) from renovate/androidx.compose-compose-bom-2026.x into main 2026-02-28 03:00:51 +01:00
Renovate Bot 2dd6722ac5 Update dependency androidx.compose:compose-bom to v2026.02.01 2026-02-28 02:00:47 +00:00
bot b15bd32edf Merge pull request 'Update dependency androidx.compose.material:material to v1.10.4' (#406) from renovate/androidx.compose.material-material-1.x into main 2026-02-26 03:01:06 +01:00
bot a1daa4d1eb Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.4' (#407) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-02-26 03:01:00 +01:00
Renovate Bot 9914010eb6 Update dependency androidx.compose.ui:ui-tooling to v1.10.4 2026-02-26 02:00:58 +00:00
Renovate Bot 6353a2b8b7 Update dependency androidx.compose.material:material to v1.10.4 2026-02-26 02:00:55 +00:00
bot 9f8c061f29 Merge pull request 'Lock file maintenance' (#405) from renovate/lock-file-maintenance into main 2026-02-22 03:01:18 +01:00
Renovate Bot ef4e8f64dd Lock file maintenance 2026-02-22 02:01:15 +00:00
bot 2065e60c3b Merge pull request 'Lock file maintenance' (#404) from renovate/lock-file-maintenance into main 2026-02-21 03:01:16 +01:00
Renovate Bot ac0eeefb26 Lock file maintenance 2026-02-21 02:01:13 +00:00
bot 22343cc7c6 Merge pull request 'Update dependency jsdom to v28.1.0' (#403) from renovate/jsdom-28.x-lockfile into main 2026-02-16 03:01:16 +01:00
Renovate Bot c8840fe925 Update dependency jsdom to v28.1.0 2026-02-16 02:01:06 +00:00
bot 0bc2614c0e Merge pull request 'Lock file maintenance' (#402) from renovate/lock-file-maintenance into main 2026-02-15 03:01:31 +01:00
Renovate Bot 28400ede30 Lock file maintenance 2026-02-15 02:01:29 +00:00
bot 91c996a0f1 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.02.00' (#399) from renovate/androidx.compose-compose-bom-2026.x into main 2026-02-15 03:01:06 +01:00
Renovate Bot bb602ea38f Update dependency androidx.compose:compose-bom to v2026.02.00 2026-02-15 02:01:02 +00:00
bot fc33efecf7 Merge pull request 'Update plugin com.android.library to v9.0.1' (#401) from renovate/com.android.library-9.x into main 2026-02-15 03:00:53 +01:00
Renovate Bot 832b7d0ee1 Update plugin com.android.library to v9.0.1 2026-02-15 02:00:50 +00:00
bot 2755201920 Merge pull request 'Update dependency @turf/turf to v7.3.4' (#395) from renovate/turf-turf-7.x-lockfile into main 2026-02-14 03:01:33 +01:00
Renovate Bot 7c953a706a Update dependency @turf/turf to v7.3.4 2026-02-14 02:01:31 +00:00
bot d26adae47c Merge pull request 'Update plugin com.android.application to v9.0.1' (#400) from renovate/com.android.application-9.x into main 2026-02-14 03:01:16 +01:00
Renovate Bot 940aba5863 Update plugin com.android.application to v9.0.1 2026-02-14 02:01:14 +00:00
bot a88ea3a5b8 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.3' (#398) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-02-13 03:01:24 +01:00
Renovate Bot af7b3e7b2c Update dependency androidx.compose.ui:ui-tooling to v1.10.3 2026-02-13 02:01:21 +00:00
bot 57007049b4 Merge pull request 'Update dependency androidx.compose.material:material to v1.10.3' (#397) from renovate/androidx.compose.material-material-1.x into main 2026-02-12 03:01:19 +01:00
bot e52e8cba27 Merge pull request 'Update dependency androidx.activity:activity-ktx to v1.12.4' (#396) from renovate/androidx.activity-activity-ktx-1.x into main 2026-02-12 03:01:17 +01:00
Renovate Bot 3a675af015 Update dependency androidx.compose.material:material to v1.10.3 2026-02-12 02:01:15 +00:00
Renovate Bot 07c2926d47 Update dependency androidx.activity:activity-ktx to v1.12.4 2026-02-12 02:01:10 +00:00
bot bc29c93e72 Merge pull request 'Update dependency @turf/area to v7.3.4' (#394) from renovate/turf-area-7.x-lockfile into main 2026-02-09 03:01:05 +01:00
Renovate Bot a8b0c99b1c Update dependency @turf/area to v7.3.4 2026-02-09 02:00:58 +00:00
bot 027af22a55 Merge pull request 'Lock file maintenance' (#393) from renovate/lock-file-maintenance into main 2026-02-08 03:01:16 +01:00
Renovate Bot c5ea332f6e Lock file maintenance 2026-02-08 02:01:10 +00:00
bot c80dcfc721 Merge pull request 'Lock file maintenance' (#392) from renovate/lock-file-maintenance into main 2026-02-07 03:08:46 +01:00
Renovate Bot 9c415da2cf Lock file maintenance 2026-02-07 02:08:45 +00:00
bot 52a4bc1c21 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.10' (#391) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2026-02-07 03:08:14 +01:00
Renovate Bot 45128b0c41 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.10 2026-02-07 02:00:43 +00:00
bot 97649f9f43 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.10' (#390) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2026-02-06 03:09:15 +01:00
bot 3de4677121 Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.3.10' (#389) from renovate/org.jetbrains.kotlin.android-2.x into main 2026-02-06 03:09:06 +01:00
Renovate Bot 03f9777c9a Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.10 2026-02-06 02:09:05 +00:00
Renovate Bot 1124d239d9 Update plugin org.jetbrains.kotlin.android to v2.3.10 2026-02-06 02:00:50 +00:00
bot 4efc445ac5 Merge pull request 'Update dependency jsdom to v28' (#388) from renovate/jsdom-28.x into main 2026-02-03 03:01:18 +01:00
Renovate Bot 960a167b44 Update dependency jsdom to v28 2026-02-03 02:01:12 +00:00
bot d4eeadc4f5 Merge pull request 'Update dependency gradle to v9.3.1' (#387) from renovate/gradle-9.x into main 2026-02-02 03:01:58 +01:00
Renovate Bot 3936601599 Update dependency gradle to v9.3.1 2026-02-02 02:01:39 +00:00
bot 453ba4c867 Merge pull request 'Update dependency androidx.navigation:navigation-compose to v2.9.7' (#386) from renovate/androidx.navigation-navigation-compose-2.x into main 2026-02-01 03:01:34 +01:00
Renovate Bot 55d25db229 Update dependency androidx.navigation:navigation-compose to v2.9.7 2026-02-01 02:01:32 +00:00
bot 528789cbfe Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.2' (#385) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-02-01 03:01:22 +01:00
Renovate Bot 867584dbb0 Update dependency androidx.compose.ui:ui-tooling to v1.10.2 2026-02-01 02:01:19 +00:00
bot e19c8eba7f Merge pull request 'Update dependency androidx.compose.material:material to v1.10.2' (#384) from renovate/androidx.compose.material-material-1.x into main 2026-01-31 03:01:59 +01:00
Renovate Bot 867ec40141 Update dependency androidx.compose.material:material to v1.10.2 2026-01-31 02:01:57 +00:00
bot dd0fd4041c Merge pull request 'Update dependency @turf/turf to v7.3.3' (#381) from renovate/turf-turf-7.x-lockfile into main 2026-01-31 03:01:37 +01:00
Renovate Bot b772d8769c Update dependency @turf/turf to v7.3.3 2026-01-31 02:01:31 +00:00
bot 92b6e98060 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026.01.01' (#383) from renovate/androidx.compose-compose-bom-2026.x into main 2026-01-30 03:01:30 +01:00
bot 0919c237c8 Merge pull request 'Update dependency androidx.activity:activity-ktx to v1.12.3' (#382) from renovate/androidx.activity-activity-ktx-1.x into main 2026-01-30 03:01:28 +01:00
Renovate Bot afd79eb2c7 Update dependency androidx.compose:compose-bom to v2026.01.01 2026-01-30 02:01:28 +00:00
Renovate Bot d953984df1 Update dependency androidx.activity:activity-ktx to v1.12.3 2026-01-30 02:01:26 +00:00
bot f606a5c57c Merge pull request 'Update dependency @turf/area to v7.3.3' (#380) from renovate/turf-area-7.x-lockfile into main 2026-01-29 03:01:20 +01:00
Renovate Bot 3e1bfe44df Update dependency @turf/area to v7.3.3 2026-01-29 02:01:13 +00:00
bot 63a552934c Merge pull request 'Lock file maintenance' (#379) from renovate/lock-file-maintenance into main 2026-01-24 03:01:34 +01:00
Renovate Bot 74faab692d Lock file maintenance 2026-01-24 02:01:30 +00:00
bot 23c866c743 Merge pull request 'Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.10.0' (#378) from renovate/org.jetbrains.kotlinx-kotlinx-serialization-json-1.x into main 2026-01-22 03:01:09 +01:00
Renovate Bot 8836c41622 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.10.0 2026-01-22 02:01:04 +00:00
bot f1249506a6 Merge pull request 'Update plugin com.android.library to v9' (#377) from renovate/com.android.library-9.x into main 2026-01-19 03:00:48 +01:00
Renovate Bot 9b62593811 Update plugin com.android.library to v9 2026-01-19 02:00:45 +00:00
bot b201c770e8 Merge pull request 'Update plugin com.android.application to v9' (#376) from renovate/com.android.application-9.x into main 2026-01-18 03:01:02 +01:00
Renovate Bot 94e64bcf0d Update plugin com.android.application to v9 2026-01-18 02:01:00 +00:00
bot 12f7913e77 Merge pull request 'Update dependency androidx.compose:compose-bom to v2026' (#375) from renovate/androidx.compose-compose-bom-2026.x into main 2026-01-18 03:00:53 +01:00
Renovate Bot 5866bf837c Update dependency androidx.compose:compose-bom to v2026 2026-01-18 02:00:51 +00:00
bot 9c11c96c00 Merge pull request 'Update dependency @turf/turf to v7.3.2' (#371) from renovate/turf-turf-7.x-lockfile into main 2026-01-17 03:02:24 +01:00
Renovate Bot 6a13d7a779 Update dependency @turf/turf to v7.3.2 2026-01-17 02:02:23 +00:00
bot c71129d6a3 Merge pull request 'Update dependency gradle to v9.3.0' (#374) from renovate/gradle-9.x into main 2026-01-17 03:02:09 +01:00
Renovate Bot a588c8509d Update dependency gradle to v9.3.0 2026-01-17 02:01:57 +00:00
bot b93082dab1 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.1' (#373) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2026-01-16 03:01:25 +01:00
bot 495312e7cd Merge pull request 'Update dependency androidx.compose.material:material to v1.10.1' (#372) from renovate/androidx.compose.material-material-1.x into main 2026-01-16 03:01:23 +01:00
Renovate Bot 01d7903fa6 Update dependency androidx.compose.ui:ui-tooling to v1.10.1 2026-01-16 02:01:21 +00:00
Renovate Bot cf30d5f265 Update dependency androidx.compose.material:material to v1.10.1 2026-01-16 02:01:19 +00:00
bot 6294502850 Merge pull request 'Update dependency @turf/area to v7.3.2' (#370) from renovate/turf-area-7.x-lockfile into main 2026-01-15 03:01:03 +01:00
Renovate Bot 1b692865dc Update dependency @turf/area to v7.3.2 2026-01-15 02:00:58 +00:00
bot e20eee2f84 Merge pull request 'Lock file maintenance' (#369) from renovate/lock-file-maintenance into main 2026-01-11 03:01:13 +01:00
Renovate Bot 8d61249b6a Lock file maintenance 2026-01-11 02:01:11 +00:00
bot 2265e4f4cc Merge pull request 'Lock file maintenance' (#368) from renovate/lock-file-maintenance into main 2026-01-10 03:01:18 +01:00
Renovate Bot e6b290d991 Lock file maintenance 2026-01-10 02:01:15 +00:00
bot b4da9656d6 Merge pull request 'Lock file maintenance' (#367) from renovate/lock-file-maintenance into main 2026-01-03 13:23:19 +01:00
Renovate Bot 34079e700d Lock file maintenance 2026-01-03 12:23:17 +00:00
bot 910d1de178 Merge pull request 'Lock file maintenance' (#366) from renovate/lock-file-maintenance into main 2025-12-27 03:01:30 +01:00
Renovate Bot e2296859c5 Lock file maintenance 2025-12-27 02:01:28 +00:00
bot 267126e664 Merge pull request 'Update dependency jsdom to v27.4.0' (#365) from renovate/jsdom-27.x-lockfile into main 2025-12-27 03:01:05 +01:00
Renovate Bot fe0ee5c463 Update dependency jsdom to v27.4.0 2025-12-27 02:00:57 +00:00
bot 064be48564 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v13.2.1' (#364) from renovate/com.mikepenz.aboutlibraries.plugin-13.x into main 2025-12-21 03:01:07 +01:00
Renovate Bot b8a1a0e0bc Update plugin com.mikepenz.aboutlibraries.plugin to v13.2.1 2025-12-21 02:01:05 +00:00
bot fb30049f04 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v13.2.1' (#363) from renovate/com.mikepenz-aboutlibraries-core-13.x into main 2025-12-21 03:00:57 +01:00
Renovate Bot 4c6a556cc8 Update dependency com.mikepenz:aboutlibraries-core to v13.2.1 2025-12-21 02:00:51 +00:00
bot 2c45e4dfe4 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v13.2.1' (#362) from renovate/com.mikepenz-aboutlibraries-compose-m3-13.x into main 2025-12-20 03:01:04 +01:00
Renovate Bot c2eec3965f Update dependency com.mikepenz:aboutlibraries-compose-m3 to v13.2.1 2025-12-20 02:01:02 +00:00
bot dad18f6c0c Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v13.2.1' (#361) from renovate/com.mikepenz-aboutlibraries-13.x into main 2025-12-20 03:00:54 +01:00
Renovate Bot 3bf8705179 Update dependency com.mikepenz:aboutlibraries to v13.2.1 2025-12-20 02:00:47 +00:00
bot c1df3f6a92 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.0' (#360) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2025-12-19 03:08:55 +01:00
Renovate Bot cfc5ba086a Update plugin org.jetbrains.kotlin.plugin.serialization to v2.3.0 2025-12-19 02:00:48 +00:00
bot 1431d384db Merge pull request 'Update dependency androidx.compose:compose-bom to v2025.12.01' (#359) from renovate/androidx.compose-compose-bom-2025.x into main 2025-12-18 03:00:55 +01:00
bot 9fad8233b2 Merge pull request 'Update dependency androidx.activity:activity-ktx to v1.12.2' (#358) from renovate/androidx.activity-activity-ktx-1.x into main 2025-12-18 03:00:51 +01:00
Renovate Bot b985b9dee0 Update dependency androidx.compose:compose-bom to v2025.12.01 2025-12-18 02:00:50 +00:00
Renovate Bot 22ced4b782 Update dependency androidx.activity:activity-ktx to v1.12.2 2025-12-18 02:00:47 +00:00
bot 64090e0eb1 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.0' (#357) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2025-12-17 03:09:59 +01:00
bot 11cf1787b2 Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.3.0' (#356) from renovate/org.jetbrains.kotlin.android-2.x into main 2025-12-17 03:09:50 +01:00
Renovate Bot 2ca47892c1 Update plugin org.jetbrains.kotlin.plugin.compose to v2.3.0 2025-12-17 02:09:48 +00:00
Renovate Bot ec71180c95 Update plugin org.jetbrains.kotlin.android to v2.3.0 2025-12-17 02:01:05 +00:00
bot db10def0fb Merge pull request 'Lock file maintenance' (#355) from renovate/lock-file-maintenance into main 2025-12-14 03:01:23 +01:00
Renovate Bot 38b0995523 Lock file maintenance 2025-12-14 02:01:20 +00:00
bot a1384e7594 Merge pull request 'Update plugin com.android.library to v8.13.2' (#353) from renovate/com.android.library-8.x into main 2025-12-13 03:01:30 +01:00
Renovate Bot fac01dc0ca Update plugin com.android.library to v8.13.2 2025-12-13 02:01:29 +00:00
bot 063649a5df Merge pull request 'Lock file maintenance' (#354) from renovate/lock-file-maintenance into main 2025-12-13 03:01:22 +01:00
Renovate Bot bb6baa3fe8 Lock file maintenance 2025-12-13 02:01:20 +00:00
bot 6e0b2632ff Merge pull request 'Update plugin com.android.application to v8.13.2' (#352) from renovate/com.android.application-8.x into main 2025-12-12 03:00:48 +01:00
Renovate Bot 33cd4c17da Update plugin com.android.application to v8.13.2 2025-12-12 02:00:45 +00:00
bot 62eb91bdf3 Merge pull request 'Update dependency jsdom to v27.3.0' (#351) from renovate/jsdom-27.x-lockfile into main 2025-12-09 03:01:06 +01:00
Renovate Bot 6a717ca652 Update dependency jsdom to v27.3.0 2025-12-09 02:01:00 +00:00
bot 0eb8337089 Merge pull request 'Lock file maintenance' (#350) from renovate/lock-file-maintenance into main 2025-12-07 03:01:51 +01:00
Renovate Bot a68eceab6d Lock file maintenance 2025-12-07 02:01:46 +00:00
bot 78fefa9bd2 Merge pull request 'Lock file maintenance' (#349) from renovate/lock-file-maintenance into main 2025-12-06 03:01:23 +01:00
Renovate Bot dcf8f05657 Lock file maintenance 2025-12-06 02:01:20 +00:00
bot 0a327cbea1 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.10.0' (#348) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2025-12-05 03:00:54 +01:00
bot 240669f9f2 Merge pull request 'Update dependency androidx.compose.material:material to v1.10.0' (#347) from renovate/androidx.compose.material-material-1.x into main 2025-12-05 03:00:52 +01:00
Renovate Bot b1c1fe78f3 Update dependency androidx.compose.ui:ui-tooling to v1.10.0 2025-12-05 02:00:50 +00:00
Renovate Bot afab88a939 Update dependency androidx.compose.material:material to v1.10.0 2025-12-05 02:00:48 +00:00
bot 0b5669cb52 Merge pull request 'Update dependency androidx.compose:compose-bom to v2025.12.00' (#346) from renovate/androidx.compose-compose-bom-2025.x into main 2025-12-04 03:00:51 +01:00
bot e7af455803 Merge pull request 'Update dependency androidx.activity:activity-ktx to v1.12.1' (#345) from renovate/androidx.activity-activity-ktx-1.x into main 2025-12-04 03:00:50 +01:00
Renovate Bot ca8231d988 Update dependency androidx.compose:compose-bom to v2025.12.00 2025-12-04 02:00:49 +00:00
Renovate Bot 1cf43f41d3 Update dependency androidx.activity:activity-ktx to v1.12.1 2025-12-04 02:00:46 +00:00
bot 52d2aa37fa Merge pull request 'Lock file maintenance' (#344) from renovate/lock-file-maintenance into main 2025-11-30 03:01:05 +01:00
Renovate Bot b1a96d1003 Lock file maintenance 2025-11-30 02:01:03 +00:00
bot d858795a54 Merge pull request 'Lock file maintenance' (#343) from renovate/lock-file-maintenance into main 2025-11-29 03:01:27 +01:00
Renovate Bot dbec6ade39 Lock file maintenance 2025-11-29 02:01:25 +00:00
bot 937cd31bac Merge pull request 'Update dependency @turf/area to v7.3.1' (#341) from renovate/turf-area-7.x-lockfile into main 2025-11-28 03:01:01 +01:00
Renovate Bot ad03d60934 Update dependency @turf/area to v7.3.1 2025-11-28 02:00:56 +00:00
bot 55f9e52c19 Merge pull request 'Lock file maintenance' (#340) from renovate/lock-file-maintenance into main 2025-11-23 03:01:24 +01:00
Renovate Bot af7ebfc6fb Lock file maintenance 2025-11-23 02:01:22 +00:00
bot 2b3c4cea80 Merge pull request 'Lock file maintenance' (#339) from renovate/lock-file-maintenance into main 2025-11-22 03:01:39 +01:00
Renovate Bot 16bfc6cbba Lock file maintenance 2025-11-22 02:01:38 +00:00
bot aa0dfa23fa Merge pull request 'Update actions/checkout action to v6' (#338) from renovate/actions-checkout-6.x into main 2025-11-22 03:00:50 +01:00
Renovate Bot 4c245337bd Update actions/checkout action to v6 2025-11-22 02:00:48 +00:00
bot 00adc184d7 Merge pull request 'Update dependency androidx.activity:activity-ktx to v1.12.0' (#337) from renovate/androidx.activity-activity-ktx-1.x into main 2025-11-21 03:00:58 +01:00
bot 32210875c4 Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.9.5' (#336) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2025-11-21 03:00:54 +01:00
Renovate Bot 63a10cbd12 Update dependency androidx.activity:activity-ktx to v1.12.0 2025-11-21 02:00:52 +00:00
Renovate Bot 43cca9e874 Update dependency androidx.compose.ui:ui-tooling to v1.9.5 2025-11-21 02:00:49 +00:00
bot d97c63f75f Merge pull request 'Update dependency androidx.compose.material:material to v1.9.5' (#335) from renovate/androidx.compose.material-material-1.x into main 2025-11-20 03:00:52 +01:00
bot 50e5e74f59 Merge pull request 'Update dependency androidx.compose:compose-bom to v2025.11.01' (#334) from renovate/androidx.compose-compose-bom-2025.x into main 2025-11-20 03:00:51 +01:00
Renovate Bot 94561e2f96 Update dependency androidx.compose.material:material to v1.9.5 2025-11-20 02:00:50 +00:00
Renovate Bot 23513fc8fb Update dependency androidx.compose:compose-bom to v2025.11.01 2025-11-20 02:00:48 +00:00
bot bea9bb0b96 Merge pull request 'Update dependency @turf/turf to v7.3.0' (#333) from renovate/turf-turf-7.x-lockfile into main 2025-11-19 03:01:22 +01:00
Renovate Bot 0da8f01a92 Update dependency @turf/turf to v7.3.0 2025-11-19 02:01:17 +00:00
bot ede977452d Merge pull request 'Update dependency @turf/area to v7.3.0' (#332) from renovate/turf-area-7.x-lockfile into main 2025-11-18 03:02:02 +01:00
Renovate Bot e12494e5ed Update dependency @turf/area to v7.3.0 2025-11-18 02:01:57 +00:00
bot 1c7c66ee50 Merge pull request 'Update dependency gradle to v9.2.1' (#331) from renovate/gradle-9.x into main 2025-11-18 03:01:47 +01:00
Renovate Bot 88f86c75c8 Update dependency gradle to v9.2.1 2025-11-18 02:01:31 +00:00
bot 036db580f5 Merge pull request 'Lock file maintenance' (#330) from renovate/lock-file-maintenance into main 2025-11-15 03:01:49 +01:00
Renovate Bot 6dcd4a9e25 Lock file maintenance 2025-11-15 02:01:38 +00:00
bot da276fb8f8 Merge pull request 'Update dependency jsdom to v27.2.0' (#329) from renovate/jsdom-27.x-lockfile into main 2025-11-13 03:01:17 +01:00
Renovate Bot 05301b7bd5 Update dependency jsdom to v27.2.0 2025-11-13 02:01:09 +00:00
bot 78c35c28b3 Merge pull request 'Update plugin com.android.library to v8.13.1' (#328) from renovate/com.android.library-8.x into main 2025-11-12 03:01:00 +01:00
Renovate Bot 863ede58b4 Update plugin com.android.library to v8.13.1 2025-11-12 02:00:55 +00:00
bot 7d6692b17a Merge pull request 'Update plugin com.android.application to v8.13.1' (#327) from renovate/com.android.application-8.x into main 2025-11-11 03:00:58 +01:00
Renovate Bot 4f31eadbd4 Update plugin com.android.application to v8.13.1 2025-11-11 02:00:55 +00:00
bot 56d2d2684d Merge pull request 'Lock file maintenance' (#326) from renovate/lock-file-maintenance into main 2025-11-09 03:01:23 +01:00
Renovate Bot 0668c65bfe Lock file maintenance 2025-11-09 02:01:20 +00:00
bot abe3d5e958 Merge pull request 'Lock file maintenance' (#325) from renovate/lock-file-maintenance into main 2025-11-08 03:01:20 +01:00
Renovate Bot 7379e27f90 Lock file maintenance 2025-11-08 02:01:18 +00:00
bot be8b297235 Merge pull request 'Update dependency androidx.compose:compose-bom to v2025.11.00' (#324) from renovate/androidx.compose-compose-bom-2025.x into main 2025-11-06 03:01:58 +01:00
bot 64ad296815 Merge pull request 'Update dependency androidx.navigation:navigation-compose to v2.9.6' (#323) from renovate/androidx.navigation-navigation-compose-2.x into main 2025-11-06 03:01:49 +01:00
Renovate Bot 17266f3c0b Update dependency androidx.compose:compose-bom to v2025.11.00 2025-11-06 02:01:46 +00:00
Renovate Bot 0d9cae6550 Update dependency androidx.navigation:navigation-compose to v2.9.6 2025-11-06 02:01:38 +00:00
bot 41c1eb1257 Merge pull request 'Lock file maintenance' (#322) from renovate/lock-file-maintenance into main 2025-11-01 03:02:17 +01:00
Renovate Bot 4a6164ac84 Lock file maintenance 2025-11-01 02:02:15 +00:00
bot aec008e7be Merge pull request 'Update dependency jsdom to v27.1.0' (#321) from renovate/jsdom-27.x-lockfile into main 2025-11-01 03:01:24 +01:00
Renovate Bot 0d76b994a2 Update dependency jsdom to v27.1.0 2025-11-01 02:01:15 +00:00
bot 14bfaabb11 Merge pull request 'Update dependency gradle to v9.2.0' (#320) from renovate/gradle-9.x into main 2025-10-30 03:01:38 +01:00
Renovate Bot d77c5ec391 Update dependency gradle to v9.2.0 2025-10-30 02:01:23 +00:00
bot b793ac7a6e Merge pull request 'Lock file maintenance' (#319) from renovate/lock-file-maintenance into main 2025-10-26 03:01:47 +01:00
Renovate Bot d04e78694c Lock file maintenance 2025-10-26 02:01:40 +00:00
bot d6dd4e6b96 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2.2.21' (#318) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main 2025-10-25 04:09:16 +02:00
Renovate Bot 44e5c54c77 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.2.21 2025-10-25 02:09:08 +00:00
bot bdb49cd085 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.compose to v2.2.21' (#317) from renovate/org.jetbrains.kotlin.plugin.compose-2.x into main 2025-10-25 04:08:56 +02:00
Renovate Bot e76aca71eb Update plugin org.jetbrains.kotlin.plugin.compose to v2.2.21 2025-10-25 02:01:01 +00:00
bot 98321b4c3b Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2.2.21' (#316) from renovate/org.jetbrains.kotlin.android-2.x into main 2025-10-24 04:06:06 +02:00
bot b082fc44dc Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.9.4' (#315) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2025-10-24 04:00:59 +02:00
Renovate Bot 0776814283 Update plugin org.jetbrains.kotlin.android to v2.2.21 2025-10-24 02:00:57 +00:00
Renovate Bot 3a9718d1a7 Update dependency androidx.compose.ui:ui-tooling to v1.9.4 2025-10-24 02:00:55 +00:00
bot d9ef921a43 Merge pull request 'Update dependency androidx.compose.material:material to v1.9.4' (#314) from renovate/androidx.compose.material-material-1.x into main 2025-10-23 04:00:52 +02:00
bot c415e8a829 Merge pull request 'Update dependency androidx.compose:compose-bom to v2025.10.01' (#313) from renovate/androidx.compose-compose-bom-2025.x into main 2025-10-23 04:00:50 +02:00
Renovate Bot daa291d5de Update dependency androidx.compose.material:material to v1.9.4 2025-10-23 02:00:50 +00:00
Renovate Bot d2987ebe1e Update dependency androidx.compose:compose-bom to v2025.10.01 2025-10-23 02:00:47 +00:00
bot ed32fd1183 Merge pull request 'Lock file maintenance' (#312) from renovate/lock-file-maintenance into main 2025-10-19 04:01:44 +02:00
Renovate Bot 83b1ec9c92 Lock file maintenance 2025-10-19 02:01:42 +00:00
bot 5e982e93ee Merge pull request 'Update dependency jsdom to v27.0.1' (#311) from renovate/jsdom-27.x-lockfile into main 2025-10-19 04:01:10 +02:00
Renovate Bot 846081536d Update dependency jsdom to v27.0.1 2025-10-19 02:01:03 +00:00
bot 9f2f7844f9 Merge pull request 'Lock file maintenance' (#310) from renovate/lock-file-maintenance into main 2025-10-18 04:01:33 +02:00
Renovate Bot 9966bba33c Lock file maintenance 2025-10-18 02:01:31 +00:00
bot ae59b054c9 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v13.1.0' (#308) from renovate/com.mikepenz-aboutlibraries-core-13.x into main 2025-10-18 04:00:51 +02:00
Renovate Bot 8bed6b040b Update dependency com.mikepenz:aboutlibraries-core to v13.1.0 2025-10-16 02:00:51 +00:00
bot d1933ec661 Merge pull request 'Update plugin com.mikepenz.aboutlibraries.plugin to v13' (#309) from renovate/com.mikepenz.aboutlibraries.plugin-13.x into main 2025-10-15 04:01:20 +02:00
Renovate Bot b40932624a Update plugin com.mikepenz.aboutlibraries.plugin to v13 2025-10-15 02:01:17 +00:00
bot 6c5e727c67 Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v13.1.0' (#307) from renovate/com.mikepenz-aboutlibraries-compose-m3-13.x into main 2025-10-15 04:01:15 +02:00
Renovate Bot 6420ae2f13 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v13.1.0 2025-10-15 02:01:07 +00:00
bot 91b234777d Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v13.1.0' (#306) from renovate/com.mikepenz-aboutlibraries-13.x into main 2025-10-14 04:01:25 +02:00
Renovate Bot b1a646f1f2 Update dependency com.mikepenz:aboutlibraries to v13.1.0 2025-10-14 02:01:17 +00:00
bot 89134681f3 Merge pull request 'Lock file maintenance' (#305) from renovate/lock-file-maintenance into main 2025-10-12 04:01:39 +02:00
Renovate Bot 3b670b8ce2 Lock file maintenance 2025-10-12 02:01:36 +00:00
bot b0ba07a7fb Merge pull request 'Update dependency com.mikepenz:aboutlibraries-core to v13' (#304) from renovate/com.mikepenz-aboutlibraries-core-13.x into main 2025-10-12 04:00:55 +02:00
Renovate Bot 07adda1d74 Update dependency com.mikepenz:aboutlibraries-core to v13 2025-10-12 02:00:48 +00:00
bot 6a86d05bad Merge pull request 'Update dependency com.mikepenz:aboutlibraries-compose-m3 to v13' (#303) from renovate/com.mikepenz-aboutlibraries-compose-m3-13.x into main 2025-10-11 04:01:08 +02:00
Renovate Bot dc94c465d6 Update dependency com.mikepenz:aboutlibraries-compose-m3 to v13 2025-10-11 02:01:06 +00:00
bot 950bfbb42e Merge pull request 'Update dependency com.mikepenz:aboutlibraries to v13' (#302) from renovate/com.mikepenz-aboutlibraries-13.x into main 2025-10-11 04:00:57 +02:00
Renovate Bot 927ac05f03 Update dependency com.mikepenz:aboutlibraries to v13 2025-10-11 02:00:51 +00:00
bot d6d84c6892 Merge pull request 'Update dependency androidx.compose:compose-bom to v2025.10.00' (#301) from renovate/androidx.compose-compose-bom-2025.x into main 2025-10-10 04:00:51 +02:00
Renovate Bot a34c1dbb11 Update dependency androidx.compose:compose-bom to v2025.10.00 2025-10-10 02:00:48 +00:00
bot 6df13a044b Merge pull request 'Update dependency androidx.compose.ui:ui-tooling to v1.9.3' (#300) from renovate/androidx.compose.ui-ui-tooling-1.x into main 2025-10-09 04:00:57 +02:00
bot 68fb0023da Merge pull request 'Update dependency androidx.compose.material:material to v1.9.3' (#299) from renovate/androidx.compose.material-material-1.x into main 2025-10-09 04:00:53 +02:00
Renovate Bot 686982c096 Update dependency androidx.compose.ui:ui-tooling to v1.9.3 2025-10-09 02:00:52 +00:00
Renovate Bot 84898895df Update dependency androidx.compose.material:material to v1.9.3 2025-10-09 02:00:49 +00:00
soraefir 472237ba8f wip 2025-10-07 22:38:21 +02:00
Renovate Bot 2ae7e1fb8c Lock file maintenance 2025-10-04 02:00:41 +00:00
Renovate Bot ed4d751dce Update dependency mapshaper to v0.6.113 2025-10-01 02:00:38 +00:00
Renovate Bot b5274d2113 Lock file maintenance 2025-09-28 02:00:47 +00:00
Renovate Bot d7ad8ebd74 Lock file maintenance 2025-09-27 02:01:04 +00:00
Renovate Bot 43a521de16 Update dependency androidx.compose.material3:material3 to v1.4.0 2025-09-27 02:00:37 +00:00
Renovate Botandbot 4cf8c497a8 Update dependency androidx.navigation:navigation-compose to v2.9.5 2025-09-26 04:00:37 +02:00
Renovate Bot fd57b5dee4 Update dependency androidx.compose.ui:ui-tooling to v1.9.2 2025-09-26 02:00:30 +00:00
Renovate Botandbot 49b80dc3b9 Update dependency androidx.compose.material:material to v1.9.2 2025-09-25 04:00:37 +02:00
Renovate Bot c0e3943700 Update dependency androidx.compose:compose-bom to v2025.09.01 2025-09-25 02:00:27 +00:00
sora f1608179f8 Update app/build.gradle 2025-09-21 13:22:34 +02:00
soraefir 86aa888be6 Fix build 2025-09-21 13:09:36 +02:00
soraefir 9c1374c99f Update and cleanup 2025-09-21 12:58:00 +02:00
soraefir 6b2f786afe Update and cleanup 2025-09-21 12:44:25 +02:00
Renovate Bot 2ded750c46 Lock file maintenance 2025-09-21 02:00:42 +00:00
Renovate Bot 686be17fd2 Lock file maintenance 2025-09-20 02:00:45 +00:00
Renovate Bot 7339e16e61 Update dependency gradle to v9.1.0 2025-09-19 02:01:07 +00:00
Renovate Bot e43264b327 Lock file maintenance 2025-09-14 02:00:58 +00:00
Renovate Bot 532a34cc7f Update dependency jsdom to v27 2025-09-14 02:00:34 +00:00
Renovate Bot 59a02332d0 Lock file maintenance 2025-09-13 02:00:54 +00:00
Renovate Bot 02a83a491f Update dependency mapshaper to v0.6.112 2025-09-13 02:00:33 +00:00
Renovate Botandbot 0a01aa95f1 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.2.20 2025-09-11 04:08:17 +02:00
Renovate Bot 438eae23b0 Update plugin org.jetbrains.kotlin.android to v2.2.20 2025-09-11 02:00:28 +00:00
Renovate Botandbot 796faf393e Update actions/setup-java action to v5 2025-09-10 04:00:33 +02:00
Renovate Bot 9643d0ebb8 Update plugin com.android.library to v8.13.0 2025-09-10 02:00:25 +00:00
Renovate Bot 0999a0c512 Update plugin com.android.application to v8.13.0 2025-09-09 02:00:27 +00:00
Renovate Botandbot 7a3eb6c8f3 Update dependency com.google.android.material:material to v1.13.0 2025-09-08 04:08:06 +02:00
Renovate Bot 5a01c846fa Update plugin org.jetbrains.kotlin.plugin.serialization to v2.2.10 2025-09-08 02:00:25 +00:00
Renovate Bot 4533f36bd2 Update plugin org.jetbrains.kotlin.android to v2.2.10 2025-09-07 02:01:03 +00:00
Renovate Bot ec45f36ec4 Update dependency mapshaper to v0.6.111 2025-09-07 02:00:45 +00:00
Renovate Bot 5396b570ec Update actions/checkout action to v5 2025-08-12 02:01:07 +00:00
Renovate Bot d0c656e3d0 Lock file maintenance 2025-08-09 02:02:05 +00:00
Renovate Bot adc416a5e3 Update dependency gradle to v9 2025-08-02 02:01:52 +00:00
Renovate Bot b30430ec7d Update plugin com.android.library to v8.12.0 2025-08-02 02:01:04 +00:00
Renovate Botandbot 767d018d45 Update plugin com.android.application to v8.12.0 2025-08-01 04:01:25 +02:00
Renovate Bot cbfc484ed8 Update dependency mapshaper to v0.6.109 2025-08-01 02:01:15 +00:00
Renovate Bot 39418809f6 Update dependency mapshaper to v0.6.108 2025-07-28 02:01:11 +00:00
Renovate Bot 40ab056bc0 Update dependency mapshaper to v0.6.107 2025-07-27 02:01:33 +00:00
Renovate Bot ad64b289d4 Lock file maintenance 2025-07-26 02:02:02 +00:00
Renovate Bot 5bdb16b48b Lock file maintenance 2025-07-20 02:01:34 +00:00
Renovate Bot 89ca3200b4 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.2.0 2025-07-19 02:06:55 +00:00
Renovate Bot 54dbf74721 Update plugin org.jetbrains.kotlin.android to v2.2.0 2025-07-14 02:01:00 +00:00
Renovate Bot 2cc7a69b0f Lock file maintenance 2025-07-13 02:07:16 +00:00
Renovate Bot 37026870c7 Update plugin com.android.library to v8.11.1 2025-07-13 02:01:00 +00:00
Renovate Bot 8a9e389a53 Update plugin com.mikepenz.aboutlibraries.plugin to v12.2.4 2025-07-12 02:01:12 +00:00
Renovate Bot 7a2bae0e6b Update plugin com.android.application to v8.11.1 2025-07-11 02:00:59 +00:00
Renovate Bot 5b199aef8e Update plugin com.android.library to v8.11.0 2025-07-06 02:01:37 +00:00
Renovate Bot 99ff29af88 Update dependency mapshaper to v0.6.106 2025-07-06 02:01:20 +00:00
Renovate Bot 1d36d282f4 Update dependency gradle to v8.14.3 2025-07-05 02:02:10 +00:00
Renovate Bot 122dfe8981 Update dependency com.mikepenz:aboutlibraries to v12.2.4 2025-07-02 02:01:02 +00:00
Renovate Bot 0f9f428eed Update plugin com.android.application to v8.11.0 2025-06-29 02:01:32 +00:00
Renovate Bot ff4a2513c3 Update dependency mapshaper to v0.6.105 2025-06-29 02:01:17 +00:00
Renovate Bot 14a3fb9ffd Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.9.0 2025-06-28 02:01:27 +00:00
Renovate Bot 952bddf9e2 Update dependency mapshaper to v0.6.104 2025-06-28 02:01:12 +00:00
Renovate Bot f346a93b1f Lock file maintenance 2025-06-22 02:01:32 +00:00
Renovate Bot 7ac9dee6d8 Update plugin com.mikepenz.aboutlibraries.plugin to v12.2.3 2025-06-21 02:01:13 +00:00
Renovate Bot 767b98468b Update dependency com.mikepenz:aboutlibraries to v12.2.3 2025-06-16 02:01:01 +00:00
Renovate Bot 84c20349c7 Lock file maintenance 2025-06-15 02:01:33 +00:00
Renovate Bot 097b86b6a5 Update plugin com.mikepenz.aboutlibraries.plugin to v12.2.2 2025-06-14 02:01:11 +00:00
Renovate Bot 30f6853d98 Update dependency com.mikepenz:aboutlibraries to v12.2.2 2025-06-11 02:01:23 +00:00
Renovate Bot fe201a139d Lock file maintenance 2025-06-08 02:01:45 +00:00
Renovate Bot 4da09b509f Update plugin com.mikepenz.aboutlibraries.plugin to v12.2.1 2025-06-08 02:01:02 +00:00
Renovate Bot c009e750bb Update dependency gradle to v8.14.2 2025-06-07 02:01:58 +00:00
Renovate Bot 69ea4cf7c0 Update dependency com.mikepenz:aboutlibraries to v12.2.1 2025-06-07 02:01:01 +00:00
Renovate Bot 2f3c3ee84c Update plugin com.mikepenz.aboutlibraries.plugin to v12.2.0 2025-06-01 02:01:13 +00:00
Renovate Bot dbd84a91bf Update plugin com.android.library to v8.10.1 2025-06-01 02:01:03 +00:00
Renovate Bot 34560b2da2 Update dependency com.mikepenz:aboutlibraries to v12.2.0 2025-05-31 02:01:11 +00:00
Renovate Bot c8bb846d0f Update plugin com.android.application to v8.10.1 2025-05-29 02:01:00 +00:00
Renovate Bot 5bd6920857 Lock file maintenance 2025-05-24 02:01:48 +00:00
Renovate Bot 5080498b96 Update dependency gradle to v8.14.1 2025-05-23 02:01:40 +00:00
Renovate Bot 3cc522aaa5 Update plugin com.android.library to v8.10.0 2025-05-18 02:01:34 +00:00
Renovate Bot 832b06f424 Lock file maintenance 2025-05-18 02:01:26 +00:00
Renovate Bot 19dfd83351 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.21 2025-05-17 02:07:28 +00:00
Renovate Bot 75058f0705 Update plugin org.jetbrains.kotlin.android to v2.1.21 2025-05-14 02:01:24 +00:00
Renovate Bot da2d72246e Lock file maintenance 2025-05-11 02:02:16 +00:00
Renovate Bot e13517317f Update plugin com.android.application to v8.10.0 2025-05-11 02:01:25 +00:00
Renovate Bot ca6d74b581 Update plugin com.mikepenz.aboutlibraries.plugin to v12.1.2 2025-05-10 02:01:42 +00:00
Renovate Bot 23b47df111 Update dependency com.mikepenz:aboutlibraries to v12.1.2 2025-05-08 02:01:04 +00:00
Renovate Bot 38422cc30a Update dependency com.mikepenz:aboutlibraries to v12.1.0 2025-05-04 02:01:51 +00:00
Renovate Bot 2f314e5082 Lock file maintenance 2025-05-04 02:01:42 +00:00
Renovate Bot 27befa1790 Lock file maintenance 2025-05-03 02:01:55 +00:00
Renovate Bot 86aaa3899b Update plugin com.mikepenz.aboutlibraries.plugin to v12.1.0 2025-05-02 02:01:02 +00:00
Renovate Bot 205f81bdab Update plugin com.mikepenz.aboutlibraries.plugin to v12.1.0-exp01 2025-04-27 02:01:14 +00:00
Renovate Bot 9899f8038b Update plugin com.android.library to v8.9.2 2025-04-27 02:01:04 +00:00
Renovate Bot 62b3ff8500 Update dependency gradle to v8.14 2025-04-26 02:01:52 +00:00
Renovate Bot 2dbb4a2d77 Update plugin com.android.application to v8.9.2 2025-04-22 02:01:02 +00:00
Renovate Bot f4f56e0711 Lock file maintenance 2025-04-20 02:01:29 +00:00
Renovate Bot 8b4ceae602 Update plugin com.mikepenz.aboutlibraries.plugin to v12 2025-04-20 02:01:01 +00:00
Renovate Bot 6619e7c891 Update dependency com.mikepenz:aboutlibraries to v12 2025-04-19 02:01:27 +00:00
Renovate Bot 0aac38c05e Update dependency jsdom to v26.1.0 2025-04-17 02:01:14 +00:00
Renovate Bot cb67901e41 Lock file maintenance 2025-04-13 02:01:22 +00:00
Renovate Bot 4b418942da Lock file maintenance 2025-04-12 02:01:26 +00:00
Renovate Bot 5e03bb33e3 Lock file maintenance 2025-04-05 02:02:03 +00:00
Renovate Bot 22a75e9bc7 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.8.1 2025-04-02 02:01:16 +00:00
Renovate Bot 8ef3702f89 Lock file maintenance 2025-03-30 02:02:01 +00:00
Renovate Bot a89c916e1a Update plugin com.android.library to v8.9.1 2025-03-29 02:01:28 +00:00
Renovate Bot 6eeab1103a Update plugin com.android.application to v8.9.1 2025-03-25 02:01:16 +00:00
Renovate Bot 89c10a67fd Lock file maintenance 2025-03-23 02:01:32 +00:00
Renovate Bot aa2b503732 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.20 2025-03-22 02:05:27 +00:00
Renovate Bot f01fdbad00 Update plugin org.jetbrains.kotlin.android to v2.1.20 2025-03-21 02:01:17 +00:00
Renovate Bot 0fc5da8693 Lock file maintenance 2025-03-15 02:01:24 +00:00
Renovate Bot 891a15c1f9 Lock file maintenance 2025-03-09 02:01:22 +00:00
Renovate Bot ff7e05a747 Update plugin com.android.library to v8.9.0 2025-03-09 02:00:54 +00:00
Renovate Bot 9e741fe04e Lock file maintenance 2025-03-08 02:01:29 +00:00
Renovate Bot e8307d79f5 Update plugin com.android.application to v8.9.0 2025-03-05 02:00:56 +00:00
Renovate Bot ac694850da Update plugin com.android.library to v8.8.2 2025-03-02 02:02:03 +00:00
Renovate Bot 2aaf3661e5 Update dependency gradle to v8.13 2025-03-02 02:01:46 +00:00
Renovate Bot b661a7da47 Update plugin com.android.application to v8.8.2 2025-03-01 02:01:20 +00:00
Renovate Bot c38ca8d8c4 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.5 2025-02-26 02:01:02 +00:00
Renovate Bot 80a8c21cb0 Update plugin com.mikepenz.aboutlibraries.plugin to v11.6.3 2025-02-22 02:01:17 +00:00
Renovate Bot 944ff3529b Update dependency com.mikepenz:aboutlibraries to v11.6.3 2025-02-20 02:00:31 +00:00
Renovate Bot fe4ae26428 Update dependency com.mikepenz:aboutlibraries to v11.6.0 2025-02-16 02:01:15 +00:00
Renovate Bot bee13854f5 Update plugin com.android.library to v8.8.1 2025-02-16 02:00:55 +00:00
Renovate Bot c02cfa4344 Lock file maintenance 2025-02-15 02:01:36 +00:00
Renovate Bot a4376e5513 Update plugin com.android.application to v8.8.1 2025-02-14 02:00:46 +00:00
Renovate Bot 731ef956d2 Lock file maintenance 2025-02-08 02:01:55 +00:00
Renovate Bot 714abc2a96 Lock file maintenance 2025-02-02 02:01:35 +00:00
Renovate Bot 542fc9b01f Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.10 2025-02-01 02:05:00 +00:00
Renovate Bot 0e436cb1fc Update plugin org.jetbrains.kotlin.android to v2.1.10 2025-01-28 02:01:18 +00:00
Renovate Bot 324e887d68 Lock file maintenance 2025-01-26 02:02:15 +00:00
Renovate Bot c6c4c072a8 Update plugin com.mikepenz.aboutlibraries.plugin to v11.5.0 2025-01-26 02:01:29 +00:00
Renovate Bot 02f289d4d0 Update dependency com.mikepenz:aboutlibraries to v11.5.0 2025-01-25 02:02:21 +00:00
Renovate Bot fb89deabdf Update dependency gradle to v8.12.1 2025-01-25 02:02:01 +00:00
Renovate Bot 5ef6f76355 Lock file maintenance 2025-01-19 01:01:18 +00:00
Renovate Bot 40edcff528 Lock file maintenance 2025-01-18 01:01:31 +00:00
Renovate Bot 370fa6f15f Update dependency jsdom to v26 2025-01-13 01:00:55 +00:00
Renovate Bot 1b2a2f5ff4 Update plugin com.android.library to v8.8.0 2025-01-12 01:01:04 +00:00
Renovate Bot 0c7490dea4 Update plugin com.mikepenz.aboutlibraries.plugin to v11.4.0 2025-01-12 01:00:51 +00:00
Renovate Bot 5ffc1cce50 Update plugin com.android.application to v8.8.0 2025-01-11 01:01:08 +00:00
Renovate Bot bebfaf0921 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.8.0 2025-01-07 01:00:52 +00:00
Renovate Bot 21db9c6d23 Update dependency com.mikepenz:aboutlibraries to v11.4.0 2025-01-05 01:01:08 +00:00
Renovate Bot 5cb12372e1 Update dependency @turf/turf to v7.2.0 2025-01-04 01:01:11 +00:00
Renovate Bot 1c45957cc3 Update dependency @turf/area to v7.2.0 2024-12-30 01:00:56 +00:00
Renovate Bot 86e233e93b Lock file maintenance 2024-12-28 01:01:17 +00:00
Renovate Bot 2575e0f159 Lock file maintenance 2024-12-22 01:01:16 +00:00
Renovate Bot 4442b94211 Update dependency gradle to v8.12 2024-12-21 01:01:42 +00:00
Renovate Bot 9a88c4a943 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.4 2024-12-21 01:00:47 +00:00
Renovate Bot 6bf9570916 Lock file maintenance 2024-12-15 01:01:13 +00:00
Renovate Bot 1c7aca98c1 Lock file maintenance 2024-12-14 07:44:56 +00:00
Renovate Bot 4982e168ff Lock file maintenance 2024-12-08 01:01:39 +00:00
Renovate Bot 0b9e42ac6c Update plugin com.android.library to v8.7.3 2024-12-07 01:04:55 +00:00
Renovate Bot 05d44d274e Update plugin com.android.application to v8.7.3 2024-12-03 03:27:12 +00:00
Renovate Bot 25c06cd390 Lock file maintenance 2024-12-01 01:04:59 +00:00
Renovate Bot 0e7a939172 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.1.0 2024-11-30 01:05:00 +00:00
Renovate Bot 0b8d699d68 Update plugin org.jetbrains.kotlin.android to v2.1.0 2024-11-28 01:01:05 +00:00
Renovate Bot 34f9fb53bc Update dependency mapshaper to v0.6.102 2024-11-23 01:04:54 +00:00
Renovate Bot ae782b1c32 Update dependency gradle to v8.11.1 2024-11-21 01:02:10 +00:00
Renovate Bot bc78898f8e Lock file maintenance 2024-11-16 01:04:37 +00:00
Renovate Bot 8ae862e292 Update dependency gradle to v8.11 2024-11-12 01:02:37 +00:00
sora 2b1b82e163 Update app/src/main/AndroidManifest.xml 2024-11-09 12:42:52 +01:00
Renovate Bot edc5df342d Lock file maintenance 2024-11-09 01:05:09 +00:00
Renovate Bot 7d32e267c6 Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.3 2024-11-08 01:01:56 +00:00
sora fa8f4218be Bump version 2024-11-07 07:50:08 +01:00
100 changed files with 9004 additions and 20725 deletions
+30 -8
View File
@@ -23,8 +23,10 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: set up secrets
run: |
echo "${{ secrets.RELEASE_KEYSTORE }}" > keystore.asc
@@ -32,7 +34,6 @@ jobs:
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch keystore.asc > app/keystore.properties
gpg -d --passphrase "${{ secrets.RELEASE_KEYSTORE_PASSWORD }}" --batch key.asc > app/key.jks
- uses: gradle/wrapper-validation-action@v3
- name: create and checkout branch
if: github.event_name == 'pull_request'
@@ -41,17 +42,38 @@ jobs:
run: git checkout -B "$BRANCH"
- name: set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v6
with:
java-version: 17
java-version: 21
distribution: "temurin"
cache: 'gradle'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
- name: Run unit tests
run: ./gradlew testDebugUnitTest
- name: Build APK
run: ./gradlew assembleSignedRelease
run: |
VERSION_CODE=$(git rev-list --count HEAD)
VERSION_BASE=$(git describe --tags --abbrev=0 | sed 's/^v//')
VERSION_DEV=$(git rev-list --count $(git describe --tags --abbrev=0)..HEAD)
if [ $VERSION_DEV -gt 0 ]; then
VERSION_NAME="${VERSION_BASE}.${VERSION_DEV}"
else
VERSION_NAME="${VERSION_BASE}"
fi
./gradlew assembleSignedRelease -PVERSION_CODE=$VERSION_CODE -PVERSION_NAME=$VERSION_NAME
- name: Upload APK
uses: actions/upload-artifact@v7
with:
path: app/build/outputs/apk/signedRelease/app-signedRelease.apk
compression-level: 0
archive: false
- name: Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
+3 -3
View File
@@ -20,8 +20,8 @@ app/build/
app/debug/
app/release/
captures/
.externalNativeBuild
.cxx
local.properties
keystore.properties
key.jks
key.jks
# generated map intermediates; the packed .bmap in assets is what ships
mapdata/*.geojson
+3 -1
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
+50 -32
View File
@@ -1,24 +1,24 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.0.21'
id 'com.mikepenz.aboutlibraries.plugin' version '11.2.3'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.4.10'
id 'org.jetbrains.kotlin.plugin.compose' version '2.4.10'
}
android {
namespace 'net.helcel.beans'
compileSdk 34
compileSdk = 37
defaultConfig {
buildConfigField("String", "APP_NAME", "\"Beans\"")
manifestPlaceholders["APP_NAME"] = "Beans"
applicationId 'net.helcel.beans'
minSdk 28
targetSdk 34
versionCode 1
versionName "1.0"
minSdk = 28
targetSdk = 37
versionName project.hasProperty('VERSION_NAME') ? project.property('VERSION_NAME') : "1.5"
versionCode project.hasProperty('VERSION_CODE') ? project.property('VERSION_CODE').toInteger() : 1
}
signingConfigs {
create("release") {
register("release") {
try {
def keystorePropertiesFile = rootProject.file("app/keystore.properties")
def keystoreProperties = new Properties()
@@ -33,20 +33,18 @@ android {
}
}
}
buildTypes {
debug {
debuggable true
}
release {
minifyEnabled true
shrinkResources false
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
signedRelease {
minifyEnabled true
shrinkResources false
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.getByName("release")
}
@@ -54,17 +52,15 @@ android {
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
encoding 'utf-8'
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17
}
buildFeatures {
viewBinding true
compose true
buildConfig true
}
dependenciesInfo {
@@ -73,21 +69,43 @@ android {
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
composeOptions {
kotlinCompilerExtensionVersion = "2.2.20"
}
androidResources {
// The app ships English strings only; without this the libraries drag
// in some eighty locales of their own.
localeFilters += ["en"]
}
lint {
disable 'UsingMaterialAndMaterial3Libraries'
}
}
aboutLibraries {
exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"]
configPath = "config"
excludeFields = ["generated"]
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.2'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3'
implementation 'androidx.compose.material3:material3:1.4.0'
implementation "androidx.compose.material:material:1.12.0"
implementation 'androidx.compose.material:material-icons-extended:1.7.8'
implementation 'androidx.navigation:navigation-compose:2.10.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:aboutlibraries:11.2.3'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.11.0'
implementation 'androidx.compose.ui:ui'
implementation "androidx.activity:activity-ktx:1.13.0"
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation platform('androidx.compose:compose-bom:2026.08.00')
debugImplementation 'androidx.compose.ui:ui-tooling:1.12.0'
}
tasks.configureEach { task ->
if (task.name.startsWith("merge") && task.name.endsWith("Assets")) {
task.outputs.upToDateWhen { false }
}
}
+4 -35
View File
@@ -1,52 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="1.0a">
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.Beans"
tools:replace="android:allowBackup"
tools:targetApi="31">
<profileable android:shell="true" />
android:label="${APP_NAME}"
android:supportsRtl="true">
<activity
android:name=".activity.MainActivity"
android:name=".activity.MainScreen"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".activity.EditActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name=".activity.StatsActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
<activity
android:name=".activity.SettingsActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
</application>
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 MiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.1 MiB

@@ -1,69 +0,0 @@
package net.helcel.beans.activity
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.addCallback
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.tabs.TabLayoutMediator
import net.helcel.beans.R
import net.helcel.beans.activity.adapter.ViewPagerAdapter
import net.helcel.beans.activity.fragment.EditGroupAddFragment
import net.helcel.beans.activity.fragment.EditPlaceFragment
import net.helcel.beans.countries.World
import net.helcel.beans.databinding.ActivityEditBinding
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Settings
import net.helcel.beans.helper.Theme.createActionBar
class EditActivity : AppCompatActivity() {
private lateinit var _binding: ActivityEditBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityEditBinding.inflate(layoutInflater)
setContentView(_binding.root)
createActionBar(this, getString(R.string.action_edit))
val adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, _binding.pager)
_binding.pager.adapter = adapter
adapter.addFragment(null, EditPlaceFragment(World.WWW, adapter))
TabLayoutMediator(_binding.tab, _binding.pager) { tab, position ->
tab.text = adapter.getLabel(position)
}.attach()
onBackPressedDispatcher.addCallback {
if (!adapter.backPressed()) {
finish()
}
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
if (Settings.isSingleGroup(this)) {
menuInflater.inflate(R.menu.menu_edit, menu)
}
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_color -> {
Data.groups.getUniqueEntry()?.let { group ->
EditGroupAddFragment(group.key, {
(_binding.pager.adapter as ViewPagerAdapter?)?.refreshColors(group.color)
}, {}, false).show(supportFragmentManager, "AddColorDialogFragment")
}
}
else -> finish()
}
return super.onOptionsItemSelected(item)
}
}
@@ -0,0 +1,66 @@
package net.helcel.beans.activity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
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.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.helcel.beans.R
import net.helcel.beans.activity.sub.EditPlaceScreen
import net.helcel.beans.countries.World
@Preview
@Composable
fun EditScreen(onExit:()->Unit = {}) {
SysTheme {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.action_edit)) },
navigationIcon = {
IconButton(onClick = onExit) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
}
)
},
) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.background)
.padding(innerPadding) // ensures content is below the app bar
) {
Column {
Spacer(
modifier = Modifier.height(2.dp).fillMaxWidth()
.background(MaterialTheme.colors.background)
)
EditPlaceScreen(World.WWW, onExit)
}
}
}
}
}
@@ -1,75 +0,0 @@
package net.helcel.beans.activity
import android.content.Intent
import android.graphics.drawable.PictureDrawable
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import com.caverock.androidsvg.RenderOptions
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.databinding.ActivityMainBinding
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Settings
import net.helcel.beans.svg.CSSWrapper
import net.helcel.beans.svg.SVGWrapper
class MainActivity : AppCompatActivity() {
private lateinit var _binding: ActivityMainBinding
private lateinit var psvg: SVGWrapper
private lateinit var css: CSSWrapper
override fun onRestart() {
refreshProjection()
refreshMap()
super.onRestart()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val d = when (item.itemId) {
R.id.action_edit -> EditActivity::class.java
R.id.action_stats -> StatsActivity::class.java
R.id.action_settings -> SettingsActivity::class.java
else -> throw Exception("Non Existent Menu Item")
}
startActivity(Intent(this@MainActivity, d))
return true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityMainBinding.inflate(layoutInflater)
Settings.start(this)
setContentView(_binding.root)
_binding.photoView.minimumScale = 1f
_binding.photoView.maximumScale = 40f
GeoLocImporter.importStates(this)
Data.loadData(this, Int.MIN_VALUE)
refreshProjection()
refreshMap()
}
private fun refreshMap() {
val opt: RenderOptions = RenderOptions.create()
opt.css(css.get())
_binding.photoView.setImageDrawable(PictureDrawable(psvg.get()?.renderToPicture(opt)))
}
fun refreshProjection() {
psvg = SVGWrapper(this)
css = CSSWrapper(this)
}
}
@@ -0,0 +1,203 @@
package net.helcel.beans.activity
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.size
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Percent
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import net.helcel.beans.BuildConfig
import net.helcel.beans.activity.sub.EditPlaceDialog
import net.helcel.beans.activity.sub.MapPickDialog
import net.helcel.beans.activity.sub.applyDirectVisit
import net.helcel.beans.activity.sub.commitVisitDialog
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.countries.GeoLocTree
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Settings
import net.helcel.beans.map.MapAssets
import net.helcel.beans.map.MapStyle
import net.helcel.beans.map.MapView
import net.helcel.beans.map.MapWorld
import net.helcel.beans.map.MapReader
class MainScreen : ComponentActivity() {
private var world by mutableStateOf<MapWorld?>(null)
private var loadToken = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
actionBar?.hide()
Settings.start(this)
Data.loadData(this, Int.MIN_VALUE)
GeoLocImporter.importStates(this)
refreshProjection()
setContent {
SysTheme {
// Both bars: without the navigation inset the system buttons sit on top of
// whatever is at the bottom of a screen, such as the About button.
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colors.primary)
.statusBarsPadding()
.navigationBarsPadding(),
) {
AppNavHost()
}
}
}
}
@Composable
fun AppNavHost() {
val navController = rememberNavController()
NavHost(navController, startDestination = "main") {
composable("main") { MainScreenC(world, navController) }
composable("settings") { SettingsMainScreen { navController.navigate("main") } }
composable("edit") { EditScreen { navController.navigate("main") } }
composable("stats") { StatsScreen { navController.navigate("main") } }
}
}
@Composable
fun MainScreenC(world: MapWorld?, nav: NavHostController){
SysTheme {
Scaffold(
topBar = {
TopAppBar(
title = { Text(BuildConfig.APP_NAME) },
actions = {
IconButton(onClick = { nav.navigate("edit") }) {
Icon(Icons.Default.Edit, contentDescription = "Edit")
}
IconButton(onClick = { nav.navigate("stats") }){
Icon(Icons.Default.Percent, contentDescription = "Stats")
}
IconButton(onClick = { nav.navigate("settings") }) {
Icon(Icons.Default.Settings, contentDescription = "Settings")
}
}
)
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding).fillMaxSize()) {
if (world == null) LoadingMap() else MapScreen(world)
}
}
}
}
@Composable
fun LoadingMap() {
Box(
modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background),
contentAlignment = Alignment.Center,
) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
strokeWidth = 4.dp,
modifier = Modifier.size(50.dp),
)
}
}
@Composable
fun MapScreen(world: MapWorld) {
val ctx = LocalContext.current
val visits by Data.visits.visitsFlow.collectAsState()
val groups by Data.groups.groupsFlow.collectAsState()
val land = MaterialTheme.colors.onBackground.toArgb()
val background = MaterialTheme.colors.background.toArgb()
val style = remember(visits, groups, land, background) {
MapStyle.build(ctx, land, background)
}
val touchRadius = Settings.getTouchRadius(ctx)
var candidates by remember { mutableStateOf<List<GeoLoc>>(emptyList()) }
var showColor by remember { mutableStateOf(false) }
// A place is either applied straight away, or the colour dialog picks
// the group for it, exactly as it does from the edit list.
fun select(loc: GeoLoc) {
if (!applyDirectVisit(ctx, loc)) showColor = true
}
if (candidates.isNotEmpty()) {
MapPickDialog(
candidates = candidates,
onPick = { candidates = emptyList(); select(it) },
onDismiss = { candidates = emptyList() },
)
}
if (showColor) {
EditPlaceDialog(false) { cleared ->
showColor = false
commitVisitDialog(cleared)
}
}
AndroidView(
factory = { MapView(it) },
update = { view ->
view.world = world
view.style = style
view.touchRadiusDp = touchRadius
// Always the tree, even for a single hit: landing on a region is
// just as often a way of reaching the country around it.
view.onPick = { picks ->
val locs = picks.mapNotNull { GeoLocTree.find(it.code) }
if (locs.isNotEmpty()) candidates = locs
}
},
modifier = Modifier.fillMaxSize()
)
}
/** Reloads the map asset, on a worker thread since it is a few megabytes. */
fun refreshProjection() {
val asset = MapAssets.assetFor(this)
val token = ++loadToken
world = null
Thread {
val parsed = assets.open(asset).use { MapReader.read(it) }
runOnUiThread { if (token == loadToken) world = parsed }
}.start()
}
}
@@ -1,63 +0,0 @@
package net.helcel.beans.activity
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import net.helcel.beans.R
import net.helcel.beans.activity.fragment.AboutFragment
import net.helcel.beans.activity.fragment.LicenseFragment
import net.helcel.beans.activity.fragment.SettingsFragment
import net.helcel.beans.helper.Theme.createActionBar
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
createActionBar(this, getString(R.string.action_settings))
// Populate activity with settings fragment
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_view, SettingsFragment(), getString(R.string.action_settings))
.commit()
// Change title in action bar according to current fragment
supportFragmentManager.addFragmentOnAttachListener { _, _ ->
supportActionBar?.title =
supportFragmentManager.findFragmentById(R.id.fragment_view).let {
when (it) {
is LicenseFragment -> getString(R.string.licenses)
is AboutFragment -> getString(R.string.about)
else -> getString(R.string.action_settings)
}
}
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Configure on back pressed
supportFragmentManager.findFragmentById(R.id.fragment_view).let {
when (it) {
is LicenseFragment, is AboutFragment -> {
supportFragmentManager.beginTransaction()
.remove(it)
.commit()
supportFragmentManager.beginTransaction()
.replace(
R.id.fragment_view,
SettingsFragment(),
getString(R.string.action_settings)
)
.commit()
supportActionBar?.title = getString(R.string.action_settings)
}
else -> {
finish()
}
}
}
return super.onOptionsItemSelected(item)
}
}
@@ -0,0 +1,467 @@
package net.helcel.beans.activity
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.Colors
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.RadioButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Slider
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.core.content.edit
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.helcel.beans.R
import net.helcel.beans.activity.sub.AboutScreen
import net.helcel.beans.activity.sub.EditPlaceColorDialog
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.DEFAULT_TOUCH_RADIUS
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.defaultPreferences
import net.helcel.beans.helper.MAX_TOUCH_RADIUS
import net.helcel.beans.helper.Settings
import kotlin.math.roundToInt
@Composable
fun SysTheme(
content: @Composable () -> Unit
) {
val context = LocalContext.current
val prefs = defaultPreferences(context)
val themeKey = prefs.getString(stringResource(R.string.key_theme), stringResource(R.string.system))
val darkTheme = when (themeKey) {
stringResource(R.string.system) -> isSystemInDarkTheme()
stringResource(R.string.light) -> false
stringResource(R.string.dark) -> true
else -> isSystemInDarkTheme()
}
val colorScheme = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if(darkTheme) dynamicDarkColorScheme(LocalContext.current ) else dynamicLightColorScheme(LocalContext.current )
} else {
if(darkTheme) darkColorScheme() else lightColorScheme()
}
val m2colors = Colors(
primary = colorScheme.primary,
primaryVariant = colorScheme.primaryContainer,
secondary = colorScheme.secondary,
background = colorScheme.background,
surface = colorScheme.surface,
onPrimary = colorScheme.onPrimary,
onSecondary = colorScheme.onSecondary,
onBackground = colorScheme.onBackground,
onSurface = colorScheme.onSurface,
secondaryVariant = colorScheme.secondary,
error = colorScheme.error,
onError = colorScheme.onError,
isLight = !darkTheme,
)
// Both, because the app mixes the two: most of it is Material 2, while the
// dividers are Material 3 and would otherwise fall back to the default
// light scheme.
androidx.compose.material3.MaterialTheme(colorScheme = colorScheme) {
MaterialTheme(
colors = m2colors,
content = content
)
}
}
@Preview
@Composable
fun SettingsMainScreen(onExit: ()->Unit = {}) {
val nav: NavHostController = rememberNavController()
SysTheme {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.action_settings)) },
navigationIcon = {
IconButton(onClick = {
if(!nav.popBackStack())
onExit()
}) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
}
)
}
) { innerPadding ->
// One host, inside the scaffold. Building it here and again as a
// default argument left two of them: settings drawn twice, and
// only the inner one ever navigating.
Box(modifier = Modifier.padding(innerPadding)) {
NavHost(nav, startDestination = "settings") {
composable("settings") { SettingsScreen(nav) }
composable("about") { AboutScreen() }
}
}
}
}
}
@Composable
fun SettingsScreen(navController: NavHostController) {
val context = LocalContext.current
val prefs = defaultPreferences(context)
val keyTheme = stringResource(R.string.key_theme)
val defaultTheme = stringResource(R.string.system)
val keyProjection = stringResource(R.string.key_projection)
val keyGroup = stringResource(R.string.key_group)
val keyRegional = stringResource(R.string.key_regional)
val keyCascadeStats = stringResource(R.string.key_cascade_stats)
val keyTouchRadius = stringResource(R.string.key_touch_radius)
val offString = stringResource(R.string.off)
val onString = stringResource(R.string.on)
var theme by remember { mutableStateOf(prefs.getString(keyTheme, defaultTheme)!!) }
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 cascadeStats by remember { mutableStateOf(prefs.getString(keyCascadeStats, offString)!!) }
var touchRadius by remember {
mutableIntStateOf(prefs.getInt(keyTouchRadius, DEFAULT_TOUCH_RADIUS))
}
var showGroupDialog by remember { mutableStateOf(false) }
var showRegionalDialog by remember { mutableStateOf(false) }
var showLoad by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
if (showGroupDialog) {
EditPlaceColorDialog(
deleteMode = true,
onDismiss = {
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
})
}
if (showRegionalDialog) {
Dialog(
content = {
Column(
modifier = Modifier
.background(
MaterialTheme.colors.background,
RoundedCornerShape(corner = CornerSize(16.dp))
)
.padding(16.dp),
) {
Text(
style = MaterialTheme.typography.caption,
text = stringResource(R.string.delete_regions)
)
Button(onClick = {
GeoLocImporter.clearStates()
regional = offString
prefs.edit { putString(keyRegional, offString) }
showRegionalDialog = false
}) {
Text(stringResource(R.string.ok))
}
}
},
onDismissRequest = { showRegionalDialog = false }
)
}
if (showLoad) {
Dialog(
content = {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
strokeWidth = 4.dp,
modifier = Modifier.size(50.dp)
)
},
onDismissRequest = {}
)
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.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),
stringResource(R.string.loximuthal),
),
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) {
Data.selected_group = null
showGroupDialog = true
} else {
groups = onString
prefs.edit { putString(keyGroup, onString) }
}
}
)
HorizontalDivider()
}
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 {
withContext(Dispatchers.IO) {
GeoLocImporter.importStates(context, true)
}
showLoad = false
}
} else {
showRegionalDialog = true
}
}
)
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 {
Text(
stringResource(R.string.pref_touch_radius),
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
Text(
stringResource(R.string.pref_touch_radius_desc),
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onSurface.copy(alpha = 0.6f)
)
Row(verticalAlignment = Alignment.CenterVertically) {
Slider(
value = touchRadius.toFloat(),
onValueChange = { touchRadius = it.roundToInt() },
onValueChangeFinished = { prefs.edit { putInt(keyTouchRadius, touchRadius) } },
valueRange = 0f..MAX_TOUCH_RADIUS.toFloat(),
steps = MAX_TOUCH_RADIUS - 1,
modifier = Modifier.weight(1f)
)
Text(
stringResource(R.string.pref_touch_radius_value, touchRadius),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(start = 12.dp)
)
}
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.about)) {
if (navController.currentDestination?.route != "about")
navController.navigate("about")
}
}
}
}
@Composable
fun MultiPreference(list: Array<String>, selected: String, onSelected: (String) -> Unit) {
Column(Modifier.padding(vertical = 2.dp)) {
list.forEach { value ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(42.dp)
.clickable { onSelected(value) }) {
RadioButton(selected = selected == value, onClick = { onSelected(value) })
Text(
value, modifier = Modifier.padding(start = 12.dp),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onBackground,
)
}
}
}
}
@Composable
fun PreferenceButton(text: String, onClick: () -> Unit) {
Button(onClick = onClick, modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)) {
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
)
}
}
@@ -1,57 +1,219 @@
package net.helcel.beans.activity
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.Text
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
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
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.activity.adapter.StatsListAdapter
import net.helcel.beans.countries.Country
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.GeoLoc.LocType
import net.helcel.beans.databinding.ActivityStatBinding
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.createActionBar
import net.helcel.beans.helper.Theme.getContrastColor
private val MODE_LIST = listOf(LocType.WORLD, LocType.COUNTRY, LocType.STATE)
class StatsActivity : AppCompatActivity() {
private lateinit var _binding: ActivityStatBinding
private var activeMode = LocType.WORLD
@Composable
fun StatsScreen(
onExit: ()-> Unit
) {
val modes = if (Settings.isRegional(LocalContext.current)) MODE_LIST else MODE_LIST.take(2)
var selectedTab by remember { mutableIntStateOf(0) }
var countMode by remember { mutableStateOf(true) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = ActivityStatBinding.inflate(layoutInflater)
setContentView(_binding.root)
createActionBar(this, getString(R.string.action_stat))
_binding.stats.layoutManager =
LinearLayoutManager(this, RecyclerView.VERTICAL, false)
val adapter = StatsListAdapter(_binding.stats, _binding.name)
_binding.groupColor.setOnClickListener { adapter.invertCountMode() }
_binding.stats.adapter = adapter
SysTheme {
Scaffold(
topBar = {
TopAppBar(
title = {
Row(verticalAlignment = Alignment.CenterVertically){
Text(text=stringResource(R.string.action_stat), modifier = Modifier.weight(1f))
Button(onClick = { countMode = !countMode }) {
Text(if (countMode) "Count" else "Area")
}
}
},
navigationIcon = {
IconButton(onClick = onExit) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
},
_binding.pager.adapter = object : FragmentStateAdapter(supportFragmentManager, lifecycle) {
override fun getItemCount(): Int = if (Settings.isRegional(applicationContext)) 3 else 2
override fun createFragment(position: Int): Fragment = Fragment()
}
TabLayoutMediator(_binding.tab, _binding.pager) { tab, position ->
tab.text = MODE_LIST[position].txt
}.attach()
)
},
) { padding ->
Column(Modifier.padding(padding)) {
TabRow(selectedTabIndex = selectedTab) {
modes.forEachIndexed { index, mode ->
Tab(
selected = selectedTab == index,
onClick = { selectedTab = index },
text = { Text(mode.txt) }
)
}
}
_binding.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
activeMode = MODE_LIST[position]
adapter.refreshMode(activeMode)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.End
) {
}
val activeMode = modes.getOrNull(selectedTab) ?: LocType.WORLD
StatsList(activeMode, countMode)
}
})
}
}
}
@Composable
fun StatsList(activeMode: LocType, countMode: Boolean) {
val groups = remember { Data.groups.groupsFlow }
val unCat = stringResource(R.string.uncategorized)
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(groups.value + listOf(Groups.Group(AUTO_GROUP, unCat))) { group ->
StatsRow(group, activeMode, countMode)
}
}
}
@Composable
fun StatsRow(group: Groups.Group, activeMode: LocType, countMode: Boolean) {
val context = LocalContext.current
val isCascadeStats = remember { Settings.isCascadeStats(context) }
val visits by Data.visits.visitsFlow.collectAsState()
val continents = remember { World.WWW.children.toList() }
val countries = remember {
World.WWW.children.flatMap {
if (it is Country) listOf(it) else it.children
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
finish()
return super.onOptionsItemSelected(item)
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<GeoLoc>()
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<GeoLoc>()
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 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)
Row(
modifier = Modifier
.fillMaxWidth()
.background(Color(backgroundColor))
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text=group.name,
modifier= Modifier.weight(1f),
color = Color(textColor)
)
Text(text=displayValue,
color = Color(textColor)
)
}
}
@@ -1,193 +0,0 @@
package net.helcel.beans.activity.adapter
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.checkbox.MaterialCheckBox
import net.helcel.beans.activity.fragment.EditPlaceColorFragment
import net.helcel.beans.activity.fragment.EditPlaceFragment
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.databinding.ItemListGeolocBinding
import net.helcel.beans.helper.*
import net.helcel.beans.helper.Theme.colorWrapper
class GeolocListAdapter(
private val ctx: EditPlaceFragment, private val l: GeoLoc, private val pager: ViewPagerAdapter,
private val parentHolder: FoldingListViewHolder?
) : RecyclerView.Adapter<GeolocListAdapter.FoldingListViewHolder>() {
private val sortedList = l.children.toList().sortedBy { it.fullName }
private val holders: MutableSet<FoldingListViewHolder> = mutableSetOf()
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): FoldingListViewHolder {
val binding = ItemListGeolocBinding.inflate(
LayoutInflater.from(viewGroup.context),
viewGroup,
false
)
val holder = FoldingListViewHolder(ctx.requireActivity(), binding, parentHolder, l)
holders.add(holder)
return holder
}
override fun onBindViewHolder(holder: FoldingListViewHolder, position: Int) {
val el = sortedList[position]
holder.bind(el)
holder.addListeners(el) {
if (el.children.isNotEmpty())
pager.addFragment(ctx, EditPlaceFragment(el, pager, holder))
true
}
}
override fun getItemCount(): Int {
return l.children.size
}
fun refreshColors(colorDrawable: ColorDrawable) {
holders.forEach { it.refreshColor(colorDrawable) }
}
class FoldingListViewHolder(
private val ctx: FragmentActivity,
private val _binding: ItemListGeolocBinding,
private val _parentHolder: FoldingListViewHolder? = null,
private val _parentGeoLoc: GeoLoc,
) : RecyclerView.ViewHolder(_binding.root), DialogCloser {
private lateinit var el: GeoLoc
private fun bindGroup(el: GeoLoc) {
refreshCount(el)
_binding.textView.setTypeface(null, Typeface.BOLD)
_binding.textView.backgroundTintList = ColorStateList.valueOf(
colorWrapper(
ctx,
android.R.attr.panelColorBackground
).color
).withAlpha(64)
}
fun bind(el: GeoLoc) {
this.el = el
_binding.textView.text = el.fullName
_binding.textView.backgroundTintList =
ColorStateList.valueOf(colorWrapper(ctx, android.R.attr.colorBackground).color)
if (el.children.isNotEmpty())
bindGroup(el)
refreshCheck(el)
}
fun refreshColor(colorDrawable: ColorDrawable) {
if (Data.visits.getVisited(el) !in listOf(NO_GROUP, AUTO_GROUP)) {
_binding.checkBox.buttonTintList =
ColorStateList.valueOf(colorDrawable.color)
refreshCheck(el)
}
}
fun addListeners(el: GeoLoc, expandLambda: () -> Boolean) {
if (el.children.isNotEmpty()) {
_binding.textView.setOnClickListener { expandLambda() }
}
_binding.checkBox.setOnClickListener {
Data.selected_geoloc = el
if (Data.groups.size() == 1 && Settings.isSingleGroup(ctx)) {
if (_binding.checkBox.isChecked) {
// If one has just checked the box (assign unique group)
Data.selected_group = Data.groups.getUniqueEntry()
onDialogDismiss(false)
} else {
// If one has just unchecked the box (unassign unique group)
Data.selected_group = null
onDialogDismiss(true)
}
} else {
Data.selected_group = null
EditPlaceColorFragment(this).show(
ctx.supportFragmentManager,
"AddColorDialogFragment"
)
}
_parentHolder?.refresh(_parentGeoLoc)
}
}
override fun onDialogDismiss(clear: Boolean) {
if (clear) {
Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
Data.saveData()
if (_parentGeoLoc.children.all { Data.visits.getVisited(it) == NO_GROUP }) {
Data.clearing_geoloc = _parentGeoLoc
}
}
if (Data.selected_group != null && Data.selected_geoloc != null) {
Data.visits.setVisited(Data.selected_geoloc, Data.selected_group?.key ?: NO_GROUP)
Data.saveData()
}
Data.selected_geoloc?.let { refreshCheck(it) }
Data.selected_geoloc = null
Data.selected_group = null
_parentHolder?.refresh(_parentGeoLoc)
}
private fun refreshCheck(geoLoc: GeoLoc) {
_binding.checkBox.checkedState =
if (Data.visits.getVisited(geoLoc) !in listOf(NO_GROUP, AUTO_GROUP)) {
MaterialCheckBox.STATE_CHECKED
} else if (geoLoc.children.isNotEmpty() &&
geoLoc.children.all {
Data.visits.getVisited(it) !in listOf(NO_GROUP, AUTO_GROUP)
}
) {
Data.visits.setVisited(geoLoc, AUTO_GROUP)
MaterialCheckBox.STATE_CHECKED
} else if (geoLoc.children.isEmpty() && Data.visits.getVisited(geoLoc) == AUTO_GROUP) {
MaterialCheckBox.STATE_CHECKED
} else if (geoLoc.children.any { Data.visits.getVisited(it) != NO_GROUP }) {
Data.visits.setVisited(geoLoc, AUTO_GROUP)
MaterialCheckBox.STATE_INDETERMINATE
} else {
Data.visits.setVisited(geoLoc, NO_GROUP)
if (Data.clearing_geoloc == geoLoc) {
Data.clearing_geoloc = null
}
MaterialCheckBox.STATE_UNCHECKED
}
Data.saveData()
var col = Data.groups.getGroupFromKey(Data.visits.getVisited(geoLoc)).color
if (Data.visits.getVisited(geoLoc) == AUTO_GROUP) {
col = colorWrapper(ctx, android.R.attr.colorPrimary)
} else if (col.color == Color.TRANSPARENT) {
col = colorWrapper(ctx, android.R.attr.panelColorBackground)
col.alpha = 64
}
_binding.checkBox.buttonTintList = ColorStateList.valueOf(col.color)
}
private fun refreshCount(geoLoc: GeoLoc) {
val numerator =
geoLoc.children.map { Data.visits.getVisited(it) != NO_GROUP }.count { it }
val denominator = geoLoc.children.size
_binding.count.text = Settings.getStats(ctx, numerator, denominator)
}
private fun refresh(geoLoc: GeoLoc) {
// Refresh
refreshCheck(geoLoc)
refreshCount(geoLoc)
// Recursively refresh parent
_parentHolder?.refresh(_parentGeoLoc)
}
}
}
@@ -1,78 +0,0 @@
package net.helcel.beans.activity.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.RecyclerView
import net.helcel.beans.activity.fragment.EditGroupAddFragment
import net.helcel.beans.databinding.ItemListGroupBinding
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Groups
import net.helcel.beans.helper.Theme.getContrastColor
class GroupListAdapter(
private val activity: FragmentActivity,
private val selectDialog: DialogFragment,
private val delete: Boolean = false
) : RecyclerView.Adapter<GroupListAdapter.GroupViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupViewHolder {
val binding =
ItemListGroupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return GroupViewHolder(binding, activity, selectDialog)
}
override fun onBindViewHolder(holder: GroupViewHolder, pos: Int) {
holder.bind(Data.groups.getGroupFromPos(pos))
}
override fun getItemCount(): Int {
return Data.groups.size()
}
inner class GroupViewHolder(
private val _binding: ItemListGroupBinding,
private val activity: FragmentActivity,
private val selectDialog: DialogFragment
) : RecyclerView.ViewHolder(_binding.root) {
private lateinit var dialogFragment: EditGroupAddFragment
fun bind(entry: Pair<Int, Groups.Group>) {
_binding.groupColor.text = entry.second.name
dialogFragment = EditGroupAddFragment(entry.first, {
val newEntry = Data.groups.getGroupFromKey(entry.first)
_binding.groupColor.text = newEntry.name
val newEntryColor = newEntry.color.color
val contrastNewEntryColor =
getContrastColor(newEntryColor)
_binding.groupColor.setBackgroundColor(newEntryColor)
_binding.groupColor.setTextColor(contrastNewEntryColor)
_binding.name.setTextColor(contrastNewEntryColor)
_binding.name.text = "0"
}, {
notifyItemRemoved(it)
})
val entryColor = entry.second.color.color
val contrastEntryColor = getContrastColor(entryColor)
_binding.groupColor.setBackgroundColor(entryColor)
_binding.groupColor.setTextColor(contrastEntryColor)
_binding.name.setTextColor(contrastEntryColor)
_binding.name.text = Data.visits.countVisited(entry.first).toString()
_binding.groupColor.setOnClickListener {
Data.selected_group = entry.second
selectDialog.dismiss()
}
if (!delete) {
_binding.groupColor.setOnLongClickListener {
dialogFragment.show(
activity.supportFragmentManager,
"AddColorDialogFragment"
)
true
}
}
}
}
}
@@ -1,152 +0,0 @@
package net.helcel.beans.activity.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.textview.MaterialTextView
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.GeoLoc.LocType
import net.helcel.beans.countries.World
import net.helcel.beans.databinding.ItemListGroupBinding
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Groups
import net.helcel.beans.helper.Settings
import net.helcel.beans.helper.Theme.getContrastColor
class StatsListAdapter(private val stats: RecyclerView, private val total: MaterialTextView) :
RecyclerView.Adapter<StatsListAdapter.StatsViewHolder>() {
private val unit = "km²"
private var locMode = LocType.WORLD
private lateinit var ctx: Context
private var countMode: Boolean = true
private var initialSum: Int = 0
private val wwwTotal: List<GeoLoc> = World.WWW.children.toList()
private val countryTotal: List<GeoLoc> = World.WWW.children.flatMap { it.children }
private val stateTotal: List<GeoLoc> =
World.WWW.children.flatMap { it.children.flatMap { itt -> itt.children } }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StatsViewHolder {
ctx = parent.context
val binding =
ItemListGroupBinding.inflate(LayoutInflater.from(ctx), parent, false)
return StatsViewHolder(binding)
}
override fun onBindViewHolder(holder: StatsViewHolder, pos: Int) {
initialSum += if (pos == itemCount - 1) {
holder.bind(
Pair(
AUTO_GROUP,
Groups.Group(AUTO_GROUP, ctx.getString(R.string.uncategorized))
)
)
} else {
holder.bind(Data.groups.getGroupFromPos(pos))
}
val unitNow = if (!countMode) unit else ""
total.text = Settings.getStats(ctx, initialSum, getTotal(), unitNow)
}
override fun getItemCount(): Int {
return Data.groups.size() + 1
}
private fun getTotal(): Int {
return if (countMode) {
when (locMode) {
LocType.WORLD -> wwwTotal.size
LocType.COUNTRY -> countryTotal.size
LocType.STATE -> stateTotal.size
else -> 0
}
} else {
when (locMode) {
LocType.WORLD -> wwwTotal.sumOf { it.area }
LocType.COUNTRY -> countryTotal.sumOf { it.area }
LocType.STATE -> stateTotal.sumOf { it.area }
else -> 0
}
}
}
fun refreshMode(mode: LocType) {
val sum = (0 until itemCount).map {
val viewHolder = stats.findViewHolderForAdapterPosition(it) as? StatsViewHolder
viewHolder?.refresh(mode)
}.reduce { acc, i -> acc?.plus((i ?: 0)) }
val unitNow = if (!countMode) unit else ""
total.text = Settings.getStats(ctx, sum, getTotal(), unitNow)
}
fun invertCountMode() {
countMode = !countMode
refreshMode(locMode)
}
inner class StatsViewHolder(
private val _binding: ItemListGroupBinding
) : RecyclerView.ViewHolder(_binding.root) {
private lateinit var data: Pair<Int, Groups.Group>
private lateinit var wwwCount: List<GeoLoc>
private lateinit var countryCount: List<GeoLoc>
private lateinit var stateCount: List<GeoLoc>
fun bind(entry: Pair<Int, Groups.Group>): Int {
data = entry
_binding.groupColor.text = entry.second.name
val entryColor = data.second.color.color
val contrastEntryColor = getContrastColor(entryColor)
_binding.groupColor.setBackgroundColor(entryColor)
_binding.groupColor.setTextColor(contrastEntryColor)
_binding.name.setTextColor(contrastEntryColor)
_binding.groupColor.setOnClickListener { invertCountMode() }
compute()
return refresh(locMode)
}
private fun compute() {
val visited = Data.visits.getVisitedByValue(data.first)
wwwCount = World.WWW.children.filter { it.code in visited }
countryCount =
World.WWW.children.map { it.children.filter { itt -> itt.code in visited } }
.flatten()
stateCount =
World.WWW.children.map { it.children.map { itt -> itt.children.filter { ittt -> ittt.code in visited } } }
.flatten().flatten()
}
fun refresh(mode: LocType): Int {
locMode = mode
return if (countMode) {
val count = when (locMode) {
LocType.WORLD -> wwwCount.size
LocType.COUNTRY -> countryCount.size
LocType.STATE -> stateCount.size
else -> -1
}
_binding.name.text = count.toString()
count
} else {
val area = when (locMode) {
LocType.WORLD -> wwwCount.sumOf { it.area }
LocType.COUNTRY -> countryCount.sumOf { it.area }
LocType.STATE -> stateCount.sumOf { it.area }
else -> -1
}
_binding.name.text = ctx.getString(R.string.number_with_unit, area, unit)
area
}
}
}
}
@@ -1,61 +0,0 @@
package net.helcel.beans.activity.adapter
import android.graphics.drawable.ColorDrawable
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.ViewPager2
import net.helcel.beans.activity.fragment.EditPlaceFragment
import kotlin.math.max
class ViewPagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle,
private val viewPager: ViewPager2
) :
FragmentStateAdapter(fragmentManager, lifecycle) {
private val fragmentList: MutableList<EditPlaceFragment> = ArrayList()
fun addFragment(src: EditPlaceFragment?, target: EditPlaceFragment) {
val idx = fragmentList.indexOf(src)
viewPager.currentItem = max(0, idx)
if (src != null && idx >= 0) {
fragmentList.subList(idx + 1, fragmentList.size).clear()
}
fragmentList.add(target)
notifyItemRangeChanged(max(0, idx), fragmentList.size)
viewPager.currentItem = fragmentList.size - 1
}
override fun getItemCount(): Int {
return fragmentList.size
}
fun backPressed(): Boolean {
if (viewPager.currentItem == 0) {
return false
}
val target = viewPager.currentItem
while (fragmentList.size > target) {
fragmentList.removeLast()
notifyItemRemoved(fragmentList.size)
}
return true
}
fun getLabel(pos: Int): String {
return fragmentList[pos].loc.fullName
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
fun refreshColors(colorDrawable: ColorDrawable) {
fragmentList.forEach{ it.refreshColors(colorDrawable)}
}
}
@@ -1,21 +0,0 @@
package net.helcel.beans.activity.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import net.helcel.beans.databinding.FragmentAboutBinding
class AboutFragment : Fragment() {
private lateinit var _binding: FragmentAboutBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAboutBinding.inflate(inflater, container, false)
return _binding.root
}
}
@@ -1,155 +0,0 @@
package net.helcel.beans.activity.fragment
import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import androidx.core.graphics.blue
import androidx.core.graphics.green
import androidx.core.graphics.red
import androidx.fragment.app.DialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.slider.Slider
import com.google.android.material.textfield.TextInputEditText
import net.helcel.beans.R
import net.helcel.beans.databinding.FragmentEditGroupsAddBinding
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Groups
import net.helcel.beans.helper.Theme.colorToHex6
class EditGroupAddFragment(
private val key: Int = 0,
val onAddCb: (Int) -> Unit,
val onDelCb: (Int) -> Unit,
private val deleteEnabled: Boolean = true
) : DialogFragment() {
private lateinit var _binding: FragmentEditGroupsAddBinding
private val grp = Data.groups.getGroupFromKey(key)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = MaterialAlertDialogBuilder(requireContext())
_binding = FragmentEditGroupsAddBinding.inflate(layoutInflater)
setupSlider(_binding.colorR, grp.color.color.red / 255F)
setupSlider(_binding.colorG, grp.color.color.green / 255F)
setupSlider(_binding.colorB, grp.color.color.blue / 255F)
setupText(_binding.groupColor, grp)
_binding.colorView.background = ColorDrawable(grp.color.color)
if (key == 0 || !deleteEnabled) {
_binding.btnDelete.visibility = View.INVISIBLE
_binding.btnDelete.isEnabled = false
}
_binding.btnDelete.setOnClickListener {
MaterialAlertDialogBuilder(requireActivity())
.setMessage(R.string.delete_group)
.setPositiveButton(android.R.string.ok) { _, _ ->
val pos = Data.groups.findGroupPos(key)
// Remove all countries belonging to that group
Data.visits.deleteVisited(key)
// Delete the group
Data.groups.deleteGroup(key)
Data.saveData()
onDelCb(pos)
dialog?.dismiss()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
}
_binding.btnOk.setOnClickListener {
val name = _binding.groupName.text.toString()
val color = _binding.groupColor.text.toString()
val key = (if (key != 0) key else Data.groups.genKey())
Data.groups.setGroup(key, name, ColorDrawable(Color.parseColor("#$color")))
Data.saveData()
onAddCb(key)
dialog?.dismiss()
}
_binding.btnCancel.setOnClickListener {
dialog?.cancel()
}
_binding.groupName.setText(grp.name)
builder.setView(_binding.root)
return builder.create()
}
private fun setupText(s: TextInputEditText, grp: Groups.Group?) {
s.setText(colorToHex6(ColorDrawable(grp?.color?.color ?: 0)).substring(1))
s.addTextChangedListener(
EditTextListener(
_binding.colorR,
_binding.colorG,
_binding.colorB,
_binding.groupColor,
_binding.colorView
)
)
}
private fun setupSlider(s: Slider, v: Float) {
s.valueFrom = 0F
s.valueTo = 1F
s.value = v
s.addOnChangeListener(
SliderOnChangeListener(
_binding.colorR,
_binding.colorG,
_binding.colorB,
_binding.groupColor,
_binding.colorView
)
)
}
}
private class EditTextListener(
private val colorEditR: Slider,
private val colorEditG: Slider,
private val colorEditB: Slider,
private val colorEditText: TextInputEditText,
private val colorView: View
) : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {
val col: Color
try {
col = Color.valueOf(Color.parseColor("#${colorEditText.text}"))
} catch (e: Exception) {
return
}
colorEditR.value = col.red()
colorEditG.value = col.green()
colorEditB.value = col.blue()
colorView.background = ColorDrawable(col.toArgb())
}
}
private class SliderOnChangeListener(
private val colorEditR: Slider,
private val colorEditG: Slider,
private val colorEditB: Slider,
private val colorEditText: TextInputEditText,
private val colorView: View
) : Slider.OnChangeListener {
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
val rgb =
ColorDrawable(Color.argb(1F, colorEditR.value, colorEditG.value, colorEditB.value))
colorEditText.setText(colorToHex6(rgb).substring(1))
colorView.background = rgb
}
}
@@ -1,60 +0,0 @@
package net.helcel.beans.activity.fragment
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.View
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import net.helcel.beans.R
import net.helcel.beans.activity.adapter.GroupListAdapter
import net.helcel.beans.databinding.FragmentEditPlacesColorsBinding
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.DialogCloser
class EditPlaceColorFragment(private val parent: DialogCloser, private val delete: Boolean = false) :
DialogFragment() {
private lateinit var _binding: FragmentEditPlacesColorsBinding
private lateinit var listAdapt: GroupListAdapter
private var clear: Boolean = false
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val ctx = requireContext()
val builder = MaterialAlertDialogBuilder(ctx)
_binding = FragmentEditPlacesColorsBinding.inflate(layoutInflater)
_binding.btnAdd.setOnClickListener {
EditGroupAddFragment(0, {
listAdapt.notifyItemInserted(Data.groups.findGroupPos(it))
}, {}).show(requireActivity().supportFragmentManager, "AddColorDialogFragment")
}
_binding.btnClear.setOnClickListener {
clear = true
dialog?.dismiss()
}
val dialog = builder.setView(_binding.root).create()
listAdapt = GroupListAdapter(requireActivity(), this, delete)
_binding.groupsColor.layoutManager =
LinearLayoutManager(ctx, RecyclerView.VERTICAL, false)
_binding.groupsColor.adapter = listAdapt
if (delete) {
_binding.btnAdd.visibility = View.GONE
_binding.btnClear.text = ctx.getString(R.string.cancel)
_binding.warningText.text = ctx.getString(R.string.select_group)
} else {
_binding.warningText.text = ctx.getString(R.string.edit_group)
}
return dialog
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
parent.onDialogDismiss(clear)
}
}
@@ -1,38 +0,0 @@
package net.helcel.beans.activity.fragment
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import net.helcel.beans.activity.adapter.GeolocListAdapter
import net.helcel.beans.activity.adapter.GeolocListAdapter.FoldingListViewHolder
import net.helcel.beans.activity.adapter.ViewPagerAdapter
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.databinding.FragmentEditPlacesBinding
class EditPlaceFragment(val loc: GeoLoc, private val pager: ViewPagerAdapter, private val holder: FoldingListViewHolder? = null) : Fragment() {
private lateinit var _binding: FragmentEditPlacesBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentEditPlacesBinding.inflate(inflater, container, false)
_binding.list.setItemViewCacheSize(5)
_binding.list.setHasFixedSize(true)
_binding.list.layoutManager =
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
_binding.list.adapter = GeolocListAdapter(this, loc, pager, holder)
return _binding.root
}
fun refreshColors(colorDrawable: ColorDrawable) {
(_binding.list.adapter as GeolocListAdapter?)?.refreshColors(colorDrawable)
}
}
@@ -1,33 +0,0 @@
package net.helcel.beans.activity.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.mikepenz.aboutlibraries.LibsBuilder
import net.helcel.beans.R
import net.helcel.beans.databinding.FragmentLicenseBinding
class LicenseFragment : Fragment() {
private lateinit var _binding: FragmentLicenseBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentLicenseBinding.inflate(inflater, container, false)
val librariesFragment = LibsBuilder()
.withLicenseShown(true)
.supportFragment()
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.license_fragment_view, librariesFragment)
.commit()
return _binding.root
}
}
@@ -1,142 +0,0 @@
package net.helcel.beans.activity.fragment
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.DialogCloser
import net.helcel.beans.helper.Settings
class SettingsFragment : PreferenceFragmentCompat(), DialogCloser {
private var savedInstanceState: Bundle? = null
private var rootKey: String? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
this.savedInstanceState = savedInstanceState
this.rootKey = rootKey
setPreferencesFromResource(R.xml.fragment_settings, rootKey)
val ctx = requireContext()
// Select Light/Dark/System Mode
findPreference<Preference>(getString(R.string.key_theme))?.setOnPreferenceChangeListener { _, key ->
setTheme(ctx, key as String)
}
// Select map projection
findPreference<Preference>(getString(R.string.key_projection))?.setOnPreferenceChangeListener { _, key ->
Settings.refreshProjection()
}
// Toggle groups
findPreference<Preference>(getString(R.string.key_group))?.setOnPreferenceChangeListener { _, key ->
if (key as String == ctx.getString(R.string.off)) {
val fragment = EditPlaceColorFragment(this, true)
fragment.show(
this.parentFragmentManager,
"AddColorDialogFragment"
)
false
} else {
true
}
}
// Toggle regional geolocs
findPreference<Preference>(getString(R.string.key_regional))?.setOnPreferenceChangeListener { _, key ->
when (key as String) {
ctx.getString(R.string.off) -> {
MaterialAlertDialogBuilder(requireActivity())
.setMessage(R.string.delete_regions)
.setPositiveButton(android.R.string.ok) { _, _ ->
GeoLocImporter.clearStates()
PreferenceManager.getDefaultSharedPreferences(ctx).edit().putString(
ctx.getString(R.string.key_regional),
ctx.getString(R.string.off)
).apply()
refreshPreferences()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> }
.show()
false
}
ctx.getString(R.string.on) -> {
GeoLocImporter.importStates(ctx, true)
true
}
else -> false
}
}
// Open license fragment
findPreference<Preference>(getString(R.string.licenses))?.setOnPreferenceClickListener {
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_view, LicenseFragment(), getString(R.string.licenses))
.commit()
true
}
// Open about fragment
findPreference<Preference>(getString(R.string.about))?.setOnPreferenceClickListener {
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.fragment_view, AboutFragment(), getString(R.string.about))
.commit()
true
}
}
companion object {
fun setTheme(ctx: Context, key: String?): Boolean {
AppCompatDelegate.setDefaultNightMode(
when (key) {
ctx.getString(R.string.system) -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
ctx.getString(R.string.light) -> AppCompatDelegate.MODE_NIGHT_NO
ctx.getString(R.string.dark) -> AppCompatDelegate.MODE_NIGHT_YES
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
}
)
return true
}
}
override fun onDialogDismiss(clear: Boolean) {
// When turning groups off, select one group to keep and reassign everything
Data.selected_group?.let { selectedGroup ->
// Reassign all visited that are not to selectedGroup to selectedGroup
Data.visits.reassignAllVisitedToGroup(selectedGroup.key)
// Delete all groups that are not selectedGroup
Data.groups.deleteAllExcept(selectedGroup.key)
// Save and clear global variables
Data.saveData()
Data.selected_geoloc = null
Data.selected_group = null
// Actually change preference
val ctx = requireContext()
val sp = PreferenceManager.getDefaultSharedPreferences(ctx)
sp.edit().putString(ctx.getString(R.string.key_group), ctx.getString(R.string.off))
.apply()
// Refresh entire preference fragment to reflect changes
refreshPreferences()
}
}
private fun refreshPreferences() {
preferenceScreen.removeAll()
onCreatePreferences(savedInstanceState, rootKey)
}
}
@@ -0,0 +1,78 @@
package net.helcel.beans.activity.sub
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalUriHandler
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import net.helcel.beans.R
import net.helcel.beans.BuildConfig
@Preview
@Composable
fun AboutScreen(
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxSize()
.padding(top = 20.dp).background(MaterialTheme.colors.background),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = "Logo",
modifier = Modifier
.size(300.dp)
)
Text(
text = BuildConfig.APP_NAME,
fontSize = 30.sp,
color = MaterialTheme.colors.onBackground,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp)
)
Text(
text = BuildConfig.VERSION_NAME,
fontSize = 25.sp,
color = MaterialTheme.colors.onBackground,
textAlign = TextAlign.Center,
modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp)
)
Text(
text = stringResource(R.string.beans_is_foss),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(vertical = 15.dp, horizontal = 10.dp)
)
val uriHandler = LocalUriHandler.current
val uri = stringResource(R.string.beans_repo_uri)
Text(
text = stringResource(id = R.string.beans_repo,uri),
textAlign = TextAlign.Center,
color = MaterialTheme.colors.onBackground,
modifier = Modifier
.clickable {
uriHandler.openUri(uri)
}
.padding(vertical = 15.dp, horizontal = 10.dp)
)
}
}
@@ -0,0 +1,222 @@
package net.helcel.beans.activity.sub
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Slider
import androidx.compose.material.SliderDefaults
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Theme.colorToHex6
import androidx.core.graphics.drawable.toDrawable
import androidx.core.graphics.toColorInt
import net.helcel.beans.R
@Preview
@Composable
fun EditGroupPreview(){
EditGroupDialog(0,true,{},{},{})
}
@Composable
fun EditGroupDialog(
key: Int = 0,
deleteEnabled: Boolean = true,
onAddCb: (Int) -> Unit,
onDelCb: (Int) -> Unit,
onDismiss: () -> Unit
) {
val group by remember { mutableStateOf(Data.groups.getGroupFromKey(key)) }
var name by remember { mutableStateOf(group.name) }
var colorHex by remember {
mutableStateOf(colorToHex6(group.color.color.toDrawable()).substring(1))
}
// Convert hex to Color safely
var color = remember {try {
Color("#$colorHex".toColorInt())
} catch (_: Exception) {
Color.Gray
}}
var r by remember { mutableIntStateOf((color.red *255).toInt()) }
var g by remember { mutableIntStateOf((color.green*255).toInt()) }
var b by remember { mutableIntStateOf((color.blue*255).toInt()) }
fun updateHexFromSliders() {
val newColor = Color(r, g, b)
colorHex = colorToHex6(newColor.toArgb().toDrawable()).substring(1)
color = newColor
}
Dialog(
onDismissRequest = onDismiss,
content = {
Column(
modifier = Modifier
.background(
MaterialTheme.colors.background,
RoundedCornerShape(corner = CornerSize(16.dp))
)
.padding(16.dp),
) {
Text(
color = MaterialTheme.colors.onBackground,
style = MaterialTheme.typography.h6,
text = if (key == 0) stringResource(R.string.action_add)
else stringResource(R.string.action_edit),
)
Spacer(modifier = Modifier.height(16.dp))
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Group name
OutlinedTextField(
value = name,
onValueChange = { it: String -> name = it },
modifier = Modifier.fillMaxWidth(),
placeholder = { Text(stringResource(R.string.name)) },
colors = OutlinedTextFieldDefaults.colors(
unfocusedTextColor = MaterialTheme.colors.onBackground,
focusedTextColor = MaterialTheme.colors.onBackground,
),
)
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalAlignment = Alignment.CenterVertically
) {
// Color preview
Box(
modifier = Modifier
.size(96.dp, (96).dp)
.clip(RoundedCornerShape(8.dp))
.background(color),
propagateMinConstraints = true,
content = {}
)
Column {
ColorSlider(
r.toFloat(),
{ r = it.toInt(); updateHexFromSliders() },
Color(255, 0, 0)
)
ColorSlider(
g.toFloat(),
{ g = it.toInt(); updateHexFromSliders() },
Color(0, 255, 0)
)
ColorSlider(
b.toFloat(),
{ b = it.toInt(); updateHexFromSliders() },
Color(0, 0, 255)
)
}
}
// Hex input
OutlinedTextField(
value = colorHex,
onValueChange = { n:String->
colorHex = n.filter { it.isLetterOrDigit() }
},
label = { Text(text="Color (hex)", color=MaterialTheme.colors.onBackground) },
singleLine = true,
textStyle = TextStyle(
fontSize = 12.sp
),
modifier = Modifier.fillMaxWidth(),
colors = OutlinedTextFieldDefaults.colors(
unfocusedTextColor =MaterialTheme.colors.onBackground,
focusedTextColor = MaterialTheme.colors.onBackground,
),
)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End
) {
Button(onClick = {
val newKey = if (key != 0) key else Data.groups.genKey()
Data.groups.setGroup(
newKey,
name,
"#$colorHex".toColorInt().toDrawable()
)
Data.saveData()
onAddCb(newKey)
onDismiss()
}, ) {
Text("OK")
}
if (key != 0 && deleteEnabled) {
TextButton(onClick = {
val pos = Data.groups.findGroupPos(key)
Data.visits.deleteVisited(key)
Data.groups.deleteGroup(key)
Data.saveData()
onDelCb(pos)
onDismiss()
}) {
Text("Delete")
}
}
TextButton(onClick = { onDismiss() }) {
Text("Cancel")
}
}
}
},
)
}
@Composable
fun ColorSlider(v: Float, onChange:(Float)->Unit, c:Color ){
Slider(
value = v,
onValueChange = onChange,
valueRange = 0f..255f,
steps = 255,
modifier = Modifier.height(32.dp),
colors = SliderDefaults.colors(
thumbColor = c,
activeTickColor = c,
inactiveTickColor = MaterialTheme.colors.onBackground,
)
)
}
@@ -0,0 +1,193 @@
package net.helcel.beans.activity.sub
import androidx.compose.foundation.background
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
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
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import net.helcel.beans.R
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.Groups
import androidx.core.graphics.drawable.toDrawable
import net.helcel.beans.activity.SysTheme
@Composable
fun EditPlaceDialog(delete: Boolean, onDialogDismiss: (Boolean)->Unit){
SysTheme {
var showEditGroupDialog by remember { mutableStateOf(false) }
var showEditPlaceColorDialog by remember { mutableStateOf(true) }
var showSelectedKey by remember { mutableIntStateOf(-1) }
var showDelete by remember { mutableStateOf(false) }
if (showEditGroupDialog)
EditGroupDialog(
key = showSelectedKey,
deleteEnabled = showDelete,
onAddCb = { },
onDelCb = {
},
onDismiss = {
showEditGroupDialog = false
},
)
if (showEditPlaceColorDialog)
EditPlaceColorDialog(
delete,
onAdd = {
showSelectedKey = it
showDelete = false
showEditGroupDialog = true
},
onDelete = {
showSelectedKey = it
showDelete = true
showEditGroupDialog = true
},
onClear = {
showEditPlaceColorDialog = false
onDialogDismiss(true)
},
onDismiss = {
showEditPlaceColorDialog = false
onDialogDismiss(false)
}
)
}
}
@Preview
@Composable
fun GroupListPreview() {
Data.groups = Groups(0, HashMap())
Data.groups.setGroup(0, "Testing", Color.Red.toArgb().toDrawable())
Data.groups.setGroup(1, "Testing", Color.Blue.toArgb().toDrawable())
EditPlaceColorDialog(false,{},{},{},{})
}
@Composable
fun EditPlaceColorDialog(
deleteMode: Boolean = false,
onAdd: (Int) -> Unit = {},
onDelete: (Int) -> Unit= {},
onClear: () -> Unit= {},
onDismiss: () -> Unit= {},
) {
val groups by Data.groups.groupsFlow.collectAsState()
Dialog(
onDismissRequest = onDismiss,
content = {
Column(
modifier = Modifier
.background(
MaterialTheme.colors.background,
RoundedCornerShape(corner = CornerSize(16.dp)))
.padding(16.dp)
,
) {
Text(
style = MaterialTheme.typography.h6,
color=MaterialTheme.colors.onBackground,
text = if (deleteMode) stringResource(R.string.select_group)
else stringResource(R.string.edit_group)
)
Text(
style = MaterialTheme.typography.caption,
color=MaterialTheme.colors.onBackground,
text = if (deleteMode) stringResource(R.string.select_group_sub)
else stringResource(R.string.edit_group_sub)
)
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 300.dp) // cap dialog growth
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
//.weight(1f)
) {
items(groups, key = { it.key }) { group ->
Row(
modifier = Modifier
.fillMaxWidth()
.combinedClickable(
onClick = { Data.selected_group = group; onDismiss() },
onLongClick = { onDelete(group.key) })
.background(
Color(88, 88, 88, 88),
RoundedCornerShape(corner = CornerSize(16.dp))
)
.padding(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.size(24.dp)
.background(Color(group.color.color), CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
Text(color=MaterialTheme.colors.onBackground,text=group.name)
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End) {
if (!deleteMode) {
Button(onClick = { onAdd(0) }) {
Text(stringResource(R.string.action_add))
}
}
TextButton(onClick = {
if (deleteMode) onDismiss() else onClear()
}) {
Text(
text = if (deleteMode) stringResource(R.string.cancel)
else stringResource(R.string.action_clear)
)
}
}
}
},
)
}
@@ -0,0 +1,259 @@
package net.helcel.beans.activity.sub
import android.content.Context
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.CheckboxDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Tab
import androidx.compose.material.TabRow
import androidx.compose.material.Text
import androidx.compose.material.TriStateCheckbox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.Group
import net.helcel.beans.countries.World
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.NO_GROUP
import net.helcel.beans.helper.Settings
import kotlin.math.min
@Preview
@Composable
fun EditPlaceScreenPreview(){
EditPlaceScreen(Group.EEE)
}
/**
* Applies a place straight away where a single group makes the choice obvious,
* as tapping the checkbox does. Returns false when the group still has to be
* picked from the colour dialog.
*/
fun applyDirectVisit(ctx: Context, loc: GeoLoc): Boolean {
Data.selected_geoloc = loc
Data.selected_group = null
if (Data.groups.size() != 1 || !Settings.isSingleGroup(ctx)) return false
val current = Data.visits.getVisited(loc)
Data.visits.setVisited(
loc,
if (current == NO_GROUP || current == AUTO_GROUP) Data.groups.getUniqueEntry()!!.key
else if (loc.children.any { Data.visits.getVisited(it) != NO_GROUP }) AUTO_GROUP
else NO_GROUP,
)
syncVisited()
Data.saveData()
Data.selected_geoloc = null
return true
}
/** Stores whatever the colour dialog came back with. */
fun commitVisitDialog(cleared: Boolean) {
if (cleared) {
Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
syncVisited()
Data.saveData()
}
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
Data.selected_group = null
}
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)) {
val newState = if(itc.children.any { c -> Data.visits.getVisited(c) != NO_GROUP })
AUTO_GROUP
else
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)) {
val newState = if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
AUTO_GROUP
else
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
var selectedTab by remember { mutableIntStateOf(0) }
LaunchedEffect(tabs.size) {
selectedTab = tabs.lastIndex
}
SideEffect {
// 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)
else onExit()
}
if(showEdit)
EditPlaceDialog(false) {
showEdit = false
commitVisitDialog(it)
}
Column {
val currentTab = tabs.getOrNull(selectedTab) ?: return@Column
TabRow(
selectedTabIndex = min(tabs.lastIndex, selectedTab),
) {
tabs.forEachIndexed { index, tab ->
Tab(
selected = selectedTab == index,
onClick = {
while (tabs.size > index + 1)
tabs.removeAt(tabs.lastIndex)
},
text = { Text(tab.fullName) }
)
}
}
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(currentTab.children.toList(), key= {it.code}) { loc ->
GeoLocRow(loc, {
if (loc.children.isNotEmpty()){
tabs.add(loc)
}
}, {
if (!applyDirectVisit(ctx, loc)) showEdit = true
})
}
}
}
}
@Composable
fun GeoLocRow(
loc: GeoLoc,
onClick: () -> Unit,
onToggle: () -> Unit,
) {
val visits by Data.visits.visitsFlow.collectAsState()
val checked by remember(visits, loc) {
derivedStateOf {
when (visits.getOrElse(loc.code) { NO_GROUP }) {
NO_GROUP -> ToggleableState.Off
AUTO_GROUP -> ToggleableState.Indeterminate
else -> ToggleableState.On
}
}
}
val color = if (Data.visits.getVisited(loc) !in listOf(NO_GROUP, AUTO_GROUP))
Color(Data.groups.getGroupFromKey(Data.visits.getVisited(loc)).color.color)
else MaterialTheme.colors.onBackground
Row(
modifier = Modifier
.fillMaxWidth()
.height(50.dp)
.clickable(onClick = onClick) // whole row clickable
.padding(horizontal = 20.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = loc.fullName,
style = MaterialTheme.typography.body2,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.weight(1f)
)
Text(
text = "",//loc.children.size.toString(),
style = MaterialTheme.typography.body2,
modifier = Modifier.padding(end = 16.dp)
)
TriStateCheckbox(
state = checked,
onClick = onToggle,
colors = CheckboxDefaults.colors(
checkedColor = color,
),
modifier = Modifier.size(24.dp)
)
}
Spacer(modifier = Modifier
.height(2.dp)
.fillMaxWidth()
.background(MaterialTheme.colors.onBackground))
}
@@ -0,0 +1,157 @@
package net.helcel.beans.activity.sub
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import net.helcel.beans.R
import net.helcel.beans.activity.SysTheme
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.GeoLocTree
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.NO_GROUP
private class PickRow(val loc: GeoLoc, val depth: Int)
/**
* Turns the shapes a tap landed near into an indented list.
*
* Each candidate brings its continent and country along, so a tap that is
* ambiguous between two regions can still be answered with "the whole country"
* or "the whole continent" instead of only the regions themselves.
*/
private fun rowsFor(candidates: List<GeoLoc>): List<PickRow> {
val rows = LinkedHashMap<String, PickRow>()
candidates.forEach { candidate ->
GeoLocTree.chain(candidate).forEachIndexed { depth, loc ->
rows.getOrPut(loc.code) { PickRow(loc, depth) }
}
}
return rows.values.toList()
}
@Composable
fun MapPickDialog(
candidates: List<GeoLoc>,
onPick: (GeoLoc) -> Unit,
onDismiss: () -> Unit,
) {
val visits by Data.visits.visitsFlow.collectAsState()
val rows = remember(candidates) { rowsFor(candidates) }
SysTheme {
Dialog(
onDismissRequest = onDismiss,
content = {
Column(
modifier = Modifier
.background(
MaterialTheme.colors.background,
RoundedCornerShape(corner = CornerSize(16.dp)),
)
.padding(16.dp),
) {
Text(
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
text = stringResource(R.string.select_place),
)
Text(
style = MaterialTheme.typography.caption,
color = MaterialTheme.colors.onBackground,
text = stringResource(R.string.select_place_sub),
)
Spacer(modifier = Modifier.height(16.dp))
Box(
modifier = Modifier
.fillMaxWidth()
.heightIn(max = 360.dp),
) {
LazyColumn(modifier = Modifier.fillMaxWidth()) {
items(rows, key = { it.loc.code }) { row ->
val group = visits.getOrElse(row.loc.code) { NO_GROUP }
Row(
modifier = Modifier
.fillMaxWidth()
.clickable { onPick(row.loc) }
.background(
Color(88, 88, 88, 88),
RoundedCornerShape(corner = CornerSize(16.dp)),
)
.padding(
start = 8.dp + 16.dp * row.depth,
top = 8.dp,
end = 8.dp,
bottom = 8.dp,
),
verticalAlignment = Alignment.CenterVertically,
) {
Box(
modifier = Modifier
.size(16.dp)
.background(
if (group == NO_GROUP || group == AUTO_GROUP) {
MaterialTheme.colors.onBackground
} else {
Color(
Data.groups.getGroupFromKey(group).color.color
)
},
CircleShape,
),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
color = MaterialTheme.colors.onBackground,
style = MaterialTheme.typography.body2,
text = row.loc.fullName,
// Long names wrap inside the row rather
// than running off the end of it.
modifier = Modifier.weight(1f),
)
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.End,
) {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
}
}
},
)
}
}
@@ -7,8 +7,8 @@ enum class Country(
ATA("Antarctica", 14000000),
// HKG("Hong Kong", 1104),
// MAC("Macao", 32),
// ANT("Netherlands Antilles", 800),
// MAC("Macao", 32),
// ANT("Netherlands Antilles", 800),
AFG("Afghanistan", 645487),
XAD("Akrotiri and Dhekelia", 234),
ALA("Åland", 1483),
@@ -12,4 +12,4 @@ interface GeoLoc {
val type: LocType
val children: Set<GeoLoc>
}
}
@@ -12,6 +12,7 @@ object GeoLocImporter {
fun importStates(ctx: Context, force: Boolean = false) {
if (!Settings.isRegional(ctx) and !force) {
GeoLocTree.rebuild()
return
}
val fs = BufferedReader(InputStreamReader(ctx.assets.open("geoloc_state.txt")))
@@ -26,6 +27,7 @@ object GeoLocImporter {
}
}
}
GeoLocTree.rebuild()
}
fun clearStates() {
@@ -39,6 +41,7 @@ object GeoLocImporter {
}
country.children.clear()
}
GeoLocTree.rebuild()
Data.saveData()
}
}
@@ -0,0 +1,50 @@
package net.helcel.beans.countries
/**
* Code to place lookup, plus the chain of parents above a place.
*
* Regions only exist once [GeoLocImporter] has read them, so this is rebuilt
* whenever that set changes rather than derived once and cached forever.
*/
object GeoLocTree {
private var index: Map<String, GeoLoc> = emptyMap()
private var parents: Map<String, GeoLoc> = emptyMap()
fun rebuild() {
val byCode = HashMap<String, GeoLoc>()
val parent = HashMap<String, GeoLoc>()
byCode[World.WWW.code] = World.WWW
World.WWW.children.forEach { child ->
byCode[child.code] = child
parent[child.code] = World.WWW
child.children.forEach { country ->
byCode[country.code] = country
parent[country.code] = child
country.children.forEach { state ->
byCode[state.code] = state
parent[state.code] = country
}
}
}
index = byCode
parents = parent
}
fun find(code: String): GeoLoc? {
if (index.isEmpty()) rebuild()
return index[code]
}
/** [loc] and everything above it, world first, so it reads as a path. */
fun chain(loc: GeoLoc): List<GeoLoc> {
if (index.isEmpty()) rebuild()
val path = ArrayList<GeoLoc>(4)
var current: GeoLoc? = loc
while (current != null && current != World.WWW) {
path.add(current)
current = parents[current.code]
}
return path.reversed()
}
}
@@ -2,6 +2,7 @@ package net.helcel.beans.countries
import net.helcel.beans.countries.Country.*
@Suppress("RedundantSuppression", "SpellCheckingInspection", "unused")
enum class Group(override val fullName: String, override val children: Set<GeoLoc>) : GeoLoc {
EEE(
@@ -1,13 +1,20 @@
package net.helcel.beans.helper
import android.content.ContentValues
import android.content.Context
import android.content.SharedPreferences
import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLoc
import java.util.HashMap
import androidx.core.graphics.drawable.toDrawable
import androidx.core.content.edit
import android.content.Intent
import java.io.File
object Data {
var visits : Visits = Visits(0, HashMap())
@@ -15,7 +22,6 @@ object Data {
var selected_group : Groups.Group? = null
var selected_geoloc: GeoLoc? = null
var clearing_geoloc: GeoLoc? = null
private val groupsSerial = Groups.GroupsSerializer()
private val visitsSerial = Visits.VisitsSerializer()
@@ -23,7 +29,7 @@ object Data {
private lateinit var sharedPreferences: SharedPreferences
fun loadData(ctx: Context, id:Int) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(ctx)
sharedPreferences = defaultPreferences(ctx)
val groupsString = sharedPreferences.getString("groups_$id",null)
val visitsString = sharedPreferences.getString("visits_$id",null)
@@ -33,17 +39,82 @@ object Data {
// Add default group "Visited" with app's color if there is no group already
if (groups.size() == 0) {
groups.setGroup(DEFAULT_GROUP, "Visited", ColorDrawable(ContextCompat.getColor(ctx, R.color.blue)))
saveData()
groups.setGroup(DEFAULT_GROUP, "Visited",
ContextCompat.getColor(ctx, R.color.blue).toDrawable())
}
saveData()
}
fun saveData() {
if(groups.id != visits.id) return
val id = groups.id
val editor = sharedPreferences.edit()
editor.putString("groups_$id", groupsSerial.writeTo(groups))
editor.putString("visits_$id", visitsSerial.writeTo(visits))
editor.apply()
sharedPreferences.edit(commit=true) {
putString("groups_$id", groupsSerial.writeTo(groups))
putString("visits_$id", visitsSerial.writeTo(visits))
}
}
fun exportData(ctx: Context, filepath: Uri){
val groupsJson = groupsSerial.writeTo(groups)
val visitsJson = visitsSerial.writeTo(visits)
val outputStream = ctx.contentResolver.openOutputStream(filepath)
outputStream?.write(
buildString {
append(groupsJson)
append("\n---\n") // optional separator
append(visitsJson)
}.toByteArray())
outputStream?.flush()
outputStream?.close()
}
fun importData(ctx: Context, filePath: Uri) {
val inputStream = ctx.contentResolver.openInputStream(filePath)
val data = inputStream?.bufferedReader().use { it?.readText() }
if(data==null) return
val lines = data.split("\n---\n")
val groupsJson = lines[0]
val visitsJson = lines[1]
groups = if(groupsJson.isNotEmpty()) groupsSerial.readFrom(groupsJson.byteInputStream()) else groupsSerial.defaultValue
visits = if(visitsJson.isNotEmpty()) visitsSerial.readFrom(visitsJson.byteInputStream()) else visitsSerial.defaultValue
// Add default group "Visited" with app's color if there is no group already
if (groups.size() == 0) {
groups.setGroup(DEFAULT_GROUP, "Visited",
ContextCompat.getColor(ctx, R.color.blue).toDrawable())
}
saveData()
}
fun doImport(ctx: Context, file: Uri?){
if(file!=null) {
importData(ctx, file)
val intent = ctx.packageManager
.getLaunchIntentForPackage(ctx.packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
}
ctx.startActivity(intent)
}
}
fun doExport(ctx: Context){
val fileName = "beans_backup.json"
val resolver = ctx.contentResolver
val contentValues = ContentValues().apply {
put(MediaStore.Downloads.DISPLAY_NAME, fileName) // "backup.json"
put(MediaStore.Downloads.MIME_TYPE, "text/*")
put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
}
val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
} else {
val downloadsDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
val file = File(downloadsDir, fileName)
Uri.fromFile(file)
}
if(uri!=null) exportData(ctx, uri)
}
}
@@ -1,5 +0,0 @@
package net.helcel.beans.helper
interface DialogCloser {
fun onDialogDismiss(clear: Boolean)
}
@@ -2,15 +2,17 @@ package net.helcel.beans.helper
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import androidx.core.content.ContextCompat
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.json.Json
import net.helcel.beans.R
import java.io.InputStream
import kotlin.coroutines.coroutineContext
import kotlin.random.Random
import androidx.core.graphics.drawable.toDrawable
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
private const val randSeed = 0
@@ -20,52 +22,69 @@ const val NO_GROUP = 0
const val DEFAULT_GROUP = 1
const val AUTO_GROUP = -1
@Serializable
class Groups(val id: Int, private val grps: 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) {
grps[key] = Group(key, name, col)
val old = groups[key]
if (old != null && old.name == name && old.color.color == col.color) return
groups[key] = Group(key, name, col)
updateFlow()
}
fun deleteGroup(key: Int) {
grps.remove(key)
groups.remove(key)
updateFlow()
}
fun deleteAllExcept(grp: Int) {
val keysToDelete = grps.keys.filter { it != grp }
keysToDelete.forEach { grps.remove(it) }
fun keepOnly(key: Int) {
val keep = groups[key] ?: return
groups.clear()
groups[key] = keep
updateFlow()
}
private fun updateFlow() {
_groupsFlow.value = groups.values.toList()
}
fun getGroupFromKey(key: Int): Group {
return grps.getOrDefault(key, EmptyGroup())
return groups.getOrDefault(key, EmptyGroup())
}
fun genKey(): Int {
val key = rnd.nextInt()
if (grps.containsKey(key) || key in listOf(NO_GROUP, DEFAULT_GROUP, AUTO_GROUP)) return genKey()
if (groups.containsKey(key) || key in listOf(NO_GROUP, DEFAULT_GROUP, AUTO_GROUP)) return genKey()
return key
}
fun size(): Int {
return grps.size
return groups.size
}
fun getUniqueEntry(): Group? {
assert(size() == 1)
return if (grps.size == 1) {
grps[grps.keys.first()]
return if (groups.size == 1) {
groups[groups.keys.first()]
} else {
null
}
}
fun getGroupFromPos(pos: Int): Pair<Int, Group> {
val key = grps.keys.toList()[pos]
if(groups.keys.isEmpty()) return Pair(NO_GROUP,Group(NO_GROUP,"-"))
val key = groups.keys.toList()[pos]
return Pair(key, getGroupFromKey(key))
}
fun findGroupPos(key: Int): Int {
return grps.keys.toList().indexOf(key)
return groups.keys.toList().indexOf(key)
}
class EmptyGroup : Group(0, "")
@@ -74,9 +93,7 @@ class Groups(val id: Int, private val grps: HashMap<Int, Group>) {
open class Group(
val key: Int,
val name: String,
@Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = ColorDrawable(
Color.GRAY
)
@Serializable(with = Theme.ColorDrawableSerializer::class) val color: ColorDrawable = Color.GRAY.toDrawable()
)
@OptIn(ExperimentalSerializationApi::class)
@@ -0,0 +1,12 @@
package net.helcel.beans.helper
import android.content.Context
import android.content.SharedPreferences
/**
* The preference file opened directly, under the name and mode androidx's
* PreferenceManager used. Settings written by earlier versions still load, and
* the app no longer pulls in the preference UI framework for one call.
*/
fun defaultPreferences(ctx: Context): SharedPreferences =
ctx.getSharedPreferences(ctx.packageName + "_preferences", Context.MODE_PRIVATE)
@@ -2,21 +2,26 @@ package net.helcel.beans.helper
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import net.helcel.beans.R
import net.helcel.beans.activity.MainActivity
import net.helcel.beans.activity.fragment.SettingsFragment
import net.helcel.beans.activity.MainScreen
/**
* Default distance, in dp, around a tap that still counts as hitting a place.
* Kept small because the radius is measured on screen: zoomed out to the whole
* world, even a few dp reach across several countries.
*/
const val DEFAULT_TOUCH_RADIUS = 4
/** Largest tap radius the setting offers. */
const val MAX_TOUCH_RADIUS = 48
object Settings {
private lateinit var sp: SharedPreferences
private lateinit var mainActivity: MainActivity
fun start(ctx: MainActivity) {
private lateinit var mainActivity: MainScreen
fun start(ctx: MainScreen) {
mainActivity = ctx
sp = PreferenceManager.getDefaultSharedPreferences(ctx)
SettingsFragment.setTheme(
ctx, sp.getString(ctx.getString(R.string.key_theme), ctx.getString(R.string.system))
)
sp = defaultPreferences(ctx)
}
fun isSingleGroup(ctx: Context): Boolean {
@@ -33,6 +38,21 @@ object Settings {
)
}
/**
* How far around a tap, in dp, the map looks for other places. Anything
* within it is offered as a candidate instead of painting straight away.
*/
fun getTouchRadius(ctx: Context): Float {
return sp.getInt(ctx.getString(R.string.key_touch_radius), DEFAULT_TOUCH_RADIUS).toFloat()
}
fun isCascadeStats(ctx: Context): Boolean {
return getBooleanValue(
ctx,
sp.getString(ctx.getString(R.string.key_cascade_stats), ctx.getString(R.string.off))
)
}
fun getStatPref(ctx: Context): String? {
return sp.getString(
ctx.getString(R.string.key_stats),
@@ -41,7 +61,7 @@ object Settings {
}
fun refreshProjection(): Boolean {
mainActivity.refreshProjection()
(mainActivity).refreshProjection()
return true
}
@@ -1,23 +1,16 @@
package net.helcel.beans.helper
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.util.TypedValue
import androidx.appcompat.app.AppCompatActivity
import androidx.core.graphics.ColorUtils
import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import androidx.core.graphics.drawable.toDrawable
object Theme {
fun colorWrapper(ctx: Context, res: Int): ColorDrawable {
val colorPrimaryTyped = TypedValue()
ctx.theme.resolveAttribute(res, colorPrimaryTyped, true)
return ColorDrawable(colorPrimaryTyped.data)
}
fun colorToHex6(c: ColorDrawable): String {
return '#' + colorToHex8(c).substring(3)
@@ -28,11 +21,6 @@ object Theme {
return '#' + c.color.toHexString()
}
fun createActionBar(ctx: AppCompatActivity, title: String) {
ctx.supportActionBar?.title = title
ctx.supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
fun getContrastColor(color: Int): Int {
val whiteContrast = ColorUtils.calculateContrast(Color.WHITE, color)
val blackContrast = ColorUtils.calculateContrast(Color.BLACK, color)
@@ -43,7 +31,7 @@ object Theme {
override val descriptor = PrimitiveSerialDescriptor("ColorDrawable", PrimitiveKind.INT)
override fun deserialize(decoder: Decoder): ColorDrawable {
return ColorDrawable(decoder.decodeInt())
return decoder.decodeInt().toDrawable()
}
override fun serialize(encoder: Encoder, value: ColorDrawable) {
@@ -1,5 +1,7 @@
package net.helcel.beans.helper
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
@@ -11,19 +13,31 @@ import java.io.InputStream
@Serializable
class Visits(val id: Int, private val locs: HashMap<String, Int>) {
@kotlinx.serialization.Transient
private val _visitsFlow = MutableStateFlow<Map<String,Int>>(locs.toMutableMap())
@kotlinx.serialization.Transient
val visitsFlow: StateFlow<Map<String,Int>> = _visitsFlow
fun setVisited(key: GeoLoc?, b: Int) {
if (key == null)
if (key == null || locs[key.code] == b)
return
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 }
if (keysToDelete.isEmpty()) return
keysToDelete.forEach {
locs.remove(it)
}
updateFlow()
}
fun getVisited(key: GeoLoc): Int {
@@ -47,11 +61,18 @@ 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 {
locs[it] = group
if (locs[it] != group) {
locs[it] = group
changed = true
}
}
if (changed) {
updateFlow()
}
}
@@ -0,0 +1,23 @@
package net.helcel.beans.map
import android.content.Context
import net.helcel.beans.R
import net.helcel.beans.helper.defaultPreferences
/** Picks the map asset matching the projection the user chose. */
object MapAssets {
fun assetFor(ctx: Context): String {
val preferences = defaultPreferences(ctx)
return when (
preferences.getString(
ctx.getString(R.string.key_projection),
ctx.getString(R.string.mercator),
)
) {
ctx.getString(R.string.azimuthalequidistant) -> "aeqd01.bmap"
ctx.getString(R.string.loximuthal) -> "loxim01.bmap"
else -> "webmercator01.bmap"
}
}
}
@@ -0,0 +1,133 @@
package net.helcel.beans.map
import android.graphics.Path
import android.graphics.RectF
import java.io.InputStream
private const val VERSION = 2
/**
* Reads the packed map assets produced by `packmap.js`.
*
* The maps used to ship as SVG, which spends around four megabytes per
* projection spelling coordinates out in decimal ASCII inside XML. The packed
* form says the same thing in a fraction of that, and reading it is a straight
* scan over a byte array instead of an XML parse with a float scanner on top.
*
* The format is documented in `packmap.js`; the two have to agree.
*/
object MapReader {
fun read(input: InputStream): MapWorld {
val reader = ByteReader(input.readBytes())
reader.readHeader()
val width = reader.unsigned() * reader.scale
val height = reader.unsigned() * reader.scale
val count = reader.unsigned()
val shapes = ArrayList<MapShape>(count)
repeat(count) { shapes.add(reader.shape()) }
return MapWorld(width, height, shapes)
}
}
private class ByteReader(private val data: ByteArray) {
private var pos = 0
/** One grid step in user units, read from the header. */
var scale = 1f
private set
fun readHeader() {
require(
data.size > 5 &&
data[0] == 'B'.code.toByte() && data[1] == 'M'.code.toByte() &&
data[2] == 'A'.code.toByte() && data[3] == 'P'.code.toByte()
) { "not a packed map" }
require(data[4].toInt() == VERSION) { "unsupported packed map version ${data[4]}" }
pos = 5
scale = 1f / unsigned()
}
fun unsigned(): Int {
var result = 0
var shift = 0
while (true) {
val byte = data[pos++].toInt()
result = result or ((byte and 0x7F) shl shift)
if (byte and 0x80 == 0) return result
shift += 7
}
}
/** Zigzag, because deltas run both ways and are almost always tiny. */
fun signed(): Int {
val value = unsigned()
return if (value and 1 == 1) -((value ushr 1) + 1) else value ushr 1
}
fun flag(): Boolean = data[pos++].toInt() != 0
fun text(): String {
val length = unsigned()
val value = String(data, pos, length, Charsets.UTF_8)
pos += length
return value
}
fun shape(): MapShape {
val code = text()
val isState = flag()
val ringCount = unsigned()
val rings = ArrayList<FloatArray>(ringCount)
var nonZero: Path? = null
var evenOdd: Path? = null
var minX = Float.MAX_VALUE
var minY = Float.MAX_VALUE
var maxX = -Float.MAX_VALUE
var maxY = -Float.MAX_VALUE
repeat(ringCount) {
val points = unsigned()
val useEvenOdd = flag()
val ring = FloatArray(points * 2)
// Each point is a delta from the one before it, the first from the
// origin, so the whole ring decodes in one pass.
var x = 0
var y = 0
for (i in 0 until points) {
x += signed()
y += signed()
val fx = x * scale
val fy = y * scale
ring[i * 2] = fx
ring[i * 2 + 1] = fy
if (fx < minX) minX = fx
if (fx > maxX) maxX = fx
if (fy < minY) minY = fy
if (fy > maxY) maxY = fy
}
rings.add(ring)
val path = if (useEvenOdd) {
evenOdd ?: Path().also { it.fillType = Path.FillType.EVEN_ODD; evenOdd = it }
} else {
nonZero ?: Path().also { nonZero = it }
}
path.moveTo(ring[0], ring[1])
var i = 2
while (i < ring.size) {
path.lineTo(ring[i], ring[i + 1])
i += 2
}
path.close()
}
return MapShape(
code, isState, rings,
RectF(minX, minY, maxX, maxY),
nonZero, evenOdd,
)
}
}
@@ -0,0 +1,103 @@
package net.helcel.beans.map
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PorterDuff
import android.graphics.RectF
import kotlin.math.min
import kotlin.math.roundToInt
/** Draws a [MapWorld] onto a canvas through a pan/zoom transform. */
class MapRenderer(private val map: MapWorld) {
private val fill = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
}
private val stroke = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeCap = Paint.Cap.ROUND
strokeJoin = Paint.Join.ROUND
}
private val inverse = Matrix()
private val values = FloatArray(9)
private val visible = RectF()
/** False when the border is too faint to be worth a draw call at all. */
private var strokeVisible = true
/**
* Paints the map into [canvas], mapping user units through [transform].
* Shapes outside the canvas are skipped, which is what makes drawing cheap
* once the map is zoomed in.
*/
fun draw(canvas: Canvas, transform: Matrix, style: MapStyle) {
// SRC rather than blended: the bitmap is reused between renders.
canvas.drawColor(style.background, PorterDuff.Mode.SRC)
if (!transform.invert(inverse)) return
visible.set(0f, 0f, canvas.width.toFloat(), canvas.height.toFloat())
inverse.mapRect(visible)
// Widths are given in device pixels, so undo the zoom the canvas is
// about to apply to them, then let them grow slowly with it.
transform.getValues(values)
val scale = values[Matrix.MSCALE_X]
val perPixel = 1f / scale
val fitted = min(canvas.width / map.width, canvas.height / map.height)
val zoom = (scale / fitted).coerceAtLeast(1f)
val regionPx = strokeWidthAt(zoom, REGION_STROKE_STOPS)
val countryPx = strokeWidthAt(zoom, COUNTRY_STROKE_STOPS)
canvas.save()
canvas.concat(transform)
setStroke(style.background, if (style.regional) regionPx else countryPx, perPixel)
for ((code, color) in style.fills) {
val shape = map.byCode[code] ?: continue
if (!RectF.intersects(shape.bounds, visible)) continue
fill.color = color
drawShape(canvas, shape.nonZero, true)
drawShape(canvas, shape.evenOdd, true)
}
// With regions on, the country outlines come back on top as the thicker
// borders between them.
if (style.regional) {
setStroke(style.background, countryPx, perPixel)
for (shape in map.countries) {
if (!RectF.intersects(shape.bounds, visible)) continue
drawShape(canvas, shape.nonZero, false)
drawShape(canvas, shape.evenOdd, false)
}
}
canvas.restore()
}
/**
* Sets a border width given in device pixels.
*
* Anything thinner than a pixel cannot be drawn as such: the rasteriser
* turns it into a one pixel hairline at full strength, which is why every
* border below that width used to come out the same weight no matter how
* far out the map was. Below a pixel the width is carried as opacity
* instead, so a quarter-pixel border reads as a quarter as dark.
*/
private fun setStroke(color: Int, devicePx: Float, perPixel: Float) {
strokeVisible = devicePx > 0.004f
if (!strokeVisible) return
stroke.color = color
if (devicePx < 1f) {
stroke.strokeWidth = perPixel
stroke.alpha = (devicePx * 255f).roundToInt().coerceIn(0, 255)
} else {
stroke.strokeWidth = perPixel * devicePx
stroke.alpha = 255
}
}
private fun drawShape(canvas: Canvas, path: Path?, filled: Boolean) {
if (path == null) return
if (filled) canvas.drawPath(path, fill)
if (strokeVisible) canvas.drawPath(path, stroke)
}
}
@@ -0,0 +1,115 @@
package net.helcel.beans.map
import android.content.Context
import net.helcel.beans.countries.GeoLoc
import net.helcel.beans.countries.World
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data
import net.helcel.beans.helper.NO_GROUP
import net.helcel.beans.helper.Settings
import kotlin.math.ln
/**
* Border width in device pixels at each zoom, interpolated in between.
*
* Zoom counts how far in the map is from its fitted size, so 1 is the whole
* world on screen and 64 is as far in as it goes. The stops step by fours and
* are interpolated on a log axis, so the weight changes evenly as you pinch
* rather than in a rush at one end.
*
* Region borders start at nothing on purpose: a sub-national border says very
* little with the whole world in view, and drawing three and a half thousand of
* them only muddies the land.
*
* zoom 1x 4x 16x 64x
* region 0.0 0.5 1.0 2.0
* country 1.0 1.5 3.0 6.0
*/
val STROKE_ZOOM_STOPS = floatArrayOf(1f, 4f, 16f, 64f)
val REGION_STROKE_STOPS = floatArrayOf(0f, 0.5f, 1f, 2f)
val COUNTRY_STROKE_STOPS = floatArrayOf(1f, 1.5f, 3f, 6f)
/** The width for [zoom], straight-line between the stops on a log axis. */
fun strokeWidthAt(zoom: Float, widths: FloatArray): Float {
if (zoom <= STROKE_ZOOM_STOPS.first()) return widths.first()
for (i in 1 until STROKE_ZOOM_STOPS.size) {
if (zoom <= STROKE_ZOOM_STOPS[i]) {
val low = STROKE_ZOOM_STOPS[i - 1]
val t = ln(zoom / low) / ln(STROKE_ZOOM_STOPS[i] / low)
return widths[i - 1] + t * (widths[i] - widths[i - 1])
}
}
return widths.last()
}
/**
* The colour every shape is drawn with, resolved once per render.
*
* This replaces the stylesheet the map used to be rendered through. A place
* inherits the colour of its parent, so painting a whole continent still shows
* up on each country inside it, and [fills] ends up holding exactly the shapes
* that get drawn — which is also exactly what a tap may land on.
*/
class MapStyle(
val regional: Boolean,
val land: Int,
val background: Int,
val fills: Map<String, Int>,
) {
companion object {
fun build(ctx: Context, land: Int, background: Int): MapStyle {
val regional = Settings.isRegional(ctx)
val fills = HashMap<String, Int>()
World.WWW.children.forEach { child ->
if (child.type == GeoLoc.LocType.COUNTRY) {
// A country hanging straight off the world, such as Antarctica.
addCountry(fills, child, colorOf(World.WWW, regional), regional, land)
} else {
val continent = colorOf(child, regional) ?: colorOf(World.WWW, regional)
child.children.forEach { addCountry(fills, it, continent, regional, land) }
}
}
return MapStyle(regional, land, background, fills)
}
private fun addCountry(
fills: HashMap<String, Int>,
country: GeoLoc,
inherited: Int?,
regional: Boolean,
land: Int,
) {
val color = colorOf(country, regional) ?: inherited
// Regions are only drawn for countries that actually have some; the
// rest keep their outline filled so the map stays complete.
if (regional && country.children.isNotEmpty()) {
country.children.forEach { state ->
fills[state.code] = colorOf(state, regional) ?: color ?: land
}
} else {
fills[country.code] = color ?: land
}
}
/** The group colour assigned to [loc], or `null` when it has none of its own. */
private fun colorOf(loc: GeoLoc, regional: Boolean): Int? {
val key = Data.visits.getVisited(loc)
val group = Data.groups.getGroupFromKey(key)
if (group.key != NO_GROUP) return group.color.color
// A place marked automatically stands in for its children. That only
// needs a colour of its own where the children are not drawn, and
// never on a continent, which would swallow the whole map.
val isParent = loc.type == GeoLoc.LocType.GROUP ||
loc.type == GeoLoc.LocType.CUSTOM_GROUP ||
loc.type == GeoLoc.LocType.WORLD
if (key == AUTO_GROUP && !regional && !isParent) {
return Data.groups.getGroupFromPos(0).second.color.color
}
return null
}
}
}
@@ -0,0 +1,345 @@
package net.helcel.beans.map
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.Paint
import android.os.Handler
import android.os.HandlerThread
import android.os.SystemClock
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.widget.OverScroller
import androidx.core.graphics.createBitmap
import kotlin.math.min
/** A shape a tap landed on or near, with how far away it was in user units. */
class MapPick(val code: String, val distance: Float)
/** How far past the fitted map a pinch may zoom in. */
private const val MAX_ZOOM = 64f
/** How much a double tap zooms in by. */
private const val DOUBLE_TAP_ZOOM = 3f
/** Time a gesture has to settle before the map is redrawn sharp again. */
private const val RENDER_DELAY_MS = 80L
/** How long the map may stay stretched while a gesture keeps going. */
private const val MAX_STALE_MS = 500L
/**
* Most candidates a single tap will offer. A wide radius over a crowded part of
* the map can reach dozens of places, which is more of a list than anyone wants
* to read; the nearest few are what the tap was plausibly aiming at.
*/
private const val MAX_CANDIDATES = 16
/**
* The map itself: pan, pinch and tap over a [MapWorld].
*
* Drawing every country as vectors costs far too much to do on each frame, so a
* render lands in an off-screen bitmap on a worker thread and gestures just move
* that bitmap around. Once the gesture settles the map is drawn again at the new
* zoom, which is what keeps it sharp all the way in.
*/
@SuppressLint("ViewConstructor")
class MapView(context: Context) : View(context) {
var world: MapWorld? = null
set(value) {
if (field === value) return
field = value
renderer = value?.let(::MapRenderer)
fitToView()
requestRender(0L)
}
@Volatile
var style: MapStyle? = null
set(value) {
if (field === value) return
field = value
requestRender(0L)
}
/** How far from a tap, in dp, other shapes still count as candidates. */
var touchRadiusDp: Float = 4f
/** Called on a tap with every nearby shape, nearest first. */
var onPick: ((List<MapPick>) -> Unit)? = null
@Volatile
private var renderer: MapRenderer? = null
private val scroller = OverScroller(context)
private val transform = Matrix()
private val inverse = Matrix()
private val values = FloatArray(9)
private val point = FloatArray(2)
private var minScale = 1f
private var maxScale = 1f
private val blitPaint = Paint(Paint.FILTER_BITMAP_FLAG)
private val blit = Matrix()
private val lock = Any()
private var front: Bitmap? = null
private var back: Bitmap? = null
private val frontMatrix = Matrix()
private val frontValues = FloatArray(9)
private val pendingMatrix = Matrix()
private var pendingWidth = 0
private var pendingHeight = 0
@Volatile private var lastRenderAt = 0L
private val renderThread = HandlerThread("map-render").apply { start() }
private val renderHandler = Handler(renderThread.looper)
private val renderTask = Runnable { render() }
private val scaleDetector = ScaleGestureDetector(
context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
val scale = currentScale()
val target = (scale * detector.scaleFactor).coerceIn(minScale, maxScale)
transform.postScale(target / scale, target / scale, detector.focusX, detector.focusY)
clamp()
invalidate()
return true
}
},
)
private val gestureDetector = GestureDetector(
context,
object : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent): Boolean {
scroller.forceFinished(true)
return true
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float,
): Boolean {
val map = world ?: return false
transform.getValues(values)
val scale = values[Matrix.MSCALE_X]
val x = values[Matrix.MTRANS_X].toInt()
val y = values[Matrix.MTRANS_Y].toInt()
val w = map.width * scale
val h = map.height * scale
scroller.forceFinished(true)
scroller.fling(
x, y, velocityX.toInt(), velocityY.toInt(),
if (w > width) (width - w).toInt() else x, if (w > width) 0 else x,
if (h > height) (height - h).toInt() else y, if (h > height) 0 else y,
)
postInvalidateOnAnimation()
return true
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
transform.postTranslate(-distanceX, -distanceY)
clamp()
invalidate()
return true
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
pick(e.x, e.y)
return true
}
override fun onDoubleTap(e: MotionEvent): Boolean {
val scale = currentScale()
val target = if (scale >= maxScale * 0.99f) {
minScale
} else {
min(scale * DOUBLE_TAP_ZOOM, maxScale)
}
transform.postScale(target / scale, target / scale, e.x, e.y)
clamp()
invalidate()
return true
}
},
)
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
scaleDetector.onTouchEvent(event)
gestureDetector.onTouchEvent(event)
return true
}
override fun computeScroll() {
if (!scroller.computeScrollOffset()) return
transform.getValues(values)
values[Matrix.MTRANS_X] = scroller.currX.toFloat()
values[Matrix.MTRANS_Y] = scroller.currY.toFloat()
transform.setValues(values)
postInvalidateOnAnimation()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
fitToView()
requestRender(0L)
}
override fun onDraw(canvas: Canvas) {
val current = style
canvas.drawColor(current?.background ?: 0)
var stale = true
synchronized(lock) {
val bitmap = front
if (bitmap != null && !bitmap.isRecycled && frontMatrix.invert(blit)) {
blit.postConcat(transform)
canvas.drawBitmap(bitmap, blit, blitPaint)
transform.getValues(values)
stale = !values.contentEquals(frontValues)
}
}
// Scheduling only ever happens from here or from a setter, both on the
// main thread, so a render never starts while its bitmap is on screen.
if (stale) {
val overdue = SystemClock.uptimeMillis() - lastRenderAt > MAX_STALE_MS
requestRender(if (overdue) 0L else RENDER_DELAY_MS)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
scroller.forceFinished(true)
renderHandler.removeCallbacks(renderTask)
renderThread.quitSafely()
// Dropped rather than recycled: a render may still be part way through
// one of them, and the collector is happy to take them from here.
synchronized(lock) {
front = null
back = null
}
}
/** Centres the map in the view and works out how far it may be zoomed. */
private fun fitToView() {
val map = world ?: return
if (width == 0 || height == 0 || map.width <= 0f || map.height <= 0f) return
val scale = min(width / map.width, height / map.height)
minScale = scale
maxScale = scale * MAX_ZOOM
transform.setScale(scale, scale)
transform.postTranslate(
(width - map.width * scale) / 2f,
(height - map.height * scale) / 2f,
)
}
private fun currentScale(): Float {
transform.getValues(values)
return values[Matrix.MSCALE_X]
}
/** Keeps the map from being dragged away from the viewport. */
private fun clamp() {
val map = world ?: return
transform.getValues(values)
val scale = values[Matrix.MSCALE_X]
val w = map.width * scale
val h = map.height * scale
values[Matrix.MTRANS_X] = if (w <= width) {
(width - w) / 2f
} else {
values[Matrix.MTRANS_X].coerceIn(width - w, 0f)
}
values[Matrix.MTRANS_Y] = if (h <= height) {
(height - h) / 2f
} else {
values[Matrix.MTRANS_Y].coerceIn(height - h, 0f)
}
transform.setValues(values)
}
private fun requestRender(delay: Long) {
if (width == 0 || height == 0 || renderer == null || style == null) return
synchronized(lock) {
pendingMatrix.set(transform)
pendingWidth = width
pendingHeight = height
}
renderHandler.removeCallbacks(renderTask)
renderHandler.postDelayed(renderTask, delay)
}
private fun render() {
val matrix = Matrix()
val w: Int
val h: Int
synchronized(lock) {
matrix.set(pendingMatrix)
w = pendingWidth
h = pendingHeight
}
if (w <= 0 || h <= 0) return
val current = renderer ?: return
val currentStyle = style ?: return
var target = back
if (target == null || target.width != w || target.height != h) {
target?.recycle()
target = createBitmap(w, h)
back = target
}
current.draw(Canvas(target), matrix, currentStyle)
synchronized(lock) {
back = front
front = target
frontMatrix.set(matrix)
matrix.getValues(frontValues)
}
lastRenderAt = SystemClock.uptimeMillis()
postInvalidate()
}
/** Reports every shape within the tap radius of ([x], [y]), nearest first. */
private fun pick(x: Float, y: Float) {
val map = world ?: return
val current = style ?: return
val callback = onPick ?: return
if (!transform.invert(inverse)) return
point[0] = x
point[1] = y
inverse.mapPoints(point)
val radius = touchRadiusDp * resources.displayMetrics.density / currentScale()
val picks = ArrayList<MapPick>()
for (code in current.fills.keys) {
val shape = map.byCode[code] ?: continue
val bounds = shape.bounds
if (point[0] < bounds.left - radius || point[0] > bounds.right + radius) continue
if (point[1] < bounds.top - radius || point[1] > bounds.bottom + radius) continue
val distance = shape.distanceTo(point[0], point[1])
if (distance <= radius) picks.add(MapPick(code, distance))
}
if (picks.isEmpty()) return
picks.sortBy { it.distance }
callback(if (picks.size > MAX_CANDIDATES) picks.subList(0, MAX_CANDIDATES) else picks)
}
}
@@ -0,0 +1,86 @@
package net.helcel.beans.map
import android.graphics.Path
import android.graphics.RectF
import kotlin.math.sqrt
/**
* One named piece of map geometry: a country outline or a single region.
*
* The geometry is kept twice on purpose. [nonZero] and [evenOdd] are what the
* renderer hands to the canvas, while [rings] keeps the raw points because
* hit testing needs the distance to an edge, which a [Path] cannot give us.
*/
class MapShape(
val code: String,
val isState: Boolean,
val rings: List<FloatArray>,
val bounds: RectF,
val nonZero: Path?,
val evenOdd: Path?,
) {
/** True when ([x], [y]) falls inside the shape. Holes and lakes count as outside. */
fun contains(x: Float, y: Float): Boolean {
if (x < bounds.left || x > bounds.right || y < bounds.top || y > bounds.bottom) return false
// Even-odd crossing count over every ring at once: rings never overlap, so
// an island still reads as inside and a hole still reads as outside.
var inside = false
for (ring in rings) {
var j = ring.size - 2
var i = 0
while (i < ring.size) {
val yi = ring[i + 1]
val yj = ring[j + 1]
if ((yi > y) != (yj > y)) {
val xi = ring[i]
val xj = ring[j]
if (x < xi + (y - yi) * (xj - xi) / (yj - yi)) inside = !inside
}
j = i
i += 2
}
}
return inside
}
/** Distance from ([x], [y]) to the shape, `0` when the point is inside it. */
fun distanceTo(x: Float, y: Float): Float {
if (contains(x, y)) return 0f
var best = Float.MAX_VALUE
for (ring in rings) {
var j = ring.size - 2
var i = 0
while (i < ring.size) {
val d = segmentDistanceSq(x, y, ring[j], ring[j + 1], ring[i], ring[i + 1])
if (d < best) best = d
j = i
i += 2
}
}
return if (best == Float.MAX_VALUE) Float.MAX_VALUE else sqrt(best)
}
private fun segmentDistanceSq(
px: Float, py: Float,
ax: Float, ay: Float,
bx: Float, by: Float,
): Float {
val dx = bx - ax
val dy = by - ay
val len = dx * dx + dy * dy
val t = if (len <= 0f) 0f else (((px - ax) * dx + (py - ay) * dy) / len).coerceIn(0f, 1f)
val cx = px - (ax + t * dx)
val cy = py - (ay + t * dy)
return cx * cx + cy * cy
}
}
/** The whole map, in SVG user units, as a flat set of named shapes. */
class MapWorld(val width: Float, val height: Float, val shapes: List<MapShape>) {
val byCode: Map<String, MapShape> = shapes.associateBy { it.code }
/** Country outlines, keyed by ISO code. Drawn as borders when regions are on. */
val countries: List<MapShape> = shapes.filter { !it.isState }
}
@@ -1,68 +0,0 @@
package net.helcel.beans.svg
import android.content.Context
import net.helcel.beans.countries.World
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data.groups
import net.helcel.beans.helper.Data.visits
import net.helcel.beans.helper.NO_GROUP
import net.helcel.beans.helper.Settings
import net.helcel.beans.helper.Theme.colorToHex6
import net.helcel.beans.helper.Theme.colorWrapper
class CSSWrapper(private val ctx: Context) {
private val colorForeground: String =
colorToHex6(colorWrapper(ctx, android.R.attr.panelColorBackground))
private val colorBackground: String =
colorToHex6(colorWrapper(ctx, android.R.attr.colorBackground))
private val continents: String = World.WWW.children.joinToString(",") { "#${it.code}2" }
private val countries: String = World.WWW.children.joinToString(",") { itt ->
itt.children.joinToString(",") { "#${it.code}2" }
}
private val regional: String = World.WWW.children.joinToString(",") { itt ->
itt.children.joinToString(",") { "#${it.code}1" }
}
private val countryOnlyCSS: String =
"svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.1;}" +
"${regional}{display:none;}"
private val countryRegionalCSS: String =
"svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.01;}" +
"$continents,$countries{fill:none;stroke:$colorBackground;stroke-width:0.1;}"
private var customCSS: String = ""
init {
refresh()
}
private fun refresh() {
val id = if (Settings.isRegional(ctx)) "1" else "2"
customCSS = visits.getVisitedByValue().map { (k, v) ->
(if (groups.getGroupFromKey(k).key != NO_GROUP) {
v
} else if (!Settings.isRegional(ctx) && k == AUTO_GROUP) {
v.filter { it !in World.WWW.children.map { it1 -> it1.code } }
} else {
emptyList()
}).takeIf { it.isNotEmpty() }
?.joinToString(",") { "#${it}$id,#${it}" } + "{fill:${
colorToHex6(
if (k == AUTO_GROUP)
colorWrapper(ctx, android.R.attr.colorPrimary)
else groups.getGroupFromKey(k).color
)
};}"
}.joinToString("")
}
fun get(): String {
refresh()
return if (Settings.isRegional(ctx)) {
countryRegionalCSS + customCSS
} else {
countryOnlyCSS + customCSS
}
}
}
@@ -1,28 +0,0 @@
package net.helcel.beans.svg
import android.content.Context
import android.content.SharedPreferences
import androidx.preference.PreferenceManager
import com.caverock.androidsvg.SVG
import net.helcel.beans.R
class SVGWrapper(ctx: Context) {
private val sharedPreferences: SharedPreferences =
PreferenceManager.getDefaultSharedPreferences(ctx)
private val svgFile = when (sharedPreferences.getString(
ctx.getString(R.string.key_projection),
ctx.getString(R.string.mercator)
)) {
ctx.getString(R.string.azimuthalequidistant) -> "aeqd01.svg"
ctx.getString(R.string.loximuthal) -> "loxim01.svg"
ctx.getString(R.string.mercator) -> "webmercator01.svg"
else -> "webmercator01.svg"
}
private var svg: SVG? = SVG.getFromAsset(ctx.assets, svgFile)
fun get(): SVG? {
return svg
}
}
-13
View File
@@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="?attr/colorOnBackground"
android:pathData="M80,160Q80,127 103.5,103.5Q127,80 160,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L240,720L80,880ZM206,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L160,160Q160,160 160,160Q160,160 160,160L160,685L206,640ZM160,640L160,640L160,160Q160,160 160,160Q160,160 160,160L160,160Q160,160 160,160Q160,160 160,160L160,640Q160,640 160,640Q160,640 160,640L160,640Z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M480,280Q497,280 508.5,268.5Q520,257 520,240Q520,223 508.5,211.5Q497,200 480,200Q463,200 451.5,211.5Q440,223 440,240Q440,257 451.5,268.5Q463,280 480,280ZM440,600L520,600L520,360L440,360L440,600ZM80,880L80,160" />
</vector>
-9
View File
@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorOnBackground"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</vector>
-5
View File
@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>
-10
View File
@@ -1,10 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@color/white"
android:pathData="M346,820L100,574Q90,564 85,552Q80,540 80,527Q80,514 85,502Q90,490 100,480L330,251L224,145L286,80L686,480Q696,490 700.5,502Q705,514 705,527Q705,540 700.5,552Q696,564 686,574L440,820Q430,830 418,835Q406,840 393,840Q380,840 368,835Q356,830 346,820ZM393,314L179,528Q179,528 179,528Q179,528 179,528L607,528Q607,528 607,528Q607,528 607,528L393,314ZM792,840Q756,840 731,814.5Q706,789 706,752Q706,725 719.5,701Q733,677 750,654L792,600L836,654Q852,677 866,701Q880,725 880,752Q880,789 854,814.5Q828,840 792,840Z"/>
</vector>
-41
View File
@@ -1,41 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="72dp"
android:height="72dp"
android:viewportWidth="72"
android:viewportHeight="72">
<path
android:pathData="M31,16l0,-4l10,0l0,4"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="?attr/colorOnBackground"
android:strokeLineCap="round"/>
<path
android:pathData="M51,25v31c0,2.209 -1.791,4 -4,4H25c-2.209,0 -4,-1.791 -4,-4V25"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="?attr/colorOnBackground"
android:strokeLineCap="round"/>
<path
android:pathData="M17,16h38v4h-38z"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="?attr/colorOnBackground"
android:strokeLineCap="round"/>
<path
android:pathData="M41,28.25L41,55"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="?attr/colorOnBackground"
android:strokeLineCap="round"/>
<path
android:pathData="M31,28.25L31,55"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="?attr/colorOnBackground"
android:strokeLineCap="round"/>
</vector>
-10
View File
@@ -1,10 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="@color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>
-16
View File
@@ -1,16 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android" >
<path android:fillColor="?attr/colorOnBackground" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
<path android:fillColor="?attr/colorPrimary" android:pathData="M8,14m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
<path android:fillColor="?attr/colorPrimary" android:pathData="M12,8m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
<path android:fillColor="?attr/colorPrimary" android:pathData="M16,14m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"/>
</vector>
@@ -0,0 +1,35 @@
<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.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>
-18
View File
@@ -1,18 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="?attr/colorOnBackground"
android:pathData="M22,7h-9v2h9V7zM22,15h-9v2h9V15z"/>
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M5.54,11L2,7.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,11z"/>
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M5.54,19L2,15.46l1.41,-1.41l2.12,2.12l4.24,-4.24l1.41,1.41L5.54,19z"/>
</vector>
-16
View File
@@ -1,16 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android" >
<path
android:fillColor="?attr/colorOnBackground"
android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M15,18.89l-6,-2.11L9,5.11l6,2.11v11.67z"/>
</vector>
-14
View File
@@ -1,14 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android" >
<path
android:fillColor="?attr/colorOnBackground"
android:pathData="M12,22C6.49,22 2,17.51 2,12S6.49,2 12,2s10,4.04 10,9c0,3.31 -2.69,6 -6,6h-1.77c-0.28,0 -0.5,0.22 -0.5,0.5c0,0.12 0.05,0.23 0.13,0.33c0.41,0.47 0.64,1.06 0.64,1.67C14.5,20.88 13.38,22 12,22zM12,4c-4.41,0 -8,3.59 -8,8s3.59,8 8,8c0.28,0 0.5,-0.22 0.5,-0.5c0,-0.16 -0.08,-0.28 -0.14,-0.35c-0.41,-0.46 -0.63,-1.05 -0.63,-1.65c0,-1.38 1.12,-2.5 2.5,-2.5H16c2.21,0 4,-1.79 4,-4C20,7.14 16.41,4 12,4z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M17.5,13c-0.83,0 -1.5,-0.67 -1.5,-1.5c0,-0.83 0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5C19,12.33 18.33,13 17.5,13zM14.5,9C13.67,9 13,8.33 13,7.5C13,6.67 13.67,6 14.5,6S16,6.67 16,7.5C16,8.33 15.33,9 14.5,9zM5,11.5C5,10.67 5.67,10 6.5,10S8,10.67 8,11.5C8,12.33 7.33,13 6.5,13S5,12.33 5,11.5zM11,7.5C11,8.33 10.33,9 9.5,9S8,8.33 8,7.5C8,6.67 8.67,6 9.5,6S11,6.67 11,7.5z" />
</vector>
-14
View File
@@ -1,14 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android" >
<path
android:fillColor="?attr/colorOnBackground"
android:pathData="M105,727L40,680L240,360L360,500L520,240L629,403Q606,404 585.5,408.5Q565,413 545,421L523,388L371,635L250,494L105,727ZM732,423Q713,415 692.5,410Q672,405 650,404L855,80L920,127L732,423Z" />
<path
android:fillColor="?attr/colorPrimary"
android:pathData="M863,920L738,795Q718,809 693.5,816Q669,823 643,823Q568,823 515.5,770.5Q463,718 463,643Q463,568 515.5,515.5Q568,463 643,463Q718,463 770.5,515.5Q823,568 823,643Q823,669 816,693.5Q809,718 795,739L920,863L863,920ZM643,743Q685,743 714,714Q743,685 743,643Q743,601 714,572Q685,543 643,543Q601,543 572,572Q543,601 543,643Q543,685 572,714Q601,743 643,743Z" />
</vector>
-12
View File
@@ -1,12 +0,0 @@
<vector
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24"
android:width="24dp"
xmlns:android="http://schemas.android.com/apk/res/android" >
<path android:fillColor="?attr/colorOnBackground" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
<path android:fillColor="?attr/colorPrimary" android:pathData="M12,10h-2v2H9v-2H7V9h2V7h1v2h2v1z"/>
</vector>
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:theme="@style/Theme.Beans"
tools:context=".activity.EditActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:theme="@style/Theme.Beans"
tools:context=".activity.MainActivity">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/photo_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:theme="@style/Theme.Beans"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.SettingsActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
-66
View File
@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:theme="@style/Theme.Beans"
tools:context=".activity.StatsActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:visibility="gone" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/group_color"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:paddingStart="56dp"
android:text="@string/total"
android:textAlignment="textStart"
android:textColor="?attr/colorOnPrimary"
app:cornerRadius="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:gravity="start|center_vertical"
android:paddingStart="20dp"
android:paddingEnd="52dp"
android:text=""
android:textColor="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="@id/group_color"
app:layout_constraintEnd_toEndOf="@id/group_color"
app:layout_constraintTop_toTopOf="@id/group_color" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/stats"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
@@ -1,67 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.fragment.AboutFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_horizontal"
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_marginTop="20dp"
android:contentDescription="@string/logo"
android:src="@drawable/ic_launcher_foreground" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:text="@string/app_name"
android:textAlignment="center"
android:textSize="30sp"
android:textStyle="bold" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="15dp"
android:text="@string/app_version"
android:textAlignment="center"
android:textSize="25sp" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="15dp"
android:text="@string/beans_is_foss"
android:textAlignment="center" />
<com.google.android.material.textview.MaterialTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="15dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="15dp"
android:autoLink="web"
android:text="@string/beans_repo"
android:textAlignment="center" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,137 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
tools:context=".activity.fragment.EditGroupAddFragment">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/group_name"
android:layout_width="match_parent"
android:layout_height="48dp"
android:autofillHints=""
android:hint="@string/name"
android:inputType="text" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/colorView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/colorR"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.slider.Slider
android:id="@+id/colorR"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/colorG"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/colorView"
app:layout_constraintTop_toTopOf="parent"
app:thumbColor="@color/red"
app:trackColorActive="@color/red" />
<com.google.android.material.slider.Slider
android:id="@+id/colorG"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:foregroundTint="#FF0000"
app:layout_constraintBottom_toTopOf="@id/colorB"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/colorView"
app:layout_constraintTop_toBottomOf="@id/colorR"
app:thumbColor="@color/green"
app:trackColorActive="@color/green" />
<com.google.android.material.slider.Slider
android:id="@+id/colorB"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/colorView"
app:layout_constraintTop_toBottomOf="@id/colorG"
app:thumbColor="@color/blue"
app:trackColorActive="@color/blue" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:labelFor="@id/group_color"
android:text="@string/hashtag" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/group_color"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:autofillHints=""
android:hint="@string/color_rrggbb"
android:inputType="text"
android:maxLength="6" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDelete"
android:layout_width="52dp"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:tooltipText="@string/delete"
app:icon="@drawable/delete"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCancel"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:text="@string/cancel"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/btnOk"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnOk"
android:layout_width="52dp"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:text="@string/ok"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.fragment.EditPlaceFragment">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:nestedScrollingEnabled="false"
android:scrollbars="vertical" />
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/warning_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="4dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/groups_color"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAdd"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:text="@string/add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnClear"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:text="@string/clear"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.fragment.LicenseFragment">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/license_fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,52 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/textView"
android:layout_width="0dp"
android:layout_height="50dp"
android:clickable="true"
android:focusable="true"
android:gravity="start|center_vertical"
android:insetTop="4dp"
android:insetBottom="4dp"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textAllCaps="false"
android:textAppearance="?attr/textAppearanceBody2"
android:textColor="?attr/colorOnBackground"
app:cornerRadius="4dp"
app:layout_constraintBottom_toBottomOf="@id/checkBox"
app:layout_constraintEnd_toStartOf="@id/checkBox"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:gravity="start|center_vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp"
app:layout_constraintBottom_toBottomOf="@id/checkBox"
app:layout_constraintEnd_toStartOf="@id/checkBox"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkBox"
android:layout_width="50dp"
android:layout_height="50dp"
app:checkedState="indeterminate"
app:layout_constraintBottom_toBottomOf="@id/textView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toEndOf="@id/textView"
app:layout_constraintTop_toTopOf="@id/textView"
app:layout_constraintVertical_bias="0.5" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -1,34 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/group_color"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="32dp"
android:layout_marginBottom="2dp"
android:textAlignment="textStart"
android:textColor="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:gravity="start|center_vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:textColor="?attr/colorOnPrimary"
app:layout_constraintBottom_toBottomOf="@id/group_color"
app:layout_constraintEnd_toEndOf="@id/group_color"
app:layout_constraintTop_toTopOf="@id/group_color" />
</androidx.constraintlayout.widget.ConstraintLayout>
-13
View File
@@ -1,13 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="net.helcel.beans.activity.EditActivity" >
<item
android:id="@+id/action_color"
android:orderInCategory="100"
android:icon="@drawable/color"
android:title="@string/action_color"
app:showAsAction="ifRoom" />
</menu>
-25
View File
@@ -1,25 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="net.helcel.beans.activity.MainActivity" >
<item
android:id="@+id/action_edit"
android:orderInCategory="100"
android:icon="@drawable/edit"
android:title="@string/action_edit"
app:showAsAction="ifRoom" />
<item
android:id="@+id/action_stats"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orderInCategory="100"
android:title="@string/action_stat"
app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>
@@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="android:Theme.Material.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>
</style>
</resources>
+7
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>
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="entries_theme">
<item>@string/system</item>
<item>@string/light</item>
<item>@string/dark</item>
</string-array>
<string-array name="entries_stats">
<item>@string/counters</item>
<item>@string/percentages</item>
</string-array>
<string-array name="entries_onoff">
<item>@string/on</item>
<item>@string/off</item>
</string-array>
<string-array name="map_projection">
<item>@string/azimuthalequidistant</item>
<item>@string/loximuthal</item>
<item>@string/mercator</item>
</string-array>
</resources>
+31 -12
View File
@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Beans</string>
<string name="app_version">1.0a</string>
<string name="action_settings">Settings</string>
<string name="action_stat">Stats</string>
<string name="action_edit">Edit</string>
<string name="action_add">Add</string>
<string name="action_clear">Clear</string>
<string name="action_color">Color</string>
<string name="key_theme">App theme</string>
<string name="system">System</string>
@@ -13,21 +13,39 @@
<string name="key_stats">Statistics</string>
<string name="counters">Prefer counters (#)</string>
<string name="percentages">Prefer percentages (%)</string>
<string name="licenses">Licenses</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_cascade_stats">key_cascade_stats</string>
<string name="pref_cascade_stats">Cascade statistics</string>
<string name="pref_cascade_stats_desc">Include parents and children in the counts</string>
<string name="key_group_desc">Enable multiple colors</string>
<string name="key_touch_radius">key_touch_radius</string>
<string name="pref_touch_radius">Tap radius</string>
<string name="pref_touch_radius_desc">How far around a tap on the map other places should be considered</string>
<string name="pref_touch_radius_value">%1$d dp</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_repo">Project repository: https://github.com/helcel-net/beans\n Feel free to report issues or contribute to the project.</string>
<string name="foss_licenses">Free and open source dependencies and licenses</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="about_beans">About the Beans application</string>
<string name="edit_group">Select the group to assign. Long press on a group to edit its name and color.</string>
<string name="edit_group">Select the group to assign.</string>
<string name="edit_group_sub">Long press on a group to edit its name and color.</string>
<string name="select_group">Select the group to keep.</string>
<string name="select_group_sub">All others will be deleted and its mappings reassigned to the group you choose here.</string>
<string name="delete_group">Are your sure you want to delete this group and remove all its country mappings?</string>
<string name="select_group">Select one group you want to keep. All others will be deleted and its mappings reassigned to the group you choose here.</string>
<string name="select_place">Select a place</string>
<string name="select_place_sub">Tap the place to color, or a larger one that contains it.</string>
<string name="delete_regions">Are you sure you want to disable regions and reassign all regional mappings to the corresponding countries?</string>
<string name="add">Add</string>
<string name="clear">Clear</string>
<string name="logo">Logo</string>
<string name="name">Name</string>
<string name="rate">%1$d/%2$d</string>
<string name="rate_with_unit">%1$d / %2$d %3$s</string>
@@ -39,9 +57,10 @@
<string name="off">Off</string>
<string name="delete">Delete</string>
<string name="cancel">Cancel</string>
<string name="ok">Ok</string>
<string name="ok">OK</string>
<string name="total">Total</string>
<string name="uncategorized">Uncategorized</string>
<string name="azimuthalequidistant">Azimuthal Equidistant</string>
<string name="mercator">Mercator</string>
<string name="loximuthal">Loximuthal</string>
+8 -28
View File
@@ -1,32 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Beans" parent="Theme.Material3.DayNight">
<item name="colorPrimary">@color/blue</item>
<item name="background">@color/darkgray</item>
<item name="android:colorPrimary">?attr/colorPrimary</item>
<item name="android:panelColorBackground">@color/lightgray</item>
<item name="android:statusBarColor">?attr/colorPrimary</item>
<style name="Theme.Beans" parent="android:Theme.Material.Light.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>
<item name="checkboxStyle">@style/Theme.Beans.CheckBox</item>
<item name="actionBarStyle">@style/Theme.Beans.ActionBar</item>
<item name="android:actionOverflowButtonStyle">@style/Theme.Beans.ActionBar.ButtonOverflow</item>
</style>
<style name="Theme.Beans.CheckBox" parent="Widget.Material3.CompoundButton.CheckBox">
</style>
<style name="Theme.Beans.ActionBar" parent="Widget.Material3.ActionBar.Solid">
<item name="background">?attr/colorPrimary</item>
<item name="titleTextStyle">@style/Theme.Beans.ActionBar.Text</item>
<item name="android:tint">@color/white</item>
<item name="actionMenuTextColor">@color/white</item>
<item name="homeAsUpIndicator">@drawable/back</item>
</style>
<style name="Theme.Beans.ActionBar.Text" parent="TextAppearance.Material3.ActionBar.Title">
<item name="android:textColor">@color/white</item>
</style>
<style name="Theme.Beans.ActionBar.ButtonOverflow" parent="Widget.Material3.Search.ActionButton.Overflow">
<item name="android:tint">@color/white</item>
</style>
</resources>
@@ -1,73 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:theme="@style/Theme.Beans">
<ListPreference
app:defaultValue="@string/system"
app:enabled="true"
app:entries="@array/entries_theme"
app:entryValues="@array/entries_theme"
app:icon="@drawable/palette"
app:key="@string/key_theme"
app:title="@string/key_theme"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/mercator"
app:enabled="true"
app:entries="@array/map_projection"
app:entryValues="@array/map_projection"
app:icon="@drawable/map"
app:key="@string/key_projection"
app:title="@string/key_projection"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/counters"
app:enabled="true"
app:entries="@array/entries_stats"
app:entryValues="@array/entries_stats"
app:icon="@drawable/stats"
app:key="@string/key_stats"
app:title="@string/key_stats"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/off"
app:enabled="true"
app:allowDividerAbove="true"
app:entries="@array/entries_onoff"
app:entryValues="@array/entries_onoff"
app:icon="@drawable/group"
app:key="@string/key_group"
app:title="@string/key_group"
app:useSimpleSummaryProvider="true" />
<ListPreference
app:defaultValue="@string/off"
app:enabled="true"
app:entries="@array/entries_onoff"
app:entryValues="@array/entries_onoff"
app:icon="@drawable/zoomin"
app:key="@string/key_regional"
app:title="@string/key_regional"
app:useSimpleSummaryProvider="true" />
<Preference
android:summary="@string/foss_licenses"
app:enabled="true"
app:allowDividerAbove="true"
app:icon="@drawable/licenses"
app:key="@string/licenses"
app:title="@string/licenses" />
<Preference
android:summary="@string/about_beans"
app:enabled="true"
app:icon="@drawable/about"
app:key="@string/about"
app:title="@string/about" />
</PreferenceScreen>
+3 -3
View File
@@ -1,6 +1,6 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.7.2' apply false
id 'com.android.library' version '8.7.2' apply false
id 'org.jetbrains.kotlin.android' version '2.0.21' apply false
id 'com.android.application' version '9.4.0' apply false
id 'com.android.library' version '9.3.2' apply false
id 'org.jetbrains.kotlin.android' version '2.4.10' apply false
}
+2 -8
View File
@@ -1,6 +1,6 @@
#!/bin/node
import {readFileSync, existsSync} from 'fs'
import {readFileSync,existsSync} from 'fs'
import area from '@turf/area'
import * as turf from '@turf/turf'
@@ -24,7 +24,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 +32,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 +39,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
]
+48 -43
View File
@@ -1,17 +1,16 @@
#!/bin/bash
LOCAL_SVG_PATH="app/src/main/assets/"
GADM_VERSION="4.1"
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"
@@ -79,44 +78,47 @@ download_1() {
}
toSVG_0() {
local input_files=("ATA")
generate_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
for country in "${countries[@]}"
do
input_file="./temp/0/${country}.json"
if [ -f "$input_file" ]; then
input_files+=("$input_file")
else
echo "Input file $input_file not found."
fi
done
echo "Generating: $OUT_FILE using projection [$PROJ_ARGS]"
echo "Processing ${#FILES_TO_RUN[@]} files..."
# Each feature is tagged with the place it belongs to and whether it is a
# country outline or one of its regions. packmap.js groups on that; nothing
# downstream needs the features split into layers.
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;
// Antarctica's boundary file carries no country code at either level.
if (cCode === 'UNK') cCode = 'ATA';
let isGidMissing = (!GID || GID === 'undefined' || GID === 'null' || GID === '');
CODE = isGidMissing ? cCode : GID;
LEVEL = isGidMissing ? 0 : 1;
"
"$mapshaper" -i "${FILES_TO_RUN[@]}" combine-files \
-snap \
-merge-layers force\
-proj $PROJ_ARGS densify \
-simplify 10% weighted keep-shapes \
-filter-islands min-area=0 \
-sort 'this.area' ascending \
-each "$JS_PIPELINE_LOGIC" \
-o "$OUT_FILE" \
format=geojson
"$mapshaper" -i combine-files ${input_files[@]} -proj webmercator -simplify 0.005 weighted keep-shapes resolution=1200x1200 -o ./app/src/main/assets/mercator0.svg svg-data=GID_0,COUNTRY id-field=GID_0
"$mapshaper" -i combine-files ${input_files[@]} -proj aeqd +lat_0=90 -simplify 0.005 weighted keep-shapes resolution=1200x1200 -o ./app/src/main/assets/aeqd0.svg svg-data=GID_0,COUNTRY id-field=GID_0
}
toSVG_1() {
input_files=("ATA")
for country in "${countries[@]}"
do
input_file="./temp/1/${country}.json"
# input_file="./temp/1/gadm41_${country}_1.json"
if [ -f "$input_file" ]; then
input_files+=("$input_file")
else
echo "Input file $input_file not found."
fi
done
"$mapshaper" -i combine-files ${input_files[@]} -proj webmercator -simplify 0.005 weighted keep-shapes resolution=1200x1200 -o ./app/src/main/assets/mercator1.svg svg-data=GID_0,COUNTRY,GID,NAME id-field=GID
"$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
}
toSVG_01() {
build_maps() {
input_files=()
input_files+=("./temp/1/ATA.json")
@@ -136,9 +138,12 @@ 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_map "./mapdata/loxim01.geojson" "loxim" "${input_files[@]}"
generate_map "./mapdata/webmercator01.geojson" "webmercator" "${input_files[@]}"
generate_map "./mapdata/aeqd01.geojson" "aeqd +lat_0=90" "${input_files[@]}"
# The app reads the packed form; the GeoJSON above is only an intermediate.
node packmap.js ./mapdata/loxim01.geojson ./mapdata/webmercator01.geojson ./mapdata/aeqd01.geojson
}
do_1() {
@@ -153,10 +158,10 @@ 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
build_maps
#CUW, CCK, XCL, CXR, IOT, BVT, ABW, FLK, GIB, HMD, KIR, SXM, MDV, MCO, NIU, NFK, PCN, MAF, SGS, VAT
+4 -19
View File
@@ -1,24 +1,9 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.enableJetifier=false
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonTransitiveRClass=true
android.uniquePackageNames=false
android.dependency.useConstraints=false
android.r8.strictFullModeForKeepRules=false
Binary file not shown.
+3 -2
View File
@@ -1,8 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored
+7 -11
View File
@@ -1,7 +1,7 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
# gradlew start up script for POSIX generated by Gradle.
#
# Important for running:
#
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
# ksh gradlew
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -115,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -173,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -206,15 +203,14 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
Vendored
+12 -24
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem gradlew startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,30 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
@rem Execute gradlew
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%
-325
View File
@@ -1,325 +0,0 @@
var continents = {
XAD:"EEE",
ALA:"EEE",
ALB:"EEE",
AND:"EEE",
AUT:"EEE",
BLR:"EEE",
BEL:"EEE",
BIH:"EEE",
BGR:"EEE",
HRV:"EEE",
CYP:"EEE",
CZE:"EEE",
DNK:"EEE",
EST:"EEE",
FRO:"EEE",
FIN:"EEE",
FRA:"EEE",
DEU:"EEE",
GIB:"EEE",
GRC:"EEE",
GGY:"EEE",
HUN:"EEE",
ISL:"EEE",
IRL:"EEE",
IMN:"EEE",
ITA:"EEE",
JEY:"EEE",
KAZ:"EEE",
XKO:"EEE",
LVA:"EEE",
LIE:"EEE",
LTU:"EEE",
LUX:"EEE",
MLT:"EEE",
MDA:"EEE",
MCO:"EEE",
MNE:"EEE",
NLD:"EEE",
MKD:"EEE",
NOR:"EEE",
POL:"EEE",
PRT:"EEE",
ROU:"EEE",
RUS:"EEE",
SMR:"EEE",
SRB:"EEE",
SVK:"EEE",
SVN:"EEE",
ESP:"EEE",
SJM:"EEE",
SWE:"EEE",
CHE:"EEE",
UKR:"EEE",
GBR:"EEE",
VAT:"EEE",
AFG:"ABB",
ARM:"ABB",
AZE:"ABB",
BHR:"ABB",
BGD:"ABB",
BTN:"ABB",
IOT:"ABB",
BRN:"ABB",
KHM:"ABB",
CCK:"ABB",
CHN:"ABB",
CXR:"ABB",
GEO:"ABB",
HKG:"ABB",
IND:"ABB",
IDN:"ABB",
IRN:"ABB",
IRQ:"ABB",
ISR:"ABB",
JPN:"ABB",
JOR:"ABB",
KWT:"ABB",
KGZ:"ABB",
LAO:"ABB",
LBN:"ABB",
MAC:"ABB",
MYS:"ABB",
MDV:"ABB",
MNG:"ABB",
MMR:"ABB",
NPL:"ABB",
PRK:"ABB",
OMN:"ABB",
PAK:"ABB",
PSE:"ABB",
PHL:"ABB",
QAT:"ABB",
SAU:"ABB",
SGP:"ABB",
KOR:"ABB",
LKA:"ABB",
SYR:"ABB",
TWN:"ABB",
TJK:"ABB",
THA:"ABB",
TLS:"ABB",
TUR:"ABB",
TKM:"ABB",
ARE:"ABB",
UZB:"ABB",
VNM:"ABB",
YEM:"ABB",
ZNC:"ABB",
DZA:"FFF",
AGO:"FFF",
BDI:"FFF",
BEN:"FFF",
BWA:"FFF",
BFA:"FFF",
BDI:"FFF",
CPV:"FFF",
CMR:"FFF",
CAF:"FFF",
TCD:"FFF",
COM:"FFF",
COG:"FFF",
COD:"FFF",
CIV:"FFF",
DJI:"FFF",
EGY:"FFF",
GNQ:"FFF",
ERI:"FFF",
ETH:"FFF",
ATF:"FFF",
GAB:"FFF",
GMB:"FFF",
GHA:"FFF",
GIN:"FFF",
GNB:"FFF",
KEN:"FFF",
LSO:"FFF",
LBR:"FFF",
LBY:"FFF",
MDG:"FFF",
MWI:"FFF",
MLI:"FFF",
MRT:"FFF",
MUS:"FFF",
MYT:"FFF",
MAR:"FFF",
MOZ:"FFF",
NAM:"FFF",
NER:"FFF",
NGA:"FFF",
COD:"FFF",
REU:"FFF",
RWA:"FFF",
STP:"FFF",
SEN:"FFF",
SYC:"FFF",
SLE:"FFF",
SOM:"FFF",
ZAF:"FFF",
SSD:"FFF",
SHN:"FFF",
SDN:"FFF",
SWZ:"FFF",
TZA:"FFF",
TGO:"FFF",
TUN:"FFF",
UGA:"FFF",
COD:"FFF",
ZMB:"FFF",
ZWE:"FFF",
ESH:"FFF",
ABW:"NNN",
AIA:"NNN",
ATG:"NNN",
BHS:"NNN",
BRB:"NNN",
BLZ:"NNN",
BMU:"NNN",
BES:"NNN",
VGB:"NNN",
CAN:"NNN",
CYM:"NNN",
XCL:"NNN",
CRI:"NNN",
CUB:"NNN",
CUW:"NNN",
DMA:"NNN",
DOM:"NNN",
SLV:"NNN",
GRL:"NNN",
GRD:"NNN",
GLP:"NNN",
GTM:"NNN",
HTI:"NNN",
HND:"NNN",
JAM:"NNN",
MTQ:"NNN",
MEX:"NNN",
MSR:"NNN",
ANT:"NNN",
CUW:"NNN",
NIC:"NNN",
PAN:"NNN",
PRI:"NNN",
BLM:"NNN",
KNA:"NNN",
LCA:"NNN",
MAF:"NNN",
SPM:"NNN",
VCT:"NNN",
SXM:"NNN",
TTO:"NNN",
TCA:"NNN",
USA:"NNN",
UMI:"NNN",
VIR:"NNN",
ARG:"SRR",
BOL:"SRR",
BRA:"SRR",
CHL:"SRR",
COL:"SRR",
ECU:"SRR",
FLK:"SRR",
GUF:"SRR",
GUY:"SRR",
PRY:"SRR",
PER:"SRR",
SUR:"SRR",
URY:"SRR",
VEN:"SRR",
ASM:"UUU",
AUS:"UUU",
COK:"UUU",
FJI:"UUU",
PYF:"UUU",
GUM:"UUU",
KIR:"UUU",
MHL:"UUU",
FSM:"UUU",
NRU:"UUU",
NCL:"UUU",
NZL:"UUU",
NIU:"UUU",
NFK:"UUU",
MNP:"UUU",
PLW:"UUU",
PNG:"UUU",
PCN:"UUU",
WSM:"UUU",
SLB:"UUU",
TKL:"UUU",
TON:"UUU",
TUV:"UUU",
VUT:"UUU",
WLF:"UUU",
ATA:"XXX",
BVT:"XXX",
HMD:"XXX",
SGS:"XXX",
}
import fs from "fs"
import {JSDOM} from "jsdom"
function groupByContinent(svgFilePath) {
// Read the SVG file
fs.readFile(svgFilePath, 'utf-8', (err, data) => {
if (err) {
console.error('Error reading SVG file:', err);
return;
}
// Create a virtual DOM with JSDOM
const dom = new JSDOM(data);
const document = dom.window.document;
// Select the root <svg> element
const svgElement = document.querySelector('svg');
// Group <g> elements representing countries by continent
const world = {}; // Renamed continents object to 'world'
svgElement.querySelectorAll('g').forEach(countryGroup => {
const countryId = countryGroup.getAttribute('id');
if (countryId!= null){
const continentId = continents[countryId.replace(/\d/g, "")] || "XXX"; // Remove numbers from country ID to get continent ID
if (!world[continentId]) {
world[continentId] = document.createElementNS('http://www.w3.org/2000/svg', 'g');
world[continentId].setAttribute('id', continentId);
}
world[continentId].innerHTML += countryGroup.outerHTML;
}else{
console.log(countryId,countryGroup.outerHTML)
}
});
// Create new <g> elements for each continent
svgElement.innerHTML = '';
for (const continentId in world) {
svgElement.appendChild(world[continentId]);
}
// Output the modified SVG
fs.writeFile(svgFilePath, svgElement.outerHTML, 'utf-8', err => {
if (err) {
console.error('Error writing to SVG file:', err);
return;
}
console.log('SVG file updated successfully.');
});
});
}
// Example usage:
const svgFileBasePath = "./app/src/main/assets/" ;
groupByContinent(svgFileBasePath+ "webmercator01.svg");
groupByContinent(svgFileBasePath+ "aeqd01.svg");
groupByContinent(svgFileBasePath+ "loxim01.svg");
+6 -4
View File
@@ -1,9 +1,11 @@
{
"scripts": {
"pack": "node packmap.js mapdata/loxim01.geojson mapdata/webmercator01.geojson mapdata/aeqd01.geojson"
},
"dependencies": {
"@turf/area": "^7.0.0",
"@turf/turf": "^7.0.0",
"jsdom": "^25.0.0",
"mapshaper": "^0.6.79"
"@turf/area": "^7.3.5",
"@turf/turf": "^7.3.5",
"mapshaper": "^0.7.22"
},
"type": "module"
}
+232
View File
@@ -0,0 +1,232 @@
#!/bin/node
/**
* Packs the generated map data into the compact binary the app reads.
*
* The app used to ship SVG, which spends about four megabytes per projection
* spelling coordinates out in decimal ASCII inside XML, and needed the map
* parsed as a document at startup. Nothing needs a document: the app wants a
* list of places and the outlines that belong to them, which is what this
* writes.
*
* Input is mapshaper's projected GeoJSON, where each feature carries:
* CODE the place it belongs to, e.g. "FRA" or "FRA_CE"
* LEVEL 0 for a country outline, 1 for one of its regions
*
* Output format. All integers are LEB128 varints and coordinates are in grid
* steps, on a canvas MAP_WIDTH user units across:
*
* "BMAP" 0x02
* precision uvarint, grid steps per user unit
* width, height uvarint, in grid steps
* shapeCount uvarint
* per shape:
* codeLength, code uvarint + UTF-8 bytes
* flags 1 byte, bit 0 = is a region
* ringCount uvarint
* per ring:
* pointCount uvarint
* flags 1 byte, bit 0 = even-odd fill
* pointCount pairs of dx, dy zigzag varint, from the previous point,
* starting from the origin on each ring
*/
import { readFileSync, writeFileSync } from 'fs'
/** Canvas width in user units. Stroke widths in the app are relative to it. */
const MAP_WIDTH = 800
/** Blank edge left around the map, as mapshaper's own SVG output used to. */
const MARGIN = 1
/**
* Grid steps per user unit. Coordinates are snapped to this, so it sets how
* fine the map can be; it is written into the header rather than agreed by
* convention, so raising it only means regenerating the assets.
*/
const PRECISION = 20
function uvarint(bytes, value) {
while (value >= 0x80) {
bytes.push((value & 0x7f) | 0x80)
value >>>= 7
}
bytes.push(value)
}
function svarint(bytes, value) {
uvarint(bytes, value < 0 ? -value * 2 - 1 : value * 2)
}
/** Every polygon of a feature, outer rings and holes alike. */
function ringsOf(geometry) {
if (!geometry) return []
if (geometry.type === 'Polygon') return [geometry.coordinates]
if (geometry.type === 'MultiPolygon') return geometry.coordinates
return []
}
/**
* Groups features by the place they belong to. A country outline and its
* regions arrive as separate features; the code is what ties them together.
*/
function collect(features) {
const shapes = []
const byCode = new Map()
for (const feature of features) {
const code = feature.properties?.CODE
if (!code) continue
const polygons = ringsOf(feature.geometry)
if (polygons.length === 0) continue
let shape = byCode.get(code)
if (!shape) {
shape = { code, isState: feature.properties.LEVEL === 1, rings: [], seen: new Set() }
byCode.set(code, shape)
shapes.push(shape)
}
// A polygon with holes only reads correctly under an even-odd fill, which
// is the rule mapshaper's SVG output used to pick for the same reason.
const evenOdd = polygons.some((polygon) => polygon.length > 1)
for (const polygon of polygons) {
for (const ring of polygon) {
if (ring.length >= 3) shape.rings.push({ ring, evenOdd })
}
}
}
return shapes
}
/** Twice the signed area of a closed ring of `x, y` pairs. */
function shoelace(points) {
let sum = 0
const count = points.length / 2
for (let i = 0, j = count - 1; i < count; j = i++) {
sum += points[j * 2] * points[i * 2 + 1] - points[i * 2] * points[j * 2 + 1]
}
return sum
}
/**
* Snaps every ring to the output grid and throws away what that makes
* redundant: points that land on the one before them, and rings left too small
* to enclose anything. The simplification upstream works to about this
* precision, so a good share of the raw points say nothing once rounded.
*/
function quantize(shapes, canvas) {
const kept = []
for (const shape of shapes) {
const seen = new Set()
const rings = []
for (const { ring, evenOdd } of shape.rings) {
const points = []
let px = null
let py = null
for (const [rx, ry] of ring) {
const x = canvas.x(rx)
const y = canvas.y(ry)
if (x === px && y === py) continue
points.push(x, y)
px = x
py = y
}
if (points.length < 6) continue
// Coordinates are integers now, so any ring that still encloses area
// encloses at least half a grid square. One that comes out at exactly
// zero is a sliver the rounding flattened, and it would draw nothing.
if (shoelace(points) === 0) continue
// Antarctica arrives twice over, once per admin level, because its source
// carries no country code and both levels land on ATA. A ring counted
// twice cancels itself out in the even-odd test the app hit tests with,
// which would leave the shape impossible to tap.
const key = points.join(',')
if (seen.has(key)) continue
seen.add(key)
rings.push({ points, evenOdd })
}
if (rings.length > 0) kept.push({ code: shape.code, isState: shape.isState, rings })
}
// By code, which puts a country next to its own regions. They are neighbours
// on the map, so their coordinates are similar, and the compressor in the APK
// gets noticeably more out of the file for it.
return kept.sort((a, b) => (a.code < b.code ? -1 : a.code > b.code ? 1 : 0))
}
/** Projected metres in, user units on a MAP_WIDTH canvas out, y flipped. */
function fitToCanvas(shapes) {
let minX = Infinity
let minY = Infinity
let maxX = -Infinity
let maxY = -Infinity
for (const shape of shapes) {
for (const { ring } of shape.rings) {
for (const [x, y] of ring) {
if (x < minX) minX = x
if (x > maxX) maxX = x
if (y < minY) minY = y
if (y > maxY) maxY = y
}
}
}
const scale = (MAP_WIDTH - 2 * MARGIN) / (maxX - minX)
return {
width: MAP_WIDTH,
height: 2 * MARGIN + (maxY - minY) * scale,
x: (v) => Math.round((MARGIN + (v - minX) * scale) * PRECISION),
y: (v) => Math.round((MARGIN + (maxY - v) * scale) * PRECISION),
}
}
function pack(path) {
const geojson = JSON.parse(readFileSync(path, 'utf8'))
const collected = collect(geojson.features ?? [])
const canvas = fitToCanvas(collected)
const shapes = quantize(collected, canvas)
const bytes = [0x42, 0x4d, 0x41, 0x50, 0x02] // "BMAP" v2
uvarint(bytes, PRECISION)
uvarint(bytes, Math.round(canvas.width * PRECISION))
uvarint(bytes, Math.round(canvas.height * PRECISION))
uvarint(bytes, shapes.length)
let rings = 0
let points = 0
for (const shape of shapes) {
const code = Buffer.from(shape.code, 'utf8')
uvarint(bytes, code.length)
for (const byte of code) bytes.push(byte)
bytes.push(shape.isState ? 1 : 0)
uvarint(bytes, shape.rings.length)
for (const { points: ring, evenOdd } of shape.rings) {
uvarint(bytes, ring.length / 2)
bytes.push(evenOdd ? 1 : 0)
let px = 0
let py = 0
for (let i = 0; i < ring.length; i += 2) {
svarint(bytes, ring[i] - px)
svarint(bytes, ring[i + 1] - py)
px = ring[i]
py = ring[i + 1]
}
rings++
points += ring.length / 2
}
}
const out = path.replace(/^.*\//, './app/src/main/assets/').replace(/\.geojson$/, '.bmap')
writeFileSync(out, Buffer.from(bytes))
console.log(
`${out}: ${shapes.length} shapes, ${rings} rings, ${points} points, ` +
`${canvas.width}x${canvas.height.toFixed(1)}, ${bytes.length} bytes`,
)
}
const inputs = process.argv.slice(2)
if (inputs.length === 0) {
console.error('usage: node packmap.js <map>.geojson ...')
process.exit(1)
}
inputs.forEach(pack)
+3
View File
@@ -6,6 +6,9 @@ pluginManagement {
maven { url 'https://jitpack.io' }
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
+2503 -1687
View File
File diff suppressed because it is too large Load Diff