227 Commits

Author SHA1 Message Date
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 Bot
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 Bot
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 Bot
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 Bot
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
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
fa8f4218be Bump version 2024-11-07 07:50:08 +01:00
f7a166c9f2 Update README.md 2024-11-07 07:37:56 +01:00
8cc871203d Update app/build.gradle 2024-11-07 07:35:19 +01:00
Renovate Bot
4449b5cf8f Lock file maintenance 2024-11-03 01:05:23 +00:00
Renovate Bot
0f1046fcd2 Update plugin com.android.library to v8.7.2 2024-11-02 01:05:24 +00:00
Renovate Bot
395fab45f4 Update plugin com.android.application to v8.7.2 2024-11-01 01:01:19 +00:00
Renovate Bot
0b44d67d61 Lock file maintenance 2024-10-27 00:05:01 +00:00
Renovate Bot
d3242a1304 Lock file maintenance 2024-10-26 00:05:11 +00:00
Renovate Bot
9a451764c2 Update plugin com.android.library to v8.7.1 2024-10-20 00:03:39 +00:00
Renovate Bot
898fe6e862 Update plugin com.android.application to v8.7.1 2024-10-19 00:04:31 +00:00
Renovate Bot
e357b60b14 Update dependency mapshaper to v0.6.101 2024-10-16 00:01:16 +00:00
Renovate Bot
cfb23ed1a0 Lock file maintenance 2024-10-13 00:07:53 +00:00
Renovate Bot
4769901955 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.0.21 2024-10-13 00:04:09 +00:00
Renovate Bot
5c577ec763 Update plugin org.jetbrains.kotlin.android to v2.0.21 2024-10-12 00:04:45 +00:00
Renovate Bot
0724c9a021 Update dependency mapshaper to v0.6.100 2024-10-08 00:01:32 +00:00
Renovate Bot
d031aa4ee4 Update plugin com.android.library to v8.7.0 2024-10-06 00:05:09 +00:00
Renovate Bot
6c7a82475e Lock file maintenance 2024-10-05 00:05:51 +00:00
Renovate Bot
3bc34cef94 Update plugin com.android.application to v8.7.0 2024-10-02 00:02:46 +00:00
Renovate Bot
c0b489ae21 Update dependency jsdom to v25.0.1 2024-09-28 00:05:23 +00:00
Renovate Bot
96f5e01d5f Update dependency gradle to v8.10.2 2024-09-24 00:04:47 +00:00
Renovate Bot
8c5793a75b Update plugin com.android.library to v8.6.1 2024-09-22 00:04:51 +00:00
Renovate Bot
858162ba47 Lock file maintenance 2024-09-22 00:04:39 +00:00
Renovate Bot
191b3c3eff Update plugin com.android.application to v8.6.1 2024-09-21 00:06:11 +00:00
Renovate Bot
7ddc29275d Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.7.3 2024-09-20 00:02:31 +00:00
Renovate Bot
0df7bb7f2c Lock file maintenance 2024-09-14 00:04:49 +00:00
Renovate Bot
43fe9ab868 Update dependency gradle to v8.10.1 2024-09-10 00:03:46 +00:00
43f6acfab3 Update metadata/en-US/full_description.txt 2024-09-10 00:02:33 +02:00
Renovate Bot
126cbfe7b1 Lock file maintenance 2024-09-08 00:02:58 +00:00
Renovate Bot
4aad449a18 Update dependency jsdom to v25 2024-09-08 00:02:30 +00:00
Renovate Bot
ee7fbf4d5a Update plugin com.android.library to v8.6.0 2024-09-07 00:02:40 +00:00
Renovate Bot
2d48cc8dae Update dependency com.android.tools:desugar_jdk_libs_nio to v2.1.2 2024-09-05 00:02:20 +00:00
Renovate Bot
bdca9fe2a1 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.7.2 2024-09-01 00:02:55 +00:00
Renovate Bot
ff83b0abe3 Update plugin com.android.application to v8.6.0 2024-09-01 00:02:42 +00:00
Renovate Bot
dbe93b6884 Update plugin org.jetbrains.kotlin.plugin.serialization to v2.0.20 2024-08-31 00:02:59 +00:00
Renovate Bot
53f2bd5a57 Update dependency jsdom to v24.1.3 2024-08-26 00:01:44 +00:00
Renovate Bot
ff0714f942 Update plugin org.jetbrains.kotlin.android to v2.0.20 2024-08-25 00:02:31 +00:00
Renovate Bot
ed067a616e Update plugin com.mikepenz.aboutlibraries.plugin to v11.2.3 2024-08-24 00:02:58 +00:00
Renovate Bot
e259d401ad Update dependency com.mikepenz:aboutlibraries to v11.2.3 2024-08-24 00:02:43 +00:00
Renovate Bot
9599933c5f Update dependency @turf/area to v7.1.0 2024-08-18 00:03:45 +00:00
Renovate Bot
00b0b6c746 Update dependency gradle to v8.10 2024-08-18 00:03:26 +00:00
Renovate Bot
adbae39d27 Update dependency @turf/turf to v7.1.0 2024-08-17 00:03:25 +00:00
Renovate Bot
8097d25a18 Update dependency mapshaper to v0.6.99 2024-08-17 00:02:52 +00:00
Renovate Bot
fed3e55572 Update plugin com.android.library to v8.5.2 2024-08-11 00:06:15 +00:00
Renovate Bot
d76057f17c Update plugin org.jetbrains.kotlin.plugin.serialization to v2.0.10 2024-08-11 00:02:46 +00:00
Renovate Bot
aab452f798 Update plugin org.jetbrains.kotlin.android to v2.0.10 2024-08-10 00:03:14 +00:00
Renovate Bot
40fd4522ad Update plugin com.android.application to v8.5.2 2024-08-10 00:03:00 +00:00
Renovate Bot
fad65f76ee Lock file maintenance 2024-08-03 00:03:27 +00:00
Renovate Bot
8ad3a26fb0 Lock file maintenance 2024-07-28 00:04:25 +00:00
Renovate Bot
1118ed9b10 Lock file maintenance 2024-07-27 00:06:06 +00:00
Renovate Bot
53db8be5f9 Update dependency jsdom to v24.1.1 2024-07-22 00:01:58 +00:00
Renovate Bot
111a587793 Lock file maintenance 2024-07-20 00:05:10 +00:00
Renovate Bot
cc5ade027a Update plugin com.android.library to v8.5.1 2024-07-14 00:04:55 +00:00
Renovate Bot
026fc7562f Update plugin com.android.application to v8.5.1 2024-07-13 00:05:59 +00:00
Renovate Bot
63a49455e7 Update dependency gradle to v8.9 2024-07-12 00:03:34 +00:00
Renovate Bot
3f59d876a1 Lock file maintenance 2024-07-06 00:05:41 +00:00
Renovate Bot
05b78ed9a9 Update plugin com.mikepenz.aboutlibraries.plugin to v11.2.2 2024-06-30 00:04:54 +00:00
Renovate Bot
7241cdd5a1 Update dependency com.mikepenz:aboutlibraries to v11.2.2 2024-06-30 00:04:40 +00:00
Renovate Bot
8c65aeb2b9 Lock file maintenance 2024-06-29 00:05:12 +00:00
Renovate Bot
4119518ff5 Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.7.1 2024-06-29 00:04:34 +00:00
soraefir
af839915cc Release 1.0 2024-06-28 22:55:49 +02:00
Renovate Bot
9fb11df99e Lock file maintenance 2024-06-22 00:03:50 +00:00
Renovate Bot
9e18619271 Update dependency mapshaper to v0.6.98 2024-06-17 00:02:21 +00:00
Renovate Bot
755c0cd5c2 Update plugin com.android.library to v8.5.0 2024-06-16 00:03:36 +00:00
Renovate Bot
64c5f54eb8 Lock file maintenance 2024-06-16 00:03:25 +00:00
Renovate Bot
18a037421c Update dependency @turf/turf to v7 2024-06-15 00:04:48 +00:00
Renovate Bot
9660c19db7 Update plugin com.android.application to v8.5.0 2024-06-14 00:01:50 +00:00
Renovate Bot
bad189507d Update dependency @turf/area to v7 2024-06-09 07:17:18 +00:00
Renovate Bot
58ad43fffe Update dependency org.jetbrains.kotlinx:kotlinx-serialization-json to v1.7.0 2024-06-08 00:03:38 +00:00
Renovate Bot
54f4bb9138 Update dependency mapshaper to v0.6.97 2024-06-06 00:01:45 +00:00
Renovate Bot
4f578b027d Lock file maintenance 2024-06-02 00:04:06 +00:00
cd4649b329 Merge pull request 'Lock file maintenance' (#87) from renovate/lock-file-maintenance into main
Reviewed-on: #87
2024-06-01 12:26:03 +02:00
2a29237e26 Merge pull request 'Update plugin org.jetbrains.kotlin.plugin.serialization to v2' (#82) from renovate/org.jetbrains.kotlin.plugin.serialization-2.x into main
Reviewed-on: #82
2024-06-01 12:25:37 +02:00
Renovate Bot
17dd26b3b0 Lock file maintenance 2024-06-01 10:15:11 +00:00
Renovate Bot
942f713a2f Update plugin org.jetbrains.kotlin.plugin.serialization to v2 2024-06-01 10:11:20 +00:00
Renovate Bot
df4e01352a Update dependency gradle to v8.8 2024-06-01 10:10:52 +00:00
e7ab816c46 Merge pull request 'Update plugin org.jetbrains.kotlin.android to v2' (#81) from renovate/org.jetbrains.kotlin.android-2.x into main
Reviewed-on: #81
2024-06-01 12:06:23 +02:00
Renovate Bot
d171437e6f Update plugin com.mikepenz.aboutlibraries.plugin to v11.2.1 2024-06-01 00:05:24 +00:00
Renovate Bot
f33711f075 Update dependency com.mikepenz:aboutlibraries to v11.2.1 2024-05-31 00:02:12 +00:00
Renovate Bot
e854b50515 Update plugin org.jetbrains.kotlin.android to v2 2024-05-26 00:04:07 +00:00
Renovate Bot
38d11574b1 Update plugin com.mikepenz.aboutlibraries.plugin to v11.2.0 2024-05-25 13:46:51 +00:00
Renovate Bot
96bb3e9d37 Update plugin com.android.library to v8.4.1 2024-05-25 13:46:41 +00:00
Renovate Bot
fb132f81a6 Update dependency com.mikepenz:aboutlibraries to v11.2.0 2024-05-25 00:05:18 +00:00
Renovate Bot
8bfc9c21eb Update plugin com.android.application to v8.4.1 2024-05-21 00:02:37 +00:00
Renovate Bot
bd7f61e1f7 Lock file maintenance 2024-05-19 00:04:59 +00:00
Renovate Bot
108c805409 Update dependency mapshaper to v0.6.96 2024-05-19 00:04:30 +00:00
73 changed files with 3566 additions and 3662 deletions

View File

@@ -23,7 +23,7 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: set up secrets
run: |
@@ -41,7 +41,7 @@ jobs:
run: git checkout -B "$BRANCH"
- name: set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 17
distribution: "temurin"

2
.gitignore vendored
View File

@@ -20,8 +20,6 @@ app/build/
app/debug/
app/release/
captures/
.externalNativeBuild
.cxx
local.properties
keystore.properties
key.jks

View File

@@ -39,8 +39,11 @@
## 📳 Installation
<div style="display: flex; justify-content: center; align-items: center; flex-direction: row;">
<a href="https://f-droid.org/packages/net.helcel.beans/">
<!--<a href="https://f-droid.org/packages/net.helcel.beans/">
<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" width="206">
</a>-->
<a href="https://apt.izzysoft.de/fdroid/index/apk/net.helcel.beans">
<img width="200" height="80" alt="Izzy Download" src=".github/images/izzy.png">
</a>
<a href="https://github.com/helcel-net/beans/releases/latest">
<img width="200" height="84" alt="APK Download" src=".github/images/apk.png">

View File

@@ -1,21 +1,24 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.24'
id 'com.mikepenz.aboutlibraries.plugin' version '11.1.4'
id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.20'
id 'org.jetbrains.kotlin.plugin.compose' version '2.2.20'
id 'com.mikepenz.aboutlibraries.plugin' version '12.2.4'
}
android {
namespace 'net.helcel.beans'
compileSdk 34
compileSdk 36
defaultConfig {
buildConfigField("String", "APP_NAME", "\"Beans\"")
manifestPlaceholders["APP_NAME"] = "Beans"
applicationId 'net.helcel.beans'
minSdk 28
targetSdk 34
versionCode 1
versionName "1.0"
targetSdk 36
versionCode 4
versionName "1.1a"
}
signingConfigs {
create("release") {
@@ -54,17 +57,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,20 +74,47 @@ android {
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
composeOptions {
kotlinCompilerExtensionVersion = "2.2.20"
}
kotlin {
jvmToolchain(21)
}
lint {
disable 'UsingMaterialAndMaterial3Libraries'
}
}
aboutLibraries {
exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"]
configPath = "config"
library {
exclusionPatterns = [~"androidx.*", ~"com.google.android.*", ~"org.jetbrains.*"]
}
excludeFields = ["generated"]
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.0.4'
implementation 'androidx.compose.material3:material3:1.3.2'
implementation 'androidx.navigation:navigation-compose:2.9.4'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.5'
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.6.3'
implementation 'androidx.compose.ui:ui'
implementation "androidx.compose.material:material:1.9.1"
implementation "androidx.activity:activity-ktx:1.11.0"
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'com.google.android.material:material:1.13.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0'
implementation 'com.caverock:androidsvg-aar:1.4'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation 'com.mikepenz:aboutlibraries:11.1.4'
implementation 'com.mikepenz:aboutlibraries:12.2.4'
implementation 'com.mikepenz:aboutlibraries-compose-m3:12.2.4'
implementation 'com.mikepenz:aboutlibraries-core:12.2.4'
implementation platform('androidx.compose:compose-bom:2025.09.00')
debugImplementation 'androidx.compose.ui:ui-tooling:1.9.1'
}

View File

@@ -1,50 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
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:label="${APP_NAME}"
android:supportsRtl="true"
android:theme="@style/Theme.Beans"
tools:replace="android:allowBackup"
tools:targetApi="31">
<profileable android:shell="true" />
tools:replace="android:allowBackup">
<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>

View File

@@ -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)
}
}

View File

@@ -0,0 +1,64 @@
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.unit.dp
import net.helcel.beans.R
import net.helcel.beans.activity.sub.EditPlaceScreen
import net.helcel.beans.countries.World
@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)
}
}
}
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,123 @@
package net.helcel.beans.activity
import android.graphics.drawable.PictureDrawable
import android.os.Bundle
import android.widget.ImageView
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.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.DateRange
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.caverock.androidsvg.RenderOptions
import com.github.chrisbanes.photoview.PhotoView
import net.helcel.beans.BuildConfig
import net.helcel.beans.countries.GeoLocImporter
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 MainScreen : ComponentActivity() {
private lateinit var psvg: SVGWrapper
private lateinit var css: CSSWrapper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
actionBar?.hide()
Settings.start(this)
GeoLocImporter.importStates(this)
Data.loadData(this, Int.MIN_VALUE)
setContent {
SysTheme {
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colors.background)) {
AppNavHost(psvg, css)
}
}
}
refreshProjection()
}
@Composable
fun AppNavHost(psvg: SVGWrapper, css: CSSWrapper) {
val navController = rememberNavController()
NavHost(navController, startDestination = "main") {
composable("main") { MainScreenC(psvg,css, navController) }
composable("settings") { SettingsMainScreen { navController.navigate("main")} }
composable("edit") { EditScreen { navController.navigate("main") } }
composable("stats") { StatsScreen { navController.navigate("main") } }
}
}
@Composable
fun MainScreenC(psvg: SVGWrapper,css: CSSWrapper, 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.DateRange, contentDescription = "Stats")
}
IconButton(onClick = { nav.navigate("settings") }) {
Icon(Icons.Default.Settings, contentDescription = "Settings")
}
}
)
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
MapScreen(psvg, css)
}
}
}
}
@Composable
fun MapScreen(psvg: SVGWrapper, css: CSSWrapper) {
Box {
val opt: RenderOptions = RenderOptions.create()
opt.css(css.get())
val drawable = remember(psvg, css) {
PictureDrawable(psvg.get()?.renderToPicture(opt))
}
AndroidView(factory = { ctx ->
PhotoView(ctx).apply {
setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null)
setImageDrawable(drawable)
scaleType = ImageView.ScaleType.FIT_CENTER
}
}, modifier = Modifier.fillMaxSize())
}
}
fun refreshProjection() {
psvg = SVGWrapper(this)
css = CSSWrapper(this)
}
}

View File

@@ -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)
}
}

View File

@@ -0,0 +1,381 @@
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.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.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.preference.PreferenceManager
import net.helcel.beans.R
import net.helcel.beans.countries.GeoLocImporter
import net.helcel.beans.helper.Settings
import androidx.core.content.edit
import androidx.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.activity.sub.AboutScreen
import net.helcel.beans.activity.sub.EditPlaceColorDialog
import net.helcel.beans.activity.sub.EditPlaceDialog
import net.helcel.beans.activity.sub.LicenseScreen
import net.helcel.beans.helper.Data
@Composable
fun SysTheme(
content: @Composable () -> Unit
) {
val context = LocalContext.current
val prefs = PreferenceManager.getDefaultSharedPreferences(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,
)
MaterialTheme(
colors = m2colors,
content = content
)
}
@Composable
fun settingsNav(): NavHostController {
val navController = rememberNavController()
NavHost(navController, startDestination= "settings"){
composable("settings"){SettingsScreen(navController)}
composable("licenses"){ LicenseScreen() }
composable("about"){ AboutScreen() }
}
return navController
}
@Composable
fun SettingsMainScreen(onExit: ()->Unit = {}) {
var nav: NavHostController? = null
SysTheme {
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.action_settings)) },
navigationIcon = {
IconButton(onClick = {
if(nav!=null && !nav!!.popBackStack())
onExit()
}) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = null
)
}
}
)
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
nav = settingsNav()
}
}
}
}
@Preview
@Composable
fun SettingsScreen(navController: NavHostController = settingsNav()) {
val context = LocalContext.current
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var showEdit by remember { mutableStateOf(false) }
var theme by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_theme), context.getString(R.string.system))!!) }
var projection by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_projection), "default")!!) }
var groups by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_group), context.getString(R.string.off))!!) }
if(showEdit)
EditPlaceDialog(true) {
showEdit = false
val g = Data.selected_group
if (it && g != null)
Data.visits.reassignAllVisitedToGroup(g.key)
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.background(MaterialTheme.colors.background)
) {
item {
Text(
"Theme", style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
)
MultiPreference(arrayOf(stringResource(R.string.system),stringResource(R.string.light),stringResource(R.string.dark)), theme) { newTheme ->
theme = newTheme
prefs.edit { putString(context.getString(R.string.key_theme), newTheme) }
}
HorizontalDivider()
}
item {
Text(
"Map Projection",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
MultiPreference(arrayOf(stringResource(R.string.mercator), stringResource(R.string.azimuthalequidistant)), projection) { newProj ->
projection = newProj
prefs.edit { putString(context.getString(R.string.key_projection), newProj) }
Settings.refreshProjection()
}
HorizontalDivider()
}
item {
Text(
"Groups",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier.padding(top = 16.dp)
)
var showDialog by remember{mutableStateOf(false)}
if(showDialog){
EditPlaceColorDialog(
deleteMode = true,
onDismiss = {
val g = Data.selected_group
if (g != null)
Data.visits.reassignAllVisitedToGroup(g.key)
showDialog = false
})
}
MultiPreference(
arrayOf(stringResource(R.string.on), stringResource(R.string.off)),
groups
) { key ->
if (key == context.getString(R.string.off)) {
showDialog=true
}
groups = key
prefs.edit { putString(context.getString(R.string.key_group), key) }
}
HorizontalDivider()
}
item {
Text(
text = "Regional",
style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onBackground,
modifier = Modifier
.padding(top = 16.dp)
.clickable(onClick = {}),
)
RegionalScreen()
HorizontalDivider()
}
item{
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri -> Data.doImport(context, uri) }
)
Row(
modifier = Modifier.fillMaxWidth()
) {
Button(onClick = {
launcher.launch(arrayOf("*/*"))
}, modifier = Modifier
.fillMaxWidth(fraction = 0.4f)
.padding(vertical = 8.dp)) {
Text("Import")
}
Spacer(
modifier = Modifier.fillMaxWidth(0.4f)
)
Button(onClick = {
Data.doExport(context)
}, modifier = Modifier
.fillMaxWidth(fraction = 1f)
.padding(vertical = 8.dp)) {
Text("Export")
}
}
HorizontalDivider()
}
item {
PreferenceButton("Licenses") {
if (navController.currentDestination?.route != "licenses")
navController.navigate("licenses")
}
PreferenceButton("About") {
if (navController.currentDestination?.route != "about")
navController.navigate("about")
}
}
}
}
@Composable
fun RegionalScreen() {
val context = LocalContext.current
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var selected by remember { mutableStateOf(prefs.getString(context.getString(R.string.key_regional),context.getString(R.string.off))!!)}
var regional by remember{ mutableStateOf(prefs.getString(context.getString(R.string.key_regional), context.getString(R.string.off))!!)}
var showDialog by remember{mutableStateOf(false)}
var showLoad by remember{mutableStateOf(false)}
if(showDialog)
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= selected
prefs.edit {
putString(
context.getString(R.string.key_regional),
regional
)
}
showDialog=false
}){
Text(stringResource(R.string.ok))
}
}
},
onDismissRequest = { showDialog=false }
)
if(showLoad){
Dialog(
content = {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
strokeWidth = 4.dp,
modifier = Modifier.size(50.dp)
)
},
onDismissRequest = {}
)
}
val scope = rememberCoroutineScope()
MultiPreference(arrayOf(stringResource(R.string.on),stringResource(R.string.off)),regional) { key ->
when (key) {
context.getString(R.string.off) -> { showDialog=true
selected=key
}
context.getString(R.string.on) -> {
regional = key
prefs.edit { putString(context.getString(R.string.key_regional), key) }
showLoad=true
scope.launch {
withContext(Dispatchers.IO) {
GeoLocImporter.importStates(context, true)
}
showLoad = false
}
}
}
}
}
@Composable
fun MultiPreference(list: Array<String>, selected: String, onSelected: (String) -> Unit) {
Column(Modifier.padding(2.dp)) {
list.map { value ->
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.height(36.dp)
.clickable { onSelected(value) }) {
RadioButton(selected = selected == value, onClick = { onSelected(value) })
Text(
value, modifier = Modifier.padding(start = 8.dp),
color = MaterialTheme.colors.onBackground,
)
}
}
}
}
@Composable
fun PreferenceButton(text: String, onClick: () -> Unit) {
Button(onClick = onClick, modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)) {
Text(text)
}
}

View File

@@ -1,57 +1,159 @@
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.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.GeoLoc.LocType
import net.helcel.beans.databinding.ActivityStatBinding
import net.helcel.beans.helper.Settings
import net.helcel.beans.helper.Theme.createActionBar
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.Settings.isRegional
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 (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_edit), 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 visited = remember(group, activeMode) {
Data.visits.getVisitedByValue(group.key)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
finish()
return super.onOptionsItemSelected(item)
val count = when (activeMode) {
LocType.WORLD -> World.WWW.children.filter { it.code in visited }.size
LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.size
LocType.STATE -> World.WWW.children.flatMap { itc->itc.children.flatMap { it.children.filter { it.code in visited } } }.size
else -> 0
}
val area = when (activeMode) {
LocType.WORLD -> World.WWW.children.filter { it.code in visited }.sumOf { it.area }
LocType.COUNTRY -> World.WWW.children.flatMap { it.children.filter { c -> c.code in visited } }.sumOf { it.area }
LocType.STATE -> World.WWW.children.flatMap { it.children.flatMap { it.children.filter { it.code in visited } } }.sumOf { it.area }
else -> 0
}
val displayValue = if (countMode) count.toString() else context.getString(R.string.number_with_unit, area, "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)
)
}
}

View File

@@ -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)
}
}
}

View File

@@ -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
}
}
}
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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)}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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)
)
}
}

View File

@@ -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,
)
)
}

View File

@@ -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)
)
}
}
}
},
)
}

View File

@@ -0,0 +1,218 @@
package net.helcel.beans.activity.sub
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)
}
fun syncVisited(loc: GeoLoc?=World.WWW){
loc?.children?.forEach { tt ->
tt.children.forEach {itc->
if(Data.visits.getVisited(itc) in listOf(AUTO_GROUP,NO_GROUP)) {
if(itc.children.any { itcc -> Data.visits.getVisited(itcc) != NO_GROUP })
Data.visits.setVisited(itc, AUTO_GROUP)
else
Data.visits.setVisited(itc, NO_GROUP)
}
}
if(Data.visits.getVisited(tt) in listOf(AUTO_GROUP,NO_GROUP)) {
if(tt.children.any { itc -> Data.visits.getVisited(itc) != NO_GROUP })
Data.visits.setVisited(tt, AUTO_GROUP)
else
Data.visits.setVisited(tt, NO_GROUP)
}
}
}
@Composable
fun EditPlaceScreen(loc: GeoLoc, onExit:()->Unit={}) {
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 {
syncVisited()
}
BackHandler {
if (tabs.size > 1) tabs.removeAt(tabs.lastIndex)
else onExit()
}
if(showEdit)
EditPlaceDialog(false) {
showEdit = false
if (it) {
Data.visits.setVisited(Data.selected_geoloc, NO_GROUP)
Data.saveData()
if (Data.selected_geoloc!=null && Data.selected_geoloc!!.children.any { itc-> Data.visits.getVisited(itc) != NO_GROUP }) {
Data.clearing_geoloc = Data.selected_geoloc
}
}
if (Data.selected_group != null && Data.selected_geoloc != null) {
Data.visits.setVisited(Data.selected_geoloc, Data.selected_group!!.key)
Data.saveData()
}
Data.selected_geoloc = null
Data.selected_group = null
}
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)
}
}, {
Data.selected_geoloc = loc
if (Data.groups.size() == 1 && Settings.isSingleGroup(ctx)) {
Data.visits.setVisited(Data.selected_geoloc,
if (it != ToggleableState.On) Data.groups.getUniqueEntry()!!.key
else if(Data.selected_geoloc?.children?.any{ itc->
Data.visits.getVisited(itc)!= NO_GROUP } == true) AUTO_GROUP
else NO_GROUP
)
Data.selected_group = null
} else {
Data.selected_group = null
showEdit=true
}
})
}
}
}
}
@Composable
fun GeoLocRow(
loc: GeoLoc,
onClick: () -> Unit,
onCheckedChange: (ToggleableState) -> 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= { onCheckedChange(checked) },
colors = CheckboxDefaults.colors(
checkedColor = color,
),
modifier = Modifier.size(24.dp)
)
}
Spacer(modifier = Modifier
.height(2.dp)
.fillMaxWidth()
.background(MaterialTheme.colors.onBackground))
}

View File

@@ -0,0 +1,43 @@
package net.helcel.beans.activity.sub
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.mikepenz.aboutlibraries.ui.compose.DefaultChipColors
import com.mikepenz.aboutlibraries.ui.compose.DefaultLibraryColors
import com.mikepenz.aboutlibraries.ui.compose.android.rememberLibraries
import com.mikepenz.aboutlibraries.ui.compose.m3.LibrariesContainer
import net.helcel.beans.R
import net.helcel.beans.activity.SysTheme
@Preview
@Composable
fun LicenseScreen() {
val libraries = rememberLibraries(R.raw.aboutlibraries)
SysTheme {
LibrariesContainer(
libraries = libraries.value,
modifier = Modifier.fillMaxSize(),
colors = DefaultLibraryColors(
backgroundColor = MaterialTheme.colors.background,
contentColor = MaterialTheme.colors.onBackground,
licenseChipColors = DefaultChipColors(
containerColor = MaterialTheme.colors.primary,
contentColor = MaterialTheme.colors.onPrimary,
),
versionChipColors = DefaultChipColors(
containerColor = MaterialTheme.colors.secondary,
contentColor = MaterialTheme.colors.onSecondary,
),
fundingChipColors = DefaultChipColors(
containerColor = MaterialTheme.colors.secondary,
contentColor = MaterialTheme.colors.onSecondary,
),
dialogConfirmButtonColor = MaterialTheme.colors.primary,
)
)
}
}

View File

@@ -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),

View File

@@ -12,4 +12,4 @@ interface GeoLoc {
val type: LocType
val children: Set<GeoLoc>
}
}

View File

@@ -1,13 +1,21 @@
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())
@@ -33,7 +41,8 @@ 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)))
groups.setGroup(DEFAULT_GROUP, "Visited",
ContextCompat.getColor(ctx, R.color.blue).toDrawable())
saveData()
}
}
@@ -41,9 +50,73 @@ object Data {
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 {
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)
}
}

View File

@@ -1,5 +0,0 @@
package net.helcel.beans.helper
interface DialogCloser {
fun onDialogDismiss(clear: Boolean)
}

View File

@@ -2,15 +2,16 @@ 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.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,20 +21,23 @@ 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>) {
@kotlinx.serialization.Transient
private val _groupsFlow = MutableStateFlow<List<Group>>(grps.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)
_groupsFlow.value = grps.values.toList()
}
fun deleteGroup(key: Int) {
grps.remove(key)
}
fun deleteAllExcept(grp: Int) {
val keysToDelete = grps.keys.filter { it != grp }
keysToDelete.forEach { grps.remove(it) }
_groupsFlow.value = grps.values.toList()
}
fun getGroupFromKey(key: Int): Group {
@@ -60,6 +64,7 @@ class Groups(val id: Int, private val grps: HashMap<Int, Group>) {
}
fun getGroupFromPos(pos: Int): Pair<Int, Group> {
if(grps.keys.isEmpty()) return Pair(NO_GROUP,Group(NO_GROUP,"-"))
val key = grps.keys.toList()[pos]
return Pair(key, getGroupFromKey(key))
}
@@ -74,9 +79,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)

View File

@@ -4,19 +4,15 @@ 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
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))
)
}
fun isSingleGroup(ctx: Context): Boolean {
@@ -41,7 +37,7 @@ object Settings {
}
fun refreshProjection(): Boolean {
mainActivity.refreshProjection()
(mainActivity).refreshProjection()
return true
}

View File

@@ -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) {

View File

@@ -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,9 +13,17 @@ 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)
return
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
this[key.code] = b
}
locs[key.code] = b
}
@@ -21,6 +31,9 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
val keysToDelete = locs
.filter { it.value == key }
.map { it.key }
_visitsFlow.value = _visitsFlow.value.toMutableMap().apply {
keysToDelete.forEach { this.remove(it)}
}
keysToDelete.forEach {
locs.remove(it)
}
@@ -53,6 +66,7 @@ class Visits(val id: Int, private val locs: HashMap<String, Int>) {
keys.forEach {
locs[it] = group
}
_visitsFlow.value = locs
}
@OptIn(ExperimentalSerializationApi::class)

View File

@@ -1,6 +1,10 @@
package net.helcel.beans.svg
import android.content.Context
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.toArgb
import androidx.core.graphics.drawable.toDrawable
import net.helcel.beans.countries.World
import net.helcel.beans.helper.AUTO_GROUP
import net.helcel.beans.helper.Data.groups
@@ -8,15 +12,10 @@ 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" }
@@ -24,18 +23,22 @@ class CSSWrapper(private val ctx: Context) {
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;}"
@Composable
fun getBaseColors() : Pair<String, String> {
val colorForeground = colorToHex6(MaterialTheme.colors.onBackground.toArgb().toDrawable())
val colorBackground = colorToHex6(MaterialTheme.colors.background.toArgb().toDrawable())
return Pair(colorForeground, colorBackground)
}
private var customCSS: String = ""
init {
refresh()
}
private fun refresh() {
val id = if (Settings.isRegional(ctx)) "1" else "2"
customCSS = visits.getVisitedByValue().map { (k, v) ->
@@ -47,20 +50,24 @@ class CSSWrapper(private val ctx: Context) {
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
)
if (k == AUTO_GROUP) colorToHex6(groups.getGroupFromPos(0).second.color)
else colorToHex6(groups.getGroupFromKey(k).color)
};}"
}.joinToString("")
}
@Composable
fun get(): String {
val (colorForeground,colorBackground) = getBaseColors()
refresh()
return if (Settings.isRegional(ctx)) {
val countryRegionalCSS: String =
"svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.01;}" +
"$continents,$countries{fill:none;stroke:$colorBackground;stroke-width:0.1;}"
countryRegionalCSS + customCSS
} else {
val countryOnlyCSS: String =
"svg{fill:$colorForeground;stroke:$colorBackground;stroke-width:0.1;}" +
"${regional}{display:none;}"
countryOnlyCSS + customCSS
}
}

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

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>

View File

@@ -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>

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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

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>

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>

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>

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.0</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>
@@ -18,16 +18,17 @@
<string name="key_regional">Regional</string>
<string name="about">About</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="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="foss_licenses">Free and open source dependencies and licenses</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="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 +40,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>

View File

@@ -1,32 +0,0 @@
<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>
<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>

View File

@@ -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>

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.4.0' apply false
id 'com.android.library' version '8.4.0' apply false
id 'org.jetbrains.kotlin.android' version '1.9.24' apply false
id 'com.android.application' version '8.13.0' apply false
id 'com.android.library' version '8.13.0' apply false
id 'org.jetbrains.kotlin.android' version '2.2.20' apply false
}

Binary file not shown.

View File

@@ -1,8 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionSha256Sum=544c35d6bd849ae8a5ed0bcea39ba677dc40f49df7d1835561582da2009b961d

15
gradlew vendored
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.
@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/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/.
@@ -84,7 +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 "${APP_HOME:-./}" > /dev/null && pwd -P ) || 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
@@ -112,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.
@@ -170,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" )
@@ -203,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.

5
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -68,11 +70,10 @@ goto fail
: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 %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -2,10 +2,10 @@ Beans is a scratchmap of the world for Android.
Keep track of your discovery of the world on a colorful visual map.
Color a map of places based on custom labels
Country/State based coloring
Single/Multi color modes
Different map projections available
Small & Fast
Statistics (WIP)
100% Free and Open Source software, with no proprietary dependencies
* Color a map of places based on custom labels
* Country/State based coloring
* Single/Multi color modes
* Different map projections available
* Small & Fast
* Statistics (WIP)
* 100% Free and Open Source software, with no proprietary dependencies

View File

@@ -1,8 +1,8 @@
{
"dependencies": {
"@turf/area": "^6.5.0",
"@turf/turf": "^6.5.0",
"jsdom": "^24.0.0",
"@turf/area": "^7.0.0",
"@turf/turf": "^7.0.0",
"jsdom": "^27.0.0",
"mapshaper": "^0.6.79"
},
"type": "module"

3347
yarn.lock

File diff suppressed because it is too large Load Diff