Compare commits
447 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9df24081d4 | ||
|
|
255001b768 | ||
|
|
55af84b7de | ||
|
|
4f6f09dc83 | ||
|
|
7f6d0c1391 | ||
|
|
96b60d0f49 | ||
|
|
2824f0b5c3 | ||
|
|
af0921ed20 | ||
|
|
a5b55479cb | ||
|
|
a93bacd9b3 | ||
|
|
9d7278e0e2 | ||
|
|
f6d9c615a0 | ||
|
|
a8ee3f5b7d | ||
|
|
826b4f89f1 | ||
|
|
5675d065e3 | ||
|
|
3e3531551d | ||
|
|
5d7d881729 | ||
|
|
23c73e3d7e | ||
|
|
7835aa8d78 | ||
|
|
f06b712090 | ||
|
|
317695954d | ||
|
|
24cfd1c10b | ||
|
|
775faa2f55 | ||
|
|
08bd2bdf5a | ||
|
|
90254915e3 | ||
|
|
b7f56ecff4 | ||
|
|
fa3910d3c8 | ||
|
|
4500c55560 | ||
|
|
a493e1a548 | ||
|
|
ddaab42e45 | ||
|
|
9f50341ab7 | ||
|
|
9966b44a76 | ||
|
|
d44b2206d2 | ||
|
|
f61082f491 | ||
|
|
f58d96c939 | ||
|
|
29aedfa3d9 | ||
|
|
8331f92f10 | ||
|
|
123680d3e8 | ||
|
|
0f6b45d745 | ||
|
|
69faa94f18 | ||
|
|
70805b7960 | ||
|
|
56453b0658 | ||
|
|
975d95e37e | ||
|
|
ba34cd016a | ||
|
|
590b16aa49 | ||
|
|
5fe8d0cab4 | ||
|
|
9d7b181410 | ||
|
|
128532aac6 | ||
|
|
486854f56c | ||
|
|
1e30db5cd1 | ||
|
|
aad386ab04 | ||
|
|
e2bcf8d1cd | ||
|
|
f56fad1282 | ||
|
|
adb4d938cc | ||
|
|
b773f65912 | ||
|
|
de335b18d8 | ||
|
|
6c8380b8ce | ||
|
|
81afdca19d | ||
|
|
14e03ba6dd | ||
|
|
abe12b45c3 | ||
|
|
23387ae371 | ||
|
|
25f466b6d7 | ||
|
|
6692b21bf9 | ||
|
|
5959fe8be4 | ||
|
|
00f4c13fcc | ||
|
|
47054d470b | ||
|
|
d10192cae1 | ||
|
|
e1b90955c3 | ||
|
|
d249bf47c7 | ||
|
|
738dcd5f8d | ||
|
|
ad4f32ec32 | ||
|
|
4d03107ae7 | ||
|
|
0e93e310bf | ||
|
|
6cb8940696 | ||
|
|
dad30eb51e | ||
|
|
abf6a2b933 | ||
|
|
2c5685d918 | ||
|
|
b61e57b022 | ||
|
|
e6428cc8db | ||
|
|
6302006a35 | ||
|
|
ab93577a98 | ||
|
|
98b695ed4b | ||
|
|
ed8cb50b08 | ||
|
|
88d89c2760 | ||
|
|
80c25cb416 | ||
|
|
81c8e54dd2 | ||
|
|
8c01ee1581 | ||
|
|
e8db5acfbf | ||
|
|
f6bb3c03ba | ||
|
|
134f3856b9 | ||
|
|
4974cc6d83 | ||
|
|
edd072b83a | ||
|
|
35ddda5bfe | ||
|
|
8b241e3f6f | ||
|
|
b3c5fe788d | ||
|
|
6fd737f6e9 | ||
|
|
08cd4eb849 | ||
|
|
ff75594b37 | ||
|
|
2576bc4854 | ||
|
|
b2c29b647b | ||
|
|
2167a63321 | ||
|
|
fb0a2cfa1c | ||
|
|
07be77c573 | ||
|
|
ae0a84db4c | ||
|
|
dc5ffb148d | ||
|
|
066b7c085e | ||
|
|
4ae16df064 | ||
|
|
17a40127e6 | ||
|
|
31ad748796 | ||
|
|
fe4db38798 | ||
|
|
6c2243078b | ||
|
|
71f1ee8d7b | ||
|
|
ab0c37cb82 | ||
|
|
65189cd798 | ||
|
|
630178bfcf | ||
|
|
bcee975124 | ||
|
|
04fc17d73c | ||
|
|
139c02ef70 | ||
|
|
88a8520f27 | ||
|
|
4f3157a0ac | ||
|
|
17d57729b3 | ||
|
|
1f3df2e0bf | ||
|
|
e2e95ce85d | ||
|
|
d79b554dcc | ||
|
|
98e91ea3db | ||
|
|
b8c8245978 | ||
|
|
fd1f05888a | ||
|
|
2e4167689d | ||
|
|
8a2ad55dd6 | ||
|
|
44ce0cfaea | ||
|
|
70f964549e | ||
|
|
c045eed41a | ||
|
|
3ded108c3c | ||
|
|
b3eb1e31e8 | ||
|
|
7eeb10faca | ||
|
|
4208e1a4b5 | ||
|
|
54004f14b5 | ||
|
|
8eabff4888 | ||
|
|
d5b5337aeb | ||
|
|
913d8a00cf | ||
|
|
fc5003cd31 | ||
|
|
ff96e49ead | ||
|
|
36bd74e091 | ||
|
|
3d0dc16f49 | ||
|
|
41b374350b | ||
|
|
fc72044b82 | ||
|
|
e96fcd4a88 | ||
|
|
36f34bde1e | ||
|
|
624c5d8f92 | ||
|
|
7fcb187dda | ||
|
|
7188a2aa64 | ||
|
|
cf6c662832 | ||
|
|
4ceef7997d | ||
|
|
3f79bdd125 | ||
|
|
fb279f90c5 | ||
|
|
6f35ced260 | ||
|
|
c967bab524 | ||
|
|
6bf80e2b49 | ||
|
|
d97cb4b9fb | ||
|
|
17eaeb99da | ||
|
|
beebbe1c1b | ||
|
|
0a2bbd5fb4 | ||
|
|
7f1f4b67a1 | ||
|
|
d5e29a5112 | ||
|
|
77f478c9e0 | ||
|
|
1008a2c2cd | ||
|
|
2219e2fe27 | ||
|
|
8ce145a9af | ||
|
|
b799dae28b | ||
|
|
07a482a6b6 | ||
|
|
4f1253b201 | ||
|
|
8bc4a7ae40 | ||
|
|
d686becfe4 | ||
|
|
a686c51b32 | ||
|
|
382ead9e08 | ||
|
|
2da7ea4c05 | ||
|
|
20c4274c55 | ||
|
|
748212189f | ||
|
|
d86a49beb7 | ||
|
|
f8b1a20d1a | ||
|
|
14edb6f0cd | ||
|
|
7726088f91 | ||
|
|
cbc7c5a6d8 | ||
|
|
d510d81914 | ||
|
|
9f5abd6c91 | ||
|
|
966f62ac3d | ||
|
|
91caf40bdb | ||
|
|
72c0293365 | ||
|
|
ca9dc9629f | ||
|
|
438e529257 | ||
|
|
5f69123d89 | ||
|
|
cf421b52a8 | ||
|
|
1b049d35b8 | ||
|
|
f6690a3566 | ||
|
|
cc97020216 | ||
|
|
0e1e3ba46e | ||
|
|
657c209827 | ||
|
|
6ec44bb526 | ||
|
|
0943505d90 | ||
|
|
f155f7615f | ||
|
|
e8850575f2 | ||
|
|
d1c4d0a621 | ||
|
|
ecf27abdc5 | ||
|
|
5f5142baa6 | ||
|
|
fa53a9fc5a | ||
|
|
9a0a7b4e5f | ||
|
|
1a43703db5 | ||
|
|
459589c51f | ||
|
|
9393fe7380 | ||
|
|
f62bd1c3c4 | ||
|
|
27ff992d97 | ||
|
|
cb4b8a7d5f | ||
|
|
671424b202 | ||
|
|
ce1a7da1f5 | ||
|
|
236aefa34d | ||
|
|
d179490891 | ||
|
|
91e4cb3f14 | ||
|
|
37f02f52e9 | ||
|
|
01f1ffb646 | ||
|
|
131c93c86b | ||
|
|
4dd1a648ce | ||
|
|
91df749bc4 | ||
|
|
20b04e55fb | ||
|
|
a94ad9e8c2 | ||
|
|
807ff50612 | ||
|
|
d46ff39c2b | ||
|
|
199de04562 | ||
|
|
f2a18b7677 | ||
|
|
aab816db32 | ||
|
|
66ad6b9931 | ||
|
|
beeefb2be1 | ||
|
|
110c418d01 | ||
|
|
1296e66902 | ||
|
|
31d969e071 | ||
|
|
32681f6ea8 | ||
|
|
d77f67aa91 | ||
|
|
be071cfa3a | ||
|
|
e098c70684 | ||
|
|
91509f5846 | ||
|
|
454cc44793 | ||
|
|
1baf94d788 | ||
|
|
b0d9317f73 | ||
|
|
9b80f03993 | ||
|
|
af0fd8bf69 | ||
|
|
f76b19e818 | ||
|
|
02b717c612 | ||
|
|
e29d40bca2 | ||
|
|
7f8403cfb4 | ||
|
|
d5168f12c6 | ||
|
|
9b94bbf098 | ||
|
|
34c83c2253 | ||
|
|
16cfa3b37b | ||
|
|
498dc63f91 | ||
|
|
c48330dc35 | ||
|
|
ca8abd9b12 | ||
|
|
72b2b34af3 | ||
|
|
6a7b7a7d39 | ||
|
|
c1af372a06 | ||
|
|
7946663299 | ||
|
|
232aecfe3b | ||
|
|
ac1db7f10d | ||
|
|
ef99441844 | ||
|
|
c4e3534682 | ||
|
|
d335d7cab0 | ||
|
|
f7c3faa7bd | ||
|
|
1338e2306e | ||
|
|
83a2b42408 | ||
|
|
0ce5938f5b | ||
|
|
5ab50e04ae | ||
|
|
ee0fd4e8d8 | ||
|
|
369b7d9410 | ||
|
|
c9a0b270cd | ||
|
|
c8aa64fa7c | ||
|
|
d5b18bd6fb | ||
|
|
eb7ade5e48 | ||
|
|
a59444e24b | ||
|
|
c6b7157d5b | ||
|
|
3d9a622f09 | ||
|
|
50bb245777 | ||
|
|
128cebfc20 | ||
|
|
c106bc40cc | ||
|
|
52af10d549 | ||
|
|
8c03d1e9eb | ||
|
|
f1d49e317d | ||
|
|
f3be8ed97b | ||
|
|
258edb87c9 | ||
|
|
703dd40879 | ||
|
|
18f7ed19e0 | ||
|
|
3d30e746a0 | ||
|
|
51d085dbb0 | ||
|
|
66b4627d21 | ||
|
|
99263e9a66 | ||
|
|
999c5b0836 | ||
|
|
52aa1b198d | ||
|
|
36a702a6f4 | ||
|
|
512be8b0c9 | ||
|
|
3dabd07969 | ||
|
|
29bec90001 | ||
|
|
1191ac732b | ||
|
|
a80fcebe94 | ||
|
|
35b21b10e3 | ||
|
|
22d8f9a628 | ||
|
|
42e8d999d3 | ||
|
|
cf4d18e23e | ||
|
|
bfa1c45ae6 | ||
|
|
6e888499c4 | ||
|
|
9223b70eba | ||
|
|
fe55855876 | ||
|
|
6aa8a3d7a2 | ||
|
|
887702b729 | ||
|
|
0417c4f1ae | ||
|
|
0b95785f49 | ||
|
|
2772e9ad4d | ||
|
|
8a16fa3a5c | ||
|
|
84d3127675 | ||
|
|
e684fbc0dc | ||
|
|
bb92d26be9 | ||
|
|
f74bb8e4a5 | ||
|
|
5d72be8e87 | ||
|
|
04e6f63cd7 | ||
|
|
ffb0b77f37 | ||
|
|
9d621c3149 | ||
|
|
7126c3c67c | ||
|
|
62197f99cb | ||
|
|
db68452f55 | ||
|
|
9ec5010495 | ||
|
|
5978b90da2 | ||
|
|
223d9d394f | ||
|
|
38b82abc48 | ||
|
|
aade4ec488 | ||
|
|
38a02f8304 | ||
|
|
8f7e1c5629 | ||
|
|
0be90d8801 | ||
|
|
4ca9cc68cb | ||
|
|
62e9acf9be | ||
|
|
6cb682f065 | ||
|
|
4cfd5c8ef2 | ||
|
|
24bf66ddbe | ||
|
|
a0b0339c8b | ||
|
|
2c9081b313 | ||
|
|
bd245801b0 | ||
|
|
11dac62b94 | ||
|
|
a8bac7875a | ||
|
|
dbba00b51b | ||
|
|
90cddce54c | ||
|
|
f0f6c08610 | ||
|
|
a2fe9a06c5 | ||
|
|
cb79f17c23 | ||
|
|
0009895537 | ||
|
|
df705670b1 | ||
|
|
c616e9fdbd | ||
|
|
c70a092d99 | ||
|
|
34fee47c08 | ||
|
|
bf97a14fe3 | ||
|
|
60d4d56f80 | ||
|
|
8bf33c7384 | ||
|
|
595e6e9a8f | ||
|
|
9efbdfc046 | ||
|
|
e1d4b6bcc5 | ||
|
|
a6db74488e | ||
|
|
821f5d61b5 | ||
|
|
f83ac17c83 | ||
|
|
3519c7f699 | ||
|
|
78d9706cb7 | ||
|
|
a593a8054b | ||
|
|
9556be6b85 | ||
|
|
e8669f8a3d | ||
|
|
6a887ee1e4 | ||
|
|
6dbaaa3099 | ||
|
|
7f9242da1e | ||
|
|
2c3151089f | ||
|
|
1ee388126f | ||
|
|
964cecdf66 | ||
|
|
7141eb5013 | ||
|
|
d7fcb35a4e | ||
|
|
56348905a6 | ||
|
|
3336faa953 | ||
|
|
e22e1521a4 | ||
|
|
e974acac4e | ||
|
|
8a13bfcd9e | ||
|
|
1e04d6e98a | ||
|
|
a0045fc6bb | ||
|
|
ec10b51387 | ||
|
|
b054464280 | ||
|
|
1a32159526 | ||
|
|
c6cc7102e6 | ||
|
|
6a5dc93fd8 | ||
|
|
a85966bb1d | ||
|
|
bf3c401c37 | ||
|
|
4da7e0b50d | ||
|
|
d78f2f08cb | ||
|
|
d2952766e4 | ||
|
|
40503b6bd2 | ||
|
|
e875e0ee42 | ||
|
|
6f9ea6c6e3 | ||
|
|
a79d013179 | ||
|
|
4b75389a31 | ||
|
|
1039251d63 | ||
|
|
2cd9e9d642 | ||
|
|
7d495468ea | ||
|
|
e47a82a4bc | ||
|
|
87421e450a | ||
|
|
479917fad1 | ||
|
|
dfaf841160 | ||
|
|
c18ea5b15d | ||
|
|
62116473c8 | ||
|
|
bc8106bd81 | ||
|
|
7bd89b9ecb | ||
|
|
898b61945e | ||
|
|
38e022b547 | ||
|
|
b8c438503c | ||
|
|
2ca6a8e3e8 | ||
|
|
0ae201e363 | ||
|
|
9e0f535a13 | ||
|
|
d4a6789b00 | ||
|
|
d9415ed7a0 | ||
|
|
778d7293f4 | ||
|
|
19a8b5c9fe | ||
|
|
7f3c481dcb | ||
|
|
8a54b5cb05 | ||
|
|
91b3234a45 | ||
|
|
ab7cbc981b | ||
|
|
a2c1a2cf82 | ||
|
|
167ede4e62 | ||
|
|
63900996e7 | ||
|
|
c626f3d5a5 | ||
|
|
8779e65846 | ||
|
|
0c8bf84e56 | ||
|
|
90972cf933 | ||
|
|
7d9a9605fb | ||
|
|
a0bc0f2981 | ||
|
|
f3b4c8a8ff | ||
|
|
6a8220c1c2 | ||
|
|
84c28748a4 | ||
|
|
7c29b619a5 | ||
|
|
ccfdbbe826 | ||
|
|
7052ce3c3c | ||
|
|
d73ca8aa9d | ||
|
|
64703a8c28 | ||
|
|
eb54658bf4 | ||
|
|
54d1c8ba61 | ||
|
|
1c04f6211f | ||
|
|
45497f9208 | ||
|
|
140c634397 | ||
|
|
be1b3813a9 | ||
|
|
f7ed7f1e93 | ||
|
|
0df72ac4ad |
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
_img/screenshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
fastlane/metadata/android/**/images/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
github: johan12345
|
||||
custom: 'https://paypal.me/johan98'
|
||||
7
.gitignore
vendored
@@ -8,5 +8,8 @@
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
apikeys.xml
|
||||
/app/release/app-release.aab
|
||||
/_img/connectors/*.ai
|
||||
/app/**/*.aab
|
||||
/app/**/*.apk
|
||||
/_img/connectors/*.ai
|
||||
api-7125266970515251116-798419-8e2dda660c80.json
|
||||
output-metadata.json
|
||||
32
.travis.yml
@@ -1,17 +1,27 @@
|
||||
language: android
|
||||
dist: trusty
|
||||
android:
|
||||
components:
|
||||
- build-tools-29.0.3
|
||||
- android-29
|
||||
language: java
|
||||
dist: focal
|
||||
env:
|
||||
global:
|
||||
- secure: KYdFlMarsyXw+OHht1Atp+Kirbw9O09Ck14EjFuKb1eNtknurZ/tGEXuD+8xWh1W8W21kgHEG7s3rzru53t29buz+FW9f+ZmhEWXFP3OydyvXLw4BAVVOjm6xG2uHX/8MOGLJNM7cfaF25EPQ+kznHe84R29KaLH90mNRr2lPa4VnfbcnvDStiVaez/vJ72UoYSP5HICAzoF70yC3ZvvCK1hZv71UIysCbFE2IkxvMhG9OOGebdnRmFssaRCrvfRLjitobcLzkPWzZZIqdjNASf8/iAxX8VgGBYfVj8ID06AfMrtgXNJRCvcD0LICraQ+WPUbikMunRieGO8PNHSB5vKdPoC50aLUa0RoRb4G3QM1pR2A8xAFlIJFX2R7iY+2t24L9hRFqB98+QoQzutfkAI1T0rzem/wtpZpuan+bDawDJEHGCeYbE0aPDAl6lytgrEE9fRgV3c1jJLQzu0xIWG8YLl3iMg0hL+c0wCKXoeqrfCFS6kYmmG7W+rQp4tCZifvRbWfAXwIPQieffKxqdEuUwiUsYxdzCu9v9uU3nflEOLLuRgeMP3gV8mpur9b5GztpkfgfzcAqsF+NiY01kYgGtrgCYlMy0TxASE+UuALrtkQtU01wwhs9RH7Az0Ib3C+MT5DTjxHQCYETIViocmNEG2vfAbgHazCpGAhcY=
|
||||
- secure: Hoko50vP+Mwm/O4CWvPvjMxd1gGhi+Bultjyy1WpludSmZFCfKz45Mj1EqzeYk6MeMLvGOEkSLB5wjdXdgJ9j5gEOF5K34k4vESJA7+DqDO3I7Xw9cnWgOXdFqB0qGHar0TVP3Dfg7ZcRgtmeX6t2uoELFLiS9GvTnbZXk4PCUybd979Xi8XHjEQV7+3EZbSjtsI4GFeIK1rrjd0I+UM88zYrWnz1KhdCjWvQb4iZjo+ib6NmGGEMqR7jJPRZz3KB01Y+n5h21qq9/Nv31zQIqt2B5nRxy3vBvvqKapgprIk+hVOpNnBU8w89uWUU6tYUeFk0t7z1TYWjgaBrMmGCM+aKkQ2q5F/ygNzDwB+KkpJx709Yhf0ZX8g2yvkdz+Ok7moYuvmrOPOf4E/U3BlfZxbGtRD2bGYbDgHLFYlTn6v5J2kJHDJAz31yvF5jvJejDaPp2IBVfoMRy7ZJFmGUNHGd9Se6bRwxS++AoobP5WDrBiUXNe2KKDMs3e7vbaO+hLbZ9XHpjeWIJGUfvtTee8EHZqF/8A3ju53V4/R0ehlOv2UZbpYNqcmwrsy9/R4pgMfDkG3Q054LmYrxD8DIC9b8excVMwWRP5aQ1TqZnxO2B1/vJU87RcnGl3jekeHTdHXbRq9BMV4dAdPfB9X3nGIi2GgV0iTBk+24xccOc0=
|
||||
- secure: RsN/2nBVv0byMzwchuAhDti1AWKECg3Uzqk6Ahgaqg02zx8GHj60j0qNax7KuMUg70X3G4b3m8ZzndAU0wcJ98UyIku7ofUYgXHm0XYKTJwiyyhrKJ8pZ4qoeCoRkitIGIitlb84fSufVamcoLyLNbLUsO5OzL+Uscyhq/BwAhyOhj5FB9DM+GE9ntanQ4m3k4guMyMctR9CGS6Tk4LKJ1WCm0AnjTPalc+we+7yORY2J/9d6VHLKfaFXbpuWmrdnFfDZqxqcUODsxPVE0LuUtXyKQDTjjfQc8106Z/z19AJS+oLm/2UND84PD3MqsjX6EX0/9k3fY0OsoCuQAivDRmtevQ0bDQrTAyeBLcfnPCw/MYiJWyBcaYBAYK42EAfsFTDBRIduFB/Dpvg+8GuLZSdm4xVYpTlQ2pMtlGNWIGIRQsjk9LZ8swq8QBMiiF/bpMGKdfmnyQj8jkEOCWaAzkR9O+4E4Y7PuBENef9XuV0hLMryCrML2YXigxAKkEUOPhfnG+AbEY+g2kAMp+2EtXaG3tsGxoMhEYA+uRd3o2cacQMMwRpnePH5DYg6F05mrvdHPpt+P9UR1iHaHmjBPAYeksmrdP8bS088zcnePgiL6+6N0m3+l8Krihmxg5RyWjnH18IwX4RO+xg4x3cW+zaScCDbbotDMEYtChF+Hs=
|
||||
- secure: LQHMdhaPUlCuJPFrCPpUphJSY6xzAFI/7RrcAVLtLcPhGdS+MeNifIkkAH7MeitTHroOC0dGkZ4bg/8/7bKfgwY4vPH9P50kZcnX5mI6zfBHgNYJzuthj+vJH9RAtkdQOW9Fe1uPIx8R9GUWUOVnkoJh0PQ1gDXdZW5fePqUtn1kYrcCCBE+Bhe3wz6QzTBqGS1nsVRTxQfSJNGi9uH1oi9kQGgQFuCCiJ/P0A6MIhSItkOfuggx/iorA+iASbhWkB4nXYQBbFe/ZhFJWbVfgYlOM0HtpKh8B2AqKw21Em32JoovCbUof4adkY7cH8/4Rt9SujC9YOw+a6oM+e//jJT0sie77V7zl670j+qODTuNvV4qVUwtoxShyc1Sfbd+Xb0xn/OC7DzBg97YuYCF/84yyuq12rl/cofynWE1L5YvGNSJk241XUw98Bvl0MK4VIfQvG9zJP0HnQZcWKt6kFOIEJSCRbmkd2tPPAZFBXBQf/bvpULOoKwneGJZBSapRoCyGwemM+EAzVB9UOXAqsXZ4FHkt1SSJVrTVwgxvXpCfmF6LZPhbz6nvouRWGsC/GdWjrHtdW5lEOvS27qKEL5rXwQ0o+71ZICGo8j4E0GOHXyi857qZhvO7cbOnts+iiawXiWzPXv2gGGabuqPwcU8JPEoWdaiIaeGUczfjBU=
|
||||
- secure: fvPVjj3l+TZ7HF5aGn/pmrkipGIrz+MkKNy3I7pnCJSuD/oVp9nQ5ePP/dAhaRThaW+fQbq7hOmCquPAtfoN9CUnHNV2f2l9RavDQIxdqvpXqY13A0BFffZho6A6H2kO7k6kQQPQEhl4SMJjObnX12/YDaTVx3b7aIroEJ8DyY62xGTsjExtaAksuFwUEekjh0MoWICvyBoDfrYhpiEVI2721rGMHu7FIXwmE38+jj7wwZd3Bp37yI9NY/b3ZQ/HUKyYDuoAL0xl5/GaQlRepD0v2xWQUQ40NArHLfMoscXi55UaENuswCg7rt9os8jCcZ8FkZf1cVsQ71JrE0uxgs00Jfjy2QKM5u1XUZefl1Nw5cfCDTWXIEGsz9OGiidFLehWUupX/6C6wr1BStdlRt+6Pt/FXsYHxO/qog++cKqHjOJRXi+raGAb99HhQ/hLnLUMKl5DIWlKF9DImXiOpfYxrgCJc3y91vNX6noJyWYs6PvErMukTsXFHen+fM0NtfTFoKW682oILvXjoeFvuzKpk49+rcpkJbRi5+Zdo/duSPp/flwvC4LOMi0RZOO9TNMhWKdkyWweDr1HEpvQn6RS87rpHzQwRDvm85F+PkZLMMqyWpuxBWbJf0jVbew21KvTJWamuizsIgCebFh0SSxgObzmMbAIFCkzL0PRsms=
|
||||
- ANDROID_HOME=$HOME/android-sdk
|
||||
before_install:
|
||||
- openssl aes-256-cbc -K $encrypted_53968681344a_key -iv $encrypted_53968681344a_iv -in _ci/keystore.jks.enc -out _ci/keystore.jks -d
|
||||
install:
|
||||
# Download and unzip the Android command line tools (if not already there thanks to the cache mechanism)
|
||||
# Latest version of this file available here: https://developer.android.com/studio/#command-tools
|
||||
- if test ! -e $HOME/android-cmdline-tools/cmdline-tools.zip ; then curl https://dl.google.com/android/repository/commandlinetools-linux-6609375_latest.zip > $HOME/android-cmdline-tools/cmdline-tools.zip ; fi
|
||||
- unzip -qq -n $HOME/android-cmdline-tools/cmdline-tools.zip -d $HOME/android-cmdline-tools
|
||||
# Install or update Android SDK components (will not do anything if already up to date thanks to the cache mechanism)
|
||||
- echo y | $HOME/android-cmdline-tools/tools/bin/sdkmanager --sdk_root=$HOME/android-sdk 'platform-tools' > /dev/null
|
||||
# Latest version of build-tools available here: https://developer.android.com/studio/releases/build-tools.html
|
||||
- echo y | $HOME/android-cmdline-tools/tools/bin/sdkmanager --sdk_root=$HOME/android-sdk 'build-tools;29.0.3' > /dev/null
|
||||
- echo y | $HOME/android-cmdline-tools/tools/bin/sdkmanager --sdk_root=$HOME/android-sdk 'platforms;android-29' > /dev/null
|
||||
script:
|
||||
- "./gradlew lintDebug testDebugUnitTest"
|
||||
- "./gradlew lintFossDebug testFossDebugUnitTest lintGoogleDebug testGoogleDebugUnitTest"
|
||||
- "./gradlew assembleRelease"
|
||||
before_cache:
|
||||
- rm -f $HOME/.gradle/caches/modules-2/modules-2.lock
|
||||
@@ -21,11 +31,15 @@ cache:
|
||||
- "$HOME/.gradle/caches/"
|
||||
- "$HOME/.gradle/wrapper/"
|
||||
- "$HOME/.android/build-cache"
|
||||
- "$HOME/android-cmdline-tools"
|
||||
- "$HOME/android-sdk"
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: B+V5Fz8k9HbpecyMjpJuLr8aVBrdwtDBDkQh4YQ8nu+Da4AiYwEJZseWXhOWs+oms0gNen9bBxsakQQKu7GKYDs8gIXZZtANWsc0gse8xo+cYT7NqEM3jP6mM3ytAv7VNRX3N2cdL7xazELK3/5+mghfORAAdXXYKUFGG5eTKoML8zgdPVN8E9QFqiusLXqoKhxOMCSE4NS+Di7CGlUmnidRTWg6yxhE085zljmYv2owS0NRbr5a4/zW6Z9xZPALGAqsOvIvpZHuOC2s0eMJWMmYGkK/Ws/LAVxfj4U+YkFp9hlZC0zEg/JoS19Gf57QmEu+vsoQ3uOBYBFv9NPI/R9kVH6o0hcOxId3J0u+ewSGWuceGLRpizXuMxKIvLTS5j6GWkxdSieWjwh/OuVB+ciAHNM31B7GP4FWnfz0ZaEVxI/tPenNipZdl9oXdyyBQQ00vPlYp0jT80XhaMh5rDwWMUPaEjRafvymcNyqZ0iVOr0rq1CbdT92STMSmA1U3/rmhtCMD5IGD0b+gQl+VpPKe1QXViYftVxCGL+s4ke4DUZD7HR20fGs8zu61Elnwci1HufbetKFL5TmxoKSLkWFSkzrtBaJnEruZIxhNUMkUL2UPynaOcPNzLoumjHXrUb3m3s0yE4OFelmJ6mJfXswP38sS8kj3wB7R/gC4rw=
|
||||
file: app/build/outputs/apk/release/app-release.apk
|
||||
secure: "XQR4GUrGkPKYVV0xMbJifX/ewKAnenBPlM/pPacQ9irAmYNYa/yEkySz4x1K6MP8cEnuJbxHFakcDqhNRCqD7Cq2NcnCi3qtTEXHK6ApLoVl/92eyiWxu/bYlidOEZb+YPcVNtTR253NiI8GYda+CrhLd4uCmsAgES+XPFJd/t2esMlDOSAp7xalZv/zFhhlB9+SevfPFMc6kkrqeHpKnMs9SK8ltVQmh3nch2KjtDvqgDW6d3nuwn7/HAer6/HY86hmA4Rh6Mo2cV6OloX0bdJ7hvA1GOT4p3+K3lWbTRxzE0o1DXAtT7+D158iKvxHFPuF3h+CTjSlLeiss6kQZL9nFjw/KhAvu+GJOp37PcMoI++mpMiFoWPlzKpp17BVKIDinYbgi8kiU4zG+QHhe2cY85SbfAplXUaysq7uzxEZwEUYHSAHNahshVooXRqvuzkthcH0/nvinfeXrzx2xDvQ3if1NENMRgttwewU0kvU61iKUwpcf/UN2bHK3DaPes0VzSH4PTHAGjoRpksDfqUwb7S8YxbYr+44aMbSPYN8Lbjda0BxPSKWwHM5/pi7FBJN1a1w3t7sV/EiACWUWr8OovmX4ljyCybbR0w9cPzRC1zAYeSUHslLXMTW2Pp9h594RnYh3q3VfeYlFCikFvuvrafwXmTkz35uhLb+2ws="
|
||||
file:
|
||||
- app/build/outputs/apk/foss/release/app-foss-release.apk
|
||||
- app/build/outputs/apk/google/release/app-google-release.apk
|
||||
on:
|
||||
repo: johan12345/EVMap
|
||||
tags: true
|
||||
|
||||
180
Gemfile.lock
Normal file
@@ -0,0 +1,180 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.2)
|
||||
addressable (2.8.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.1.0)
|
||||
aws-partitions (1.354.0)
|
||||
aws-sdk-core (3.104.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.239.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-kms (1.36.0)
|
||||
aws-sdk-core (~> 3, >= 3.99.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.78.0)
|
||||
aws-sdk-core (~> 3, >= 3.104.3)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.3)
|
||||
claide (1.0.3)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander-fastlane (4.4.6)
|
||||
highline (~> 1.7.2)
|
||||
declarative (0.0.20)
|
||||
declarative-option (0.1.0)
|
||||
digest-crc (0.6.1)
|
||||
rake (~> 13.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.7.6)
|
||||
emoji_regex (3.0.0)
|
||||
excon (0.76.0)
|
||||
faraday (1.0.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday-cookie_jar (0.0.6)
|
||||
faraday (>= 0.7.4)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday_middleware (1.0.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.0)
|
||||
fastlane (2.156.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.3, < 3.0.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander-fastlane (>= 4.4.6, < 5.0.0)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-api-client (>= 0.37.0, < 0.39.0)
|
||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||
highline (>= 1.7.2, < 2.0.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-api-client (0.38.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (~> 0.9)
|
||||
httpclient (>= 2.8.1, < 3.0)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.0)
|
||||
signet (~> 0.12)
|
||||
google-cloud-core (1.5.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.3.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
google-cloud-errors (1.0.1)
|
||||
google-cloud-storage (1.27.0)
|
||||
addressable (~> 2.5)
|
||||
digest-crc (~> 0.4)
|
||||
google-api-client (~> 0.33)
|
||||
google-cloud-core (~> 1.2)
|
||||
googleauth (~> 0.9)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (0.13.1)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (~> 0.14)
|
||||
highline (1.7.10)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.4.0)
|
||||
json (2.3.1)
|
||||
jwt (2.2.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.10.1)
|
||||
mini_mime (1.0.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.0)
|
||||
os (1.1.1)
|
||||
plist (3.5.0)
|
||||
public_suffix (4.0.6)
|
||||
rake (13.0.1)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rouge (2.0.7)
|
||||
rubyzip (2.3.0)
|
||||
security (0.1.3)
|
||||
signet (0.14.0)
|
||||
addressable (~> 2.3)
|
||||
faraday (>= 0.17.3, < 2.0)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
CFPropertyList
|
||||
naturally
|
||||
slack-notifier (2.3.2)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unf_ext (0.0.7.7-x64-mingw32)
|
||||
unicode-display_width (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.18.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.0)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
x64-mingw32
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
|
||||
BUNDLED WITH
|
||||
2.1.2
|
||||
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Johan von Forstner
|
||||
Copyright (c) 2020-2021 Johan von Forstner
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
49
README.md
@@ -1,48 +1,49 @@
|
||||
EVMap [](https://travis-ci.org/johan12345/EVMap)
|
||||
EVMap [](https://app.travis-ci.com/johan12345/EVMap)
|
||||
=====
|
||||
|
||||
<img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/appicon_cropped.svg?sanitize=true" width=80 alt="Logo"/>
|
||||
<img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/feature_graphic.svg" width=700 alt="Logo"/>
|
||||
|
||||
Android app to access the goingelectric.de electric vehicle charging station directory.
|
||||
Android app to find electric vehicle charging stations.
|
||||
|
||||
<a href="https://play.google.com/store/apps/details?id=net.vonforst.evmap" target="_blank">
|
||||
<img src="https://play.google.com/intl/en_us/badges/images/generic/en-play-badge.png" alt="Get it on Google Play" height="100"/></a>
|
||||
<a href="https://f-droid.org/repository/browse/?fdid=net.vonforst.evmap" target="_blank">
|
||||
<img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"/></a>
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- [Material Design](https://material.io/)
|
||||
- Shows all charging stations from the community-maintained [GoingElectric.de](https://www.goingelectric.de/stromtankstellen/) directory
|
||||
- Realtime availability information (beta)
|
||||
- Search places
|
||||
- Shows all charging stations from the community-maintained [GoingElectric.de](https://www.goingelectric.de/stromtankstellen/) and [Open Charge Map](https://openchargemap.org) directories
|
||||
- Realtime availability information (only in Europe)
|
||||
- Search for places
|
||||
- Advanced filtering options, including saved filter profiles
|
||||
- Favorites list, also with availability information
|
||||
- Integrated price comparison using [Chargeprice.app](https://chargeprice.app) (only in Europe)
|
||||
- Android Auto integration
|
||||
- No ads, fully open source
|
||||
- Compatible with Android 5.0 and above
|
||||
- Can use Google Maps or Mapbox (OpenStreetMap) as map backends - the version available on F-Droid only uses Mapbox.
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||
|
||||
<img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/screenshots/phone/01_main.png" width=250 alt="Screenshot 1"/><img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/screenshots/phone/02_detail.png" width=250 alt="Screenshot 2"/>
|
||||
<img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/screenshots/phone/en/mapbox/01_map.png" width=250 alt="Screenshot 1"/><img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/screenshots/phone/en/mapbox/02_detail.png" width=250 alt="Screenshot 2"/>
|
||||
|
||||
Development setup
|
||||
-----------------
|
||||
|
||||
The App is developed using Android Studio.
|
||||
The App is developed using Android Studio and should pretty much work out-of-the-box when you clone
|
||||
the Git repository and open the project with Android Studio.
|
||||
|
||||
For testing the app, you need to obtain API Keys for the
|
||||
[GoingElectric API](https://www.goingelectric.de/stromtankstellen/api/)
|
||||
as well as for [Google APIs](https://console.developers.google.com/)
|
||||
("Maps SDK for Android" and "Places API" need to be activated). These APIs need to be put into the
|
||||
app in the form of a resource file called `apikeys.xml` under `app/src/main/res/values`, with the
|
||||
following content:
|
||||
The only exception is that you need to obtain some free API keys for the different data sources that
|
||||
EVMap uses and put them into the app in the form of a resource file called `apikeys.xml` under
|
||||
`app/src/main/res/values`. You can find more information on which API keys are necessary for which
|
||||
features and how they can be obtained in our [documentation page](doc/api_keys.md).
|
||||
|
||||
```xml
|
||||
<resources>
|
||||
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
|
||||
insert your Google Maps key here
|
||||
</string>
|
||||
<string name="goingelectric_key" translatable="false">
|
||||
insert your GoingElectric key here
|
||||
</string>
|
||||
</resources>
|
||||
```
|
||||
There are two different build flavors, `google` and `foss`, where only the `google` variant uses
|
||||
Google Maps data and provides the Android Auto integration. The `foss` variant only uses Mapbox data
|
||||
and should run on devices without Google Play Services.
|
||||
|
||||
We also have a special [documentation page](doc/android_auto.md) on how to test the Android Auto
|
||||
app.
|
||||
|
||||
25
_img/connectors/connector_ccs_typ1.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_5" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:10;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:1.7;stroke-miterlimit:10;}
|
||||
.st2{fill:none;stroke:#000000;stroke-width:0.5;stroke-miterlimit:10;}
|
||||
</style>
|
||||
<circle cx="9" cy="18.7" r="1.4" />
|
||||
<circle cx="15" cy="18.7" r="1.4" />
|
||||
<path class="st0" d="M8.9,16.1h6.2c1.5,0,2.7,1.2,2.7,2.7l0,0c0,1.5-1.2,2.7-2.7,2.7H8.9c-1.5,0-2.7-1.2-2.7-2.7l0,0
|
||||
C6.2,17.3,7.4,16.1,8.9,16.1z" />
|
||||
<g>
|
||||
<circle cx="14.7" cy="6.4" r="1.3" />
|
||||
<circle cx="15.3" cy="10.5" r="0.8" />
|
||||
<circle cx="8.7" cy="10.5" r="0.8" />
|
||||
<circle cx="9.3" cy="6.4" r="1.3" />
|
||||
<circle cx="12" cy="13.1" r="1.3" />
|
||||
<circle class="st1" cx="12" cy="9.1" r="6.3" />
|
||||
<rect x="11" y="15.4" width="2" height="1.3" />
|
||||
<line class="st2" x1="10.9" y1="1.3" x2="13.1" y2="1.3" />
|
||||
<polygon points="13.1,0.9 13.1,2.4 14.5,3.1 13.8,1 " />
|
||||
<polygon points="10.9,0.9 10.9,2.4 9.5,3.1 10.2,1 " />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 1024 500"
|
||||
style="enable-background:new 0 0 1024 500;" xml:space="preserve">
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 1024 500" style="enable-background:new 0 0 1024 500;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#E6E6E6;}
|
||||
.st1{fill:#DCDCDC;}
|
||||
@@ -15,7 +15,7 @@
|
||||
.st10{fill:#666666;}
|
||||
.st11{fill:#D1D1D1;}
|
||||
.st12{opacity:0.2;fill:#808080;enable-background:new ;}
|
||||
.st13{opacity:0.5;}
|
||||
.st13{opacity:0.5;enable-background:new ;}
|
||||
.st14{fill:#FFB300;}
|
||||
.st15{fill:#90A4AE;}
|
||||
.st16{fill:#546E7A;}
|
||||
@@ -23,10 +23,9 @@
|
||||
.st18{fill:#FFFFFF;fill-opacity:0.2;}
|
||||
.st19{fill:#3E2723;fill-opacity:0.2;}
|
||||
.st20{opacity:0.45;enable-background:new ;}
|
||||
.st21{font-family:'Roboto-Light';}
|
||||
.st22{font-size:136.5333px;}
|
||||
.st21{enable-background:new ;}
|
||||
</style>
|
||||
<g id="Ebene_1">
|
||||
<g id="Ebene_1_1_">
|
||||
<rect y="-34.4" class="st0" width="1024" height="568.9" />
|
||||
<g>
|
||||
<path class="st1"
|
||||
@@ -35,25 +34,25 @@
|
||||
d="M145.4,335.9L38.1,228.7c-6-6-6-15.4,0-21.3L91,154.1c5.7-5.7,15.4-5.7,21.3,0l107.2,107.2" />
|
||||
<path class="st2" d="M131.7,209.9L93.6,248c-2.8,2.8-7.7,2.8-10.5,0l-24.7-24.7c-2.8-2.8-2.8-7.7,0-10.5l38.1-38.1
|
||||
c2.8-2.8,7.7-2.8,10.5,0l24.7,24.7C134.8,202.2,134.5,207,131.7,209.9z" />
|
||||
<path class="st3" d="M223.3,265.1c-2,2-5.4,2-7.4,0l-107.2-107c-3.7-3.7-10.2-4-13.9,0L41.8,211c-3.7,3.7-3.7,10,0,13.9L149,332.2
|
||||
c2,2,2,5.4,0,7.4c-2,2-5.4,2-7.4,0L34.4,232.4c-8-8-8-20.8,0-28.7l52.9-53.2c8-8,20.8-8,28.7,0l107.2,107.2
|
||||
<path class="st3" d="M223.3,265.1c-2,2-5.4,2-7.4,0l-107.2-107c-3.7-3.7-10.2-4-13.9,0l-53,52.9c-3.7,3.7-3.7,10,0,13.9L149,332.2
|
||||
c2,2,2,5.4,0,7.4s-5.4,2-7.4,0L34.4,232.4c-8-8-8-20.8,0-28.7l52.9-53.2c8-8,20.8-8,28.7,0l107.2,107.2
|
||||
C225.3,260,225.3,263.1,223.3,265.1z" />
|
||||
<path class="st4" d="M131.7,209.9L93.6,248c-2.8,2.8-7.7,2.8-10.5,0l-24.7-24.7c-2.8-2.8-2.8-7.7,0-10.5l38.1-38.1
|
||||
c2.8-2.8,7.7-2.8,10.5,0l24.7,24.7C134.8,202.2,134.5,207,131.7,209.9z" />
|
||||
<path class="st3" d="M135.4,213.6l-38.1,38.1c-4.8,4.8-13.1,4.8-17.9,0L54.6,227c-4.8-4.8-4.8-13.1,0-17.9l38.1-38.1
|
||||
<path class="st3" d="M135.4,213.6l-38.1,38.1c-4.8,4.8-13.1,4.8-17.9,0L54.6,227c-4.8-4.8-4.8-13.1,0-17.9L92.7,171
|
||||
c4.8-4.8,13.1-4.8,17.9,0l24.7,24.7C140.5,200.5,140.5,208.5,135.4,213.6z M62,216.4c-0.9,0.9-0.9,2.3,0,3.1l24.7,24.7
|
||||
c0.9,0.9,2.3,0.9,3.1,0l38.1-38.1c0.9-0.9,0.9-2.3,0-3.1l-24.7-24.7c-0.9-0.9-2.3-0.9-3.1,0L62,216.4z M233.8,254.6l-95.3,95.3
|
||||
c-2,2-5.4,2-7.4,0c-2-2-2-5.4,0-7.4l95.3-95.3c2-2,5.4-2,7.4,0S235.8,252.6,233.8,254.6z M228.4,238.3c-4.8,4.8-13.1,4.8-17.9,0
|
||||
l-43-42.7c-0.9-0.9-2.3-0.9-3.1,0l-5.4,5.4c-2,2-5.4,2-7.4,0c-2-2-2-5.4,0-7.4l5.4-5.4c4.8-4.8,13.1-4.8,17.9,0l43,43
|
||||
c0.9,0.9,2.3,0.9,3.1,0c0.9-0.9,0.9-2.3,0-3.1l-58.9-59.2c-2-2-2-5.4,0-7.4c2-2,5.4-2,7.4,0l58.9,58.9
|
||||
c-2,2-5.4,2-7.4,0s-2-5.4,0-7.4l95.3-95.3c2-2,5.4-2,7.4,0S235.8,252.6,233.8,254.6z M228.4,238.3c-4.8,4.8-13.1,4.8-17.9,0
|
||||
l-43-42.7c-0.9-0.9-2.3-0.9-3.1,0L159,201c-2,2-5.4,2-7.4,0s-2-5.4,0-7.4l5.4-5.4c4.8-4.8,13.1-4.8,17.9,0l43,43
|
||||
c0.9,0.9,2.3,0.9,3.1,0c0.9-0.9,0.9-2.3,0-3.1l-58.9-59.2c-2-2-2-5.4,0-7.4s5.4-2,7.4,0l58.9,58.9
|
||||
C233.2,225.3,233.2,233.5,228.4,238.3z" />
|
||||
<path class="st3" d="M174.6,163.8l-10.5,10.5c-2,2-5.4,2-7.4,0l-21.6-21.6c-2-2-2-5.4,0-7.4l1.7-1.7l-4.8-4.6c-2-2-2-5.4,0-7.4
|
||||
c2-2,5.4-2,7.4,0l4.8,4.8l1.4-1.4c2-2,5.4-2,7.4,0l21.3,21.3C176.4,158.4,176.6,161.8,174.6,163.8z M160.4,163.2l3.1-3.1
|
||||
l-13.9-13.9l-3.1,3.1L160.4,163.2z" />
|
||||
<path class="st3" d="M174.6,163.8l-10.5,10.5c-2,2-5.4,2-7.4,0l-21.6-21.6c-2-2-2-5.4,0-7.4l1.7-1.7L132,139c-2-2-2-5.4,0-7.4
|
||||
s5.4-2,7.4,0l4.8,4.8l1.4-1.4c2-2,5.4-2,7.4,0l21.3,21.3C176.4,158.4,176.6,161.8,174.6,163.8z M160.4,163.2l3.1-3.1l-13.9-13.9
|
||||
l-3.1,3.1L160.4,163.2z" />
|
||||
<g>
|
||||
<path class="st5" d="M163.8,290.1c-0.6,0.6-1.4,1.1-2.6,1.4c-2.8,0.6-5.7-1.1-6.3-3.7l-3.7-16.2l-3.7,5.4c-1.1,1.7-3.1,2.6-5.1,2
|
||||
c-2-0.6-3.7-2-4-4l-6.5-27c-0.6-2.8,1.1-5.7,3.7-6.3c2.8-0.6,5.7,1.1,6.3,3.7l3.7,16.2l3.7-5.4c1.1-1.7,3.1-2.6,5.1-2
|
||||
c2,0.6,3.7,2,4,4l6.3,27C165.5,287.3,165,289,163.8,290.1z" />
|
||||
s-3.7-2-4-4l-6.5-27c-0.6-2.8,1.1-5.7,3.7-6.3c2.8-0.6,5.7,1.1,6.3,3.7l3.7,16.2l3.7-5.4c1.1-1.7,3.1-2.6,5.1-2s3.7,2,4,4l6.3,27
|
||||
C165.5,287.3,165,289,163.8,290.1z" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
@@ -66,11 +65,11 @@
|
||||
<path class="st3" d="M156.4,462.5l-40.1,40.1c-4.6,4.6-11.7,4.6-16.2,0l-68.3-68.3c-4.6-4.6-4.6-11.7,0-16.2L72,378
|
||||
c4.6-4.6,11.7-4.6,16.2,0l68.3,68.3C161,450.8,161,457.9,156.4,462.5z M37.8,424.4c-1.1,1.1-1.1,2.8,0,4l68.3,68.3
|
||||
c1.1,1.1,2.8,1.1,4,0l40.1-40.1c1.1-1.1,1.1-2.8,0-4l-68.3-68.3c-1.1-1.1-2.8-1.1-4,0L37.8,424.4z" />
|
||||
<path class="st2" d="M111.2,487.5c-1.7,1.7-4.3,1.7-6,0l-25-25c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l25,25
|
||||
<path class="st2" d="M111.2,487.5c-1.7,1.7-4.3,1.7-6,0l-25-25c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l25,25
|
||||
C112.9,483.2,112.9,485.8,111.2,487.5z M71.1,447.4c-0.9,0.9-2,1.4-3.1,1.1c-1.1,0-2.3-0.3-3.1-1.1c-0.9-0.9-1.4-2-1.1-3.1
|
||||
c0-0.3,0-0.6,0-0.9s0-0.6,0.3-0.9s0.3-0.6,0.3-0.9c0.9-1.1,2-2,3.4-2c1.1,0,2.3,0.3,3.1,1.1c0.3,0.3,0.3,0.3,0.6,0.6
|
||||
c0.3,0.3,0.3,0.6,0.3,0.9c0,0.3,0.3,0.6,0.3,0.9s0,0.6,0,0.9C72.2,445.4,71.7,446.6,71.1,447.4z" />
|
||||
<path class="st3" d="M68,393.9c-1.7,1.7-4.3,1.7-6,0l-9.1-9.1L39,398.8l1.1,1.1c1.7,1.7,1.7,4.3,0,6c-1.7,1.7-4.3,1.7-6,0l-4-4
|
||||
c0.3,0.3,0.3,0.6,0.3,0.9s0.3,0.6,0.3,0.9s0,0.6,0,0.9C72.2,445.4,71.7,446.6,71.1,447.4z" />
|
||||
<path class="st3" d="M68,393.9c-1.7,1.7-4.3,1.7-6,0l-9.1-9.1l-13.9,14l1.1,1.1c1.7,1.7,1.7,4.3,0,6s-4.3,1.7-6,0l-4-4
|
||||
c-1.7-1.7-1.7-4.3,0-6l20.2-20.2c1.7-1.7,4.3-1.7,6,0l11.9,11.9C69.7,389.7,69.7,392.2,68,393.9z" />
|
||||
</g>
|
||||
<g>
|
||||
@@ -78,35 +77,35 @@
|
||||
C390.3,425.8,354.1,476.4,333.4,497.2z" />
|
||||
<path class="st5" d="M293.5,387.7l48.1-11.7c1.7-0.3,2.6,1.7,1.1,2.6l-28.4,18.8l9.7,9.7c0.9,0.9,0.3,2.3-0.9,2.3l-43.8,6.8
|
||||
c-1.4,0.3-2.3-1.7-1.1-2.6l23.6-14.5l-9.4-9.4C292.4,389.1,292.7,388,293.5,387.7z" />
|
||||
<path class="st3" d="M374.6,440l-15.1-15.1c-7.1-7.1-7.1-18.8,0-26.2l8-8c1.7-1.7,4.3-1.7,6,0c1.7,1.7,1.7,4.3,0,6l-8,8
|
||||
c-4,4-4,10.2,0,13.9l15.1,15.1c1.7,1.7,1.7,4.3,0,6C378.9,441.4,376,441.4,374.6,440z" />
|
||||
<path class="st3" d="M374.6,440l-15.1-15.1c-7.1-7.1-7.1-18.8,0-26.2l8-8c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-8,8
|
||||
c-4,4-4,10.2,0,13.9l15.1,15.1c1.7,1.7,1.7,4.3,0,6S376,441.4,374.6,440z" />
|
||||
<path class="st3" d="M327.4,503.2L226.7,402.5c-1.7-1.7-1.7-4.3,0-6l73.4-73.4c7.1-7.1,18.8-7.1,26.2,0l70.3,70.3l0,0l8,8
|
||||
c1.7,1.7,1.7,4.3,0,6c-1.7,1.7-4.3,1.7-6,0l-2.3-2.3c-8.2,31.3-41.5,76.2-60,94.7l-3.1,3.1C331.7,504.9,328.8,504.9,327.4,503.2z
|
||||
c1.7,1.7,1.7,4.3,0,6s-4.3,1.7-6,0l-2.3-2.3c-8.2,31.3-41.5,76.2-60,94.7l-3.1,3.1C331.7,504.9,328.8,504.9,327.4,503.2z
|
||||
M235.8,399.6l94.4,94.4c21.6-21.6,54.6-69.1,58.9-96.1l-68.8-68.8c-4-4-10.2-4-13.9,0C306.1,329.4,235.8,399.6,235.8,399.6z" />
|
||||
<path class="st3" d="M327.4,503.2L222.7,398.5c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l104.7,104.7c1.7,1.7,1.7,4.3,0,6
|
||||
<path class="st3" d="M327.4,503.2L222.7,398.5c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l104.7,104.7c1.7,1.7,1.7,4.3,0,6
|
||||
C331.7,504.9,328.8,504.9,327.4,503.2z" />
|
||||
<path class="st2" d="M251.2,410.1c-5.4-5.4-13.9-5.4-19.3,0s-5.4,13.9,0,19.3l0,0c5.4,5.4,13.9,5.4,19.3,0
|
||||
S256.6,415.3,251.2,410.1L251.2,410.1z M315.4,474.4c-5.4-5.4-13.9-5.4-19.3,0c-5.4,5.4-5.4,13.9,0,19.3l0,0
|
||||
c5.4,5.4,13.9,5.4,19.3,0S320.9,479.8,315.4,474.4L315.4,474.4z" />
|
||||
<path class="st3" d="M228.7,432.3c-7.1-7.1-6.8-18.5,0-25.3c6.8-6.8,18.5-6.8,25.3,0c6.8,6.8,6.8,18.5,0,25.3
|
||||
C247.2,439.4,235.8,439.4,228.7,432.3z M248,413c-3.7-3.7-9.7-3.7-13.4,0c-3.7,3.7-3.7,9.7,0,13.4s9.7,3.7,13.4,0
|
||||
S256.6,415.3,251.2,410.1L251.2,410.1z M315.4,474.4c-5.4-5.4-13.9-5.4-19.3,0s-5.4,13.9,0,19.3l0,0c5.4,5.4,13.9,5.4,19.3,0
|
||||
S320.9,479.8,315.4,474.4L315.4,474.4z" />
|
||||
<path class="st3" d="M228.7,432.3c-7.1-7.1-6.8-18.5,0-25.3s18.5-6.8,25.3,0c6.8,6.8,6.8,18.5,0,25.3
|
||||
C247.2,439.4,235.8,439.4,228.7,432.3z M248,413c-3.7-3.7-9.7-3.7-13.4,0s-3.7,9.7,0,13.4s9.7,3.7,13.4,0
|
||||
C251.7,422.7,251.7,416.7,248,413z" />
|
||||
<g>
|
||||
<path class="st3" d="M293.3,496.6c-7.1-7.1-6.8-18.5,0-25.3c6.8-6.8,18.5-6.8,25.3,0c7.1,7.1,6.8,18.5,0,25.3
|
||||
<path class="st3" d="M293.3,496.6c-7.1-7.1-6.8-18.5,0-25.3s18.5-6.8,25.3,0c7.1,7.1,6.8,18.5,0,25.3
|
||||
C311.5,503.7,300.1,503.7,293.3,496.6z M312.6,477.6c-3.7-3.7-9.7-3.7-13.4,0c-3.7,3.7-3.7,9.7,0,13.4s9.7,3.7,13.4,0
|
||||
C316.3,487.2,316.3,481.3,312.6,477.6z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M293.3,496.6l-84.5-84.5c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l84.5,84.5c1.7,1.7,1.7,4.3,0,6
|
||||
<path class="st3" d="M293.3,496.6l-84.5-84.5c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l84.5,84.5c1.7,1.7,1.7,4.3,0,6
|
||||
C297.5,498.3,295,498.3,293.3,496.6z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M194.6,398.2c-0.3-0.3-0.3-0.3-0.6-0.6c-0.3-0.3-0.3-0.6-0.3-0.9c0-0.3-0.3-0.6-0.3-0.9s0-0.6,0-0.9
|
||||
c0-0.3,0-0.6,0-0.9c0-0.3,0-0.6,0.3-0.9c0.3,0,0.3-0.3,0.6-0.6s0.3-0.6,0.6-0.6c0.3,0,0.3-0.3,0.6-0.6c0.3-0.3,0.6-0.3,0.9-0.3
|
||||
c0.3,0,0.6,0,0.9-0.3c0.3-0.3,0.6,0,0.9,0c0.3,0,0.6,0,0.9,0c0.3,0,0.6,0,0.9,0.3c0.3,0,0.6,0.3,0.9,0.3c0.6,0.3,0.9,0.6,1.1,1.1
|
||||
c0.3,0.3,0.3,0.6,0.3,0.9c0,0.3,0.3,0.6,0.3,0.9c0,0.3,0,0.6,0,0.9s0,0.6,0,0.9c0,0.3,0,0.6-0.3,0.9c-0.3,0.3-0.3,0.6-0.3,0.9
|
||||
c-0.3,0.3-0.3,0.6-0.6,0.6s-0.3,0.3-0.6,0.6c-0.3,0.3-0.6,0.3-0.9,0.3c-0.3,0-0.6,0.3-0.9,0.3s-0.6,0-0.9,0c-0.3,0-0.6,0-0.9,0
|
||||
c-0.3,0-0.6,0-0.9-0.3c-0.3,0-0.6-0.3-0.9-0.3C195.1,398.8,194.8,398.5,194.6,398.2z" />
|
||||
<path class="st3" d="M194.6,398.2c-0.3-0.3-0.3-0.3-0.6-0.6c-0.3-0.3-0.3-0.6-0.3-0.9s-0.3-0.6-0.3-0.9s0-0.6,0-0.9s0-0.6,0-0.9
|
||||
s0-0.6,0.3-0.9c0.3,0,0.3-0.3,0.6-0.6s0.3-0.6,0.6-0.6s0.3-0.3,0.6-0.6c0.3-0.3,0.6-0.3,0.9-0.3c0.3,0,0.6,0,0.9-0.3
|
||||
c0.3-0.3,0.6,0,0.9,0c0.3,0,0.6,0,0.9,0c0.3,0,0.6,0,0.9,0.3c0.3,0,0.6,0.3,0.9,0.3c0.6,0.3,0.9,0.6,1.1,1.1
|
||||
c0.3,0.3,0.3,0.6,0.3,0.9s0.3,0.6,0.3,0.9s0,0.6,0,0.9s0,0.6,0,0.9s0,0.6-0.3,0.9s-0.3,0.6-0.3,0.9c-0.3,0.3-0.3,0.6-0.6,0.6
|
||||
s-0.3,0.3-0.6,0.6c-0.3,0.3-0.6,0.3-0.9,0.3c-0.3,0-0.6,0.3-0.9,0.3s-0.6,0-0.9,0c-0.3,0-0.6,0-0.9,0c-0.3,0-0.6,0-0.9-0.3
|
||||
c-0.3,0-0.6-0.3-0.9-0.3C195.1,398.8,194.8,398.5,194.6,398.2z" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
@@ -121,46 +120,43 @@
|
||||
l0.3,0.3C940.1,42.4,940.1,60.8,928.7,72.2z M893.2,36.7c-8,8-8,21,0,29.3l0.3,0.3c8,8,21,8,29.3,0c8-8,8-21,0-29.3l-0.3-0.3
|
||||
C914.2,28.7,901.1,28.7,893.2,36.7z" />
|
||||
<path class="st3" d="M896.9,55.2c-2,2-2,5.1,0,7.1c2,2,5.1,2,7.1,0l0,0c2-2,2-5.1,0-7.1C902.3,53.2,898.8,53.2,896.9,55.2
|
||||
L896.9,55.2z M911.4,40.6c-2,2-2,5.1,0,7.1c2,2,5.1,2,7.1,0l0,0c2-2,2-5.1,0-7.1C916.8,38.7,913.4,38.7,911.4,40.6L911.4,40.6z" />
|
||||
L896.9,55.2z M911.4,40.6c-2,2-2,5.1,0,7.1s5.1,2,7.1,0l0,0c2-2,2-5.1,0-7.1C916.8,38.7,913.4,38.7,911.4,40.6L911.4,40.6z" />
|
||||
<path class="st2"
|
||||
d="M971.9,88.7l10.8,10.8c7.4,7.4,7.4,19.3,0,26.7l0,0c-7.4,7.4-19.3,7.4-26.7,0l-10.8-10.8L971.9,88.7z" />
|
||||
<path class="st3" d="M985.9,129.4c-9.1,9.1-23.6,9.1-32.7,0l-8.8-8.8c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l8.8,8.8
|
||||
c5.7,5.7,15.1,5.7,20.8,0c5.7-5.7,5.7-15.1,0-20.8l-8.8-8.8c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l8.8,8.8
|
||||
C995,105.8,995,120.3,985.9,129.4z" />
|
||||
<path class="st3" d="M953.2,112.9c-1.7,1.7-4.3,1.7-6,0l-9.4-9.4c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l9.4,9.4
|
||||
C954.9,108.6,954.9,111.2,953.2,112.9z M969.4,97c-1.7,1.7-4.3,1.7-6,0l-9.4-9.4c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0
|
||||
l9.4,9.4C971.1,92.4,971.1,95.3,969.4,97z" />
|
||||
<path class="st3" d="M978.8,88.2l-34.1,34.1c-1.7,1.7-4.3,1.7-6,0c-1.7-1.7-1.7-4.3,0-6l34.1-34.1c1.7-1.7,4.3-1.7,6,0
|
||||
C980.5,83.9,980.5,86.4,978.8,88.2z M994.4,137.9c-1.7,1.7-4.3,1.7-6,0l-8.5-8.5c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0
|
||||
l8.5,8.5C995.8,133.4,996.1,136.2,994.4,137.9z" />
|
||||
<path class="st3" d="M985.9,129.4c-9.1,9.1-23.6,9.1-32.7,0l-8.8-8.8c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l8.8,8.8
|
||||
c5.7,5.7,15.1,5.7,20.8,0s5.7-15.1,0-20.8l-8.8-8.8c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l8.8,8.8C995,105.8,995,120.3,985.9,129.4z" />
|
||||
<path class="st3" d="M953.2,112.9c-1.7,1.7-4.3,1.7-6,0l-9.4-9.4c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l9.4,9.4
|
||||
C954.9,108.6,954.9,111.2,953.2,112.9z M969.4,97c-1.7,1.7-4.3,1.7-6,0l-9.4-9.4c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l9.4,9.4
|
||||
C971.1,92.4,971.1,95.3,969.4,97z" />
|
||||
<path class="st3" d="M978.8,88.2l-34.1,34.1c-1.7,1.7-4.3,1.7-6,0s-1.7-4.3,0-6l34.1-34.1c1.7-1.7,4.3-1.7,6,0
|
||||
C980.5,83.9,980.5,86.4,978.8,88.2z M994.4,137.9c-1.7,1.7-4.3,1.7-6,0l-8.5-8.5c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l8.5,8.5
|
||||
C995.8,133.4,996.1,136.2,994.4,137.9z" />
|
||||
<g>
|
||||
<path class="st5" d="M966.5,110.1c-1.4,1.4-1.4,4,0,5.4c1.4,1.4,4,1.4,5.4,0l0,0c1.4-1.4,1.4-4,0-5.4
|
||||
C970.5,108.6,968,108.6,966.5,110.1L966.5,110.1z" />
|
||||
<path class="st5" d="M966.5,110.1c-1.4,1.4-1.4,4,0,5.4s4,1.4,5.4,0l0,0c1.4-1.4,1.4-4,0-5.4C970.5,108.6,968,108.6,966.5,110.1
|
||||
L966.5,110.1z" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st7" d="M486.4,389.4l97.6,97.6l10.8-10.8c6-6,6-15.6,0-21.6l-38.7-38.7l-1.7-0.6c-6.5-2.3-8.5-10.5-3.7-15.6
|
||||
l-3.4-3.4C529.9,378.6,509.4,379.4,486.4,389.4" />
|
||||
<path class="st7" d="M486.4,389.4L584,487l10.8-10.8c6-6,6-15.6,0-21.6l-38.7-38.7l-1.7-0.6c-6.5-2.3-8.5-10.5-3.7-15.6l-3.4-3.4
|
||||
C529.9,378.6,509.4,379.4,486.4,389.4" />
|
||||
<path class="st8" d="M517.7,382.6C517.4,382.6,517.4,382.6,517.7,382.6c-1.1-0.3-2-0.3-2.8-0.3c-8.8,0.3-18.2,2.8-28.2,6.8l0,0
|
||||
l31,31C527.9,410.1,527.9,393.1,517.7,382.6 M599.3,465c-10.2-7.7-25-6.8-34.7,2.6l19.3,19.3l10.8-10.8
|
||||
C597.9,473,599.3,469,599.3,465" />
|
||||
<path class="st3" d="M581.1,490.1l-97.6-97.8c-1.1-1.1-1.4-2.3-1.1-3.7c0.3-1.4,1.1-2.6,2.6-3.1c27.9-11.9,48.6-9.4,65.7,8
|
||||
l0.3,0.3c1.7,1.7,1.7,4.3,0,6s-4.3,1.7-6,0l-0.3-0.3c-13.4-13.4-29.3-16.2-50.6-8.2L584,481l8-8c4.3-4.3,4.3-11.4,0-15.6
|
||||
l-34.4-34.4c-1.1-1.1-1.4-2.3-1.1-3.7l1.7-10.8c0.3-2.3,2.6-4,4.8-3.4c2.3,0.3,4,2.6,3.4,4.8l-1.4,8.5l33,33
|
||||
L557.6,423c-1.1-1.1-1.4-2.3-1.1-3.7l1.7-10.8c0.3-2.3,2.6-4,4.8-3.4c2.3,0.3,4,2.6,3.4,4.8l-1.4,8.5l33,33
|
||||
c7.7,7.7,7.7,20.2,0,27.9l-10.8,10.8C585.4,491.8,582.8,491.5,581.1,490.1z" />
|
||||
<path class="st2" d="M509.4,390.5c-6-6-15.6-6-21.6,0c-6,6-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0
|
||||
C515.7,406.4,515.7,396.8,509.4,390.5L509.4,390.5z M565.8,434.6c-3.4-3.4-8.8-3.4-11.9,0c-3.1,3.4-3.4,8.8,0,11.9
|
||||
c3.4,3.1,8.8,3.4,11.9,0C569.2,443.4,569.2,438,565.8,434.6z" />
|
||||
<path class="st3" d="M484.7,415.3c-7.7-7.7-7.7-20.2,0-27.9s20.2-7.7,27.9,0c7.7,7.7,7.7,20.2,0,27.9
|
||||
C504.9,422.9,492.4,422.9,484.7,415.3z M506.6,393.6c-4.3-4.3-11.4-4.3-15.6,0c-4.3,4.3-4.3,11.4,0,15.6c4.3,4.3,11.4,4.3,15.6,0
|
||||
C510.9,405,510.9,397.9,506.6,393.6z" />
|
||||
<path class="st2" d="M509.4,390.5c-6-6-15.6-6-21.6,0s-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0C515.7,406.4,515.7,396.8,509.4,390.5
|
||||
L509.4,390.5z M565.8,434.6c-3.4-3.4-8.8-3.4-11.9,0c-3.1,3.4-3.4,8.8,0,11.9s8.8,3.4,11.9,0C569.2,443.4,569.2,438,565.8,434.6z" />
|
||||
<path class="st3" d="M484.7,415.3c-7.7-7.7-7.7-20.2,0-27.9s20.2-7.7,27.9,0s7.7,20.2,0,27.9C504.9,422.9,492.4,422.9,484.7,415.3
|
||||
z M506.6,393.6c-4.3-4.3-11.4-4.3-15.6,0c-4.3,4.3-4.3,11.4,0,15.6c4.3,4.3,11.4,4.3,15.6,0C510.9,405,510.9,397.9,506.6,393.6z" />
|
||||
<path class="st2" d="M594.5,475.6c-6-6-15.6-6-21.6,0s-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0S600.5,481.5,594.5,475.6L594.5,475.6z
|
||||
" />
|
||||
<path class="st3" d="M569.7,500.3c-7.7-7.7-7.7-20.2,0-27.9s20.2-7.7,27.9,0s7.7,20.2,0,27.9C589.9,508,577.4,508,569.7,500.3z
|
||||
M591.4,478.4c-4.3-4.3-11.4-4.3-15.6,0s-4.3,11.4,0,15.6s11.4,4.3,15.6,0S595.6,483,591.4,478.4z" />
|
||||
<path class="st3" d="M569.7,500.3l-98.1-98.1c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l98.1,98.1c1.7,1.7,1.7,4.3,0,6
|
||||
C574,502,571.4,502,569.7,500.3z M459.7,390.2c-0.9-0.9-1.4-2-1.1-3.1c0-1.1,0.3-2.3,1.1-3.1c0.9-0.9,2-1.4,3.1-1.1
|
||||
c2.3,0,4.3,2,4.3,4.3c0,1.1-0.3,2.3-1.1,3.1c-0.9,0.9-2,1.4-3.1,1.1C461.4,391.4,460.5,391.1,459.7,390.2z" />
|
||||
<path class="st3" d="M569.7,500.3l-98.1-98.1c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l98.1,98.1c1.7,1.7,1.7,4.3,0,6
|
||||
S571.4,502,569.7,500.3z M459.7,390.2c-0.9-0.9-1.4-2-1.1-3.1c0-1.1,0.3-2.3,1.1-3.1c0.9-0.9,2-1.4,3.1-1.1c2.3,0,4.3,2,4.3,4.3
|
||||
c0,1.1-0.3,2.3-1.1,3.1c-0.9,0.9-2,1.4-3.1,1.1C461.4,391.4,460.5,391.1,459.7,390.2z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M306.9,39.8l-35-35.3c-7.7-7.7-19.9-8.2-28.2-1.7L218.2,23l70.3,70.3L308.6,68
|
||||
@@ -174,17 +170,16 @@
|
||||
<path class="st3" d="M196.8,64.3c-1.7-1.7-1.7-4.3,0-6L215,40.1c4-4,4-10.2,0-13.9c-4-4-10.2-4-13.9,0l-18.2,18.2
|
||||
c-1.7,1.7-4.3,1.7-6,0s-1.7-4.3,0-6l18.2-18.2c7.1-7.1,18.8-7.1,26.2,0s7.1,18.8,0,26.2l-18.2,18.2C201.1,66,198.5,66,196.8,64.3z
|
||||
M267.1,134.5c-1.7-1.7-1.7-4.3,0-6l18.2-18.2c4-4,4-10.2,0-13.9c-4-3.7-10.2-4-13.9,0l-18.2,18.2c-1.7,1.7-4.3,1.7-6,0
|
||||
c-1.7-1.7-1.7-4.3,0-6l18.2-18.2c7.1-7.1,18.8-7.1,26.2,0c7.1,7.1,7.1,18.8,0,26.2l-18.2,18.2
|
||||
C271.6,136.2,268.8,136.2,267.1,134.5z" />
|
||||
s-1.7-4.3,0-6l18.2-18.2c7.1-7.1,18.8-7.1,26.2,0c7.1,7.1,7.1,18.8,0,26.2l-18.2,18.2C271.6,136.2,268.8,136.2,267.1,134.5z" />
|
||||
<path class="st3" d="M289.6,90.1l-0.3-0.3c-19.1-25.9-41.8-48.6-67.4-67.7c-1.7-1.4-2.3-4-0.9-6c1.4-1.7,4-2.3,6-0.9
|
||||
c26.5,19.6,49.8,43,69.4,69.4c1.4,1.7,1.1,4.6-0.9,6C293.5,91.6,291.3,91.6,289.6,90.1z" />
|
||||
<path class="st3" d="M215,25.9l-0.3-0.3c-1.4-1.7-1.1-4.6,0.6-6l25.6-20.2c10.2-8,24.7-7.1,34.1,2l35,35.3c9.4,9.4,10,23.9,2,34.1
|
||||
l-20.2,25.3c-1.4,1.7-4,2-6,0.6c-2-1.4-2-4-0.6-6l20.2-25.3c5.4-6.8,4.8-16.5-1.1-22.8L268.8,7.4c-6.3-6.3-15.9-6.8-22.8-1.4
|
||||
l-25.6,20.2C218.7,27.8,216.5,27.6,215,25.9z M264.2,125.7l-13.9,13.9l-8-8l13.9-13.9 M193.7,55.2l-13.9,13.9l-8-8l13.9-13.9" />
|
||||
l-20.2,25.3c-1.4,1.7-4,2-6,0.6s-2-4-0.6-6l20.2-25.3c5.4-6.8,4.8-16.5-1.1-22.8L268.8,7.4C262.5,1.1,252.9,0.6,246,6l-25.6,20.2
|
||||
C218.7,27.8,216.5,27.6,215,25.9z M264.2,125.7l-13.9,13.9l-8-8l13.9-13.9 M193.7,55.2l-13.9,13.9l-8-8l13.9-13.9" />
|
||||
<path class="st3" d="M247.2,142.8l-8.2-8.2c-1.7-1.7-1.7-4.3,0-6l13.9-13.9c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-11.1,11.1l2,2
|
||||
l11.1-11.1c1.7-1.7,4.3-1.7,6,0c1.7,1.7,1.7,4.3,0,6l-13.7,14.2C251.4,144.5,248.9,144.5,247.2,142.8z M176.6,72.2l-8-8
|
||||
c-1.7-1.7-1.7-4.3,0-6l13.9-13.9c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-11.1,11.1l2,2l11.1-11.1c1.7-1.7,4.3-1.7,6,0
|
||||
c1.7,1.7,1.7,4.3,0,6l-13.9,13.9C180.9,73.9,178.3,73.9,176.6,72.2z" />
|
||||
l11.1-11.1c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-13.7,14.2C251.4,144.5,248.9,144.5,247.2,142.8z M176.6,72.2l-8-8
|
||||
c-1.7-1.7-1.7-4.3,0-6l13.9-13.9c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-11.1,11.1l2,2l11.1-11.1c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6
|
||||
l-13.9,13.9C180.9,73.9,178.3,73.9,176.6,72.2z" />
|
||||
<path class="st3" d="M270.2,137.6l-96.4-96.4c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l96.4,96.4c1.7,1.7,1.7,4.3,0,6
|
||||
C274.5,139.4,271.9,139.4,270.2,137.6z" />
|
||||
<path class="st2" d="M239.5,88.7l-16.8-16.8c-1.7-1.7-1.7-4.3,0-6l10-10c6.3-6.3,16.5-6.3,23,0l0,0c6.3,6.3,6.3,16.5,0,23l-10,10
|
||||
@@ -194,7 +189,7 @@
|
||||
<path class="st2" d="M213,28.1c-2.8-2.8-7.4-2.8-10,0c-2.8,2.8-2.8,7.4,0,10c2.8,2.8,7.4,2.8,10,0C215.6,35.2,215.6,30.7,213,28.1
|
||||
z M283.3,98.4c-2.8-2.8-7.4-2.8-10,0s-2.8,7.4,0,10s7.4,2.8,10,0C285.9,105.5,286.2,101.2,283.3,98.4z" />
|
||||
<path class="st3" d="M253.2,148.7L169,64.5c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l84.2,84.2c1.7,1.7,1.7,4.3,0,6
|
||||
C257.4,150.4,254.9,150.4,253.2,148.7z M274.2,157.8c-1.7-1.7-4.3-1.7-6,0s-1.7,4.3,0,6c1.7,1.7,4.3,1.7,6,0
|
||||
C257.4,150.4,254.9,150.4,253.2,148.7z M274.2,157.8c-1.7-1.7-4.3-1.7-6,0s-1.7,4.3,0,6s4.3,1.7,6,0
|
||||
C275.9,162.1,275.9,159.5,274.2,157.8z" />
|
||||
</g>
|
||||
<g>
|
||||
@@ -208,7 +203,7 @@
|
||||
<path class="st3" d="M467.1,235.8l-40.4,40.1c-4.6,4.6-11.7,4.6-16.2,0l-68.3-68.3c-4.6-4.6-4.6-11.7,0-16.2l40.1-40.1
|
||||
c4.6-4.6,11.7-4.6,16.2,0l68.3,68.3C471.3,224.1,471.3,231.2,467.1,235.8z M348.2,197.7c-1.1,1.1-1.1,2.8,0,4l68.3,68.3
|
||||
c1.1,1.1,2.8,1.1,4,0l40.4-40.1c1.1-1.1,1.1-2.8,0-4l-68.3-68.3c-1.1-1.1-2.8-1.1-4,0L348.2,197.7z" />
|
||||
<path class="st2" d="M421.5,260.8c-1.7,1.7-4.3,1.7-6,0l-25-25c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l25,25
|
||||
<path class="st2" d="M421.5,260.8c-1.7,1.7-4.3,1.7-6,0l-25-25c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l25,25
|
||||
C423.3,256.5,423.3,259.1,421.5,260.8z M381.4,220.7c-0.9,0.9-2,1.4-3.1,1.1c-1.1,0-2.3-0.3-3.1-1.1c-0.9-0.9-1.4-2-1.1-3.1
|
||||
c0-0.3,0-0.6,0-0.9c0-0.3,0-0.6,0.3-0.9c0.3-0.3,0.3-0.6,0.3-0.9c0.9-1.1,2-2,3.4-2c1.1,0,2.3,0.3,3.1,1.1
|
||||
c0.3,0.3,0.3,0.3,0.6,0.6c0.3,0.3,0.3,0.6,0.3,0.9c0,0.3,0.3,0.6,0.3,0.9c0,0.3,0,0.6,0,0.9C382.9,218.7,382.3,219.8,381.4,220.7z
|
||||
@@ -222,25 +217,25 @@
|
||||
d="M905.4,490.4L798.2,383.1c-6-6-6-15.4,0-21.3l53.2-53.2c5.7-5.7,15.4-5.7,21.3,0l107.2,107.2" />
|
||||
<path class="st2" d="M892,364.3l-38.1,38.1c-2.8,2.8-7.7,2.8-10.5,0l-24.7-24.7c-2.8-2.8-2.8-7.7,0-10.5l38.1-38.1
|
||||
c2.8-2.8,7.7-2.8,10.5,0l24.7,24.7C894.9,356.7,894.9,361.5,892,364.3z" />
|
||||
<path class="st3" d="M983.3,419.5c-2,2-5.4,2-7.4,0l-107-107c-3.7-3.7-10.2-4-13.9,0l-53.2,53.2c-3.7,3.7-3.7,10,0,13.9
|
||||
l107.2,107.2c2,2,2,5.4,0,7.4c-2,2-5.4,2-7.4,0L794.5,387.1c-8-8-8-20.8,0-28.7l53.2-53.2c8-8,20.8-8,28.7,0l107.2,107.2
|
||||
<path class="st3" d="M983.3,419.5c-2,2-5.4,2-7.4,0l-107-107c-3.7-3.7-10.2-4-13.9,0l-53.2,53.2c-3.7,3.7-3.7,10,0,13.9L909,486.8
|
||||
c2,2,2,5.4,0,7.4s-5.4,2-7.4,0L794.5,387.1c-8-8-8-20.8,0-28.7l53.2-53.2c8-8,20.8-8,28.7,0l107.2,107.2
|
||||
C985.6,414.4,985.3,417.5,983.3,419.5z" />
|
||||
<path class="st4" d="M892,364.3l-38.1,38.1c-2.8,2.8-7.7,2.8-10.5,0l-24.7-24.7c-2.8-2.8-2.8-7.7,0-10.5l38.1-38.1
|
||||
c2.8-2.8,7.7-2.8,10.5,0l24.7,24.7C894.9,356.7,894.9,361.5,892,364.3z" />
|
||||
<path class="st3" d="M895.7,368l-38.1,38.1c-4.8,4.8-13.1,4.8-17.9,0l-24.7-24.7c-4.8-4.8-4.8-13.1,0-17.9l38.1-38.1
|
||||
<path class="st3" d="M895.7,368l-38.1,38.1c-4.8,4.8-13.1,4.8-17.9,0L815,381.4c-4.8-4.8-4.8-13.1,0-17.9l38.1-38.1
|
||||
c4.8-4.8,13.1-4.8,17.9,0l24.7,24.7C900.6,355,900.6,362.9,895.7,368z M822.3,370.9c-0.9,0.9-0.9,2.3,0,3.1l24.7,24.7
|
||||
c0.9,0.9,2.3,0.9,3.1,0l38.1-38.1c0.9-0.9,0.9-2.3,0-3.1l-24.7-24.7c-0.9-0.9-2.3-0.9-3.1,0L822.3,370.9z M994.1,409l-95.3,95.3
|
||||
c-2,2-5.4,2-7.4,0c-2-2-2-5.4,0-7.4l95.3-95.3c2-2,5.4-2,7.4,0C996.1,403.6,996.1,407,994.1,409z M988.4,392.8
|
||||
c-4.8,4.8-13.1,4.8-17.9,0l-43-43c-0.9-0.9-2.3-0.9-3.1,0l-5.4,5.4c-2,2-5.4,2-7.4,0c-2-2-2-5.4,0-7.4l5.4-5.4
|
||||
c4.8-4.8,13.1-4.8,17.9,0l43,43c0.9,0.9,2.3,0.9,3.1,0c0.9-0.9,0.9-2.3,0-3.1l-58.9-58.9c-2-2-2-5.4,0-7.4c2-2,5.4-2,7.4,0
|
||||
l58.9,58.9C993.3,379.7,993.3,388,988.4,392.8z" />
|
||||
c-2,2-5.4,2-7.4,0s-2-5.4,0-7.4l95.3-95.3c2-2,5.4-2,7.4,0C996.1,403.6,996.1,407,994.1,409z M988.4,392.8
|
||||
c-4.8,4.8-13.1,4.8-17.9,0l-43-43c-0.9-0.9-2.3-0.9-3.1,0l-5.4,5.4c-2,2-5.4,2-7.4,0s-2-5.4,0-7.4l5.4-5.4
|
||||
c4.8-4.8,13.1-4.8,17.9,0l43,43c0.9,0.9,2.3,0.9,3.1,0c0.9-0.9,0.9-2.3,0-3.1l-58.9-58.9c-2-2-2-5.4,0-7.4s5.4-2,7.4,0l58.9,58.9
|
||||
C993.3,379.7,993.3,388,988.4,392.8z" />
|
||||
<path class="st3" d="M934.7,318.3l-10.5,10.5c-2,2-5.4,2-7.4,0l-21.6-21.6c-2-2-2-5.4,0-7.4l1.7-1.7l-4.8-4.8c-2-2-2-5.4,0-7.4
|
||||
c2-2,5.4-2,7.4,0l4.8,4.8l1.4-1.4c2-2,5.4-2,7.4,0l21.3,21.3C936.7,312.9,936.7,316.3,934.7,318.3z M920.5,317.7l3.1-3.1
|
||||
l-13.9-13.9l-3.1,3.1L920.5,317.7z" />
|
||||
s5.4-2,7.4,0l4.8,4.8l1.4-1.4c2-2,5.4-2,7.4,0l21.3,21.3C936.7,312.9,936.7,316.3,934.7,318.3z M920.5,317.7l3.1-3.1l-13.9-13.9
|
||||
l-3.1,3.1L920.5,317.7z" />
|
||||
<g>
|
||||
<path class="st5" d="M923.9,444.6c-0.6,0.6-1.4,1.1-2.6,1.4c-2.8,0.6-5.7-1.1-6.3-3.7l-3.7-16.2l-3.7,5.4c-1.1,1.7-3.1,2.6-5.1,2
|
||||
c-2-0.6-3.7-2-4-4l-6.5-27c-0.6-2.8,1.1-5.7,3.7-6.3c2.8-0.6,5.7,1.1,6.3,3.7l3.7,16.2l3.7-5.4c1.1-1.7,3.1-2.6,5.1-2
|
||||
c2,0.6,3.7,2,4,4l6.3,27C925.6,441.7,925.3,443.4,923.9,444.6z" />
|
||||
s-3.7-2-4-4l-6.5-27c-0.6-2.8,1.1-5.7,3.7-6.3c2.8-0.6,5.7,1.1,6.3,3.7l3.7,16.2l3.7-5.4c1.1-1.7,3.1-2.6,5.1-2s3.7,2,4,4l6.3,27
|
||||
C925.6,441.7,925.3,443.4,923.9,444.6z" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
@@ -251,9 +246,9 @@
|
||||
<path class="st12" d="M749.8,413.3l3.7,3.7c1.1,1.1,1.4,2.8,0.6,4.6L708.6,496l-10-10l51.8-68.3
|
||||
C751.2,416.1,750.9,414.4,749.8,413.3z" />
|
||||
<g>
|
||||
<path class="st3" d="M706.6,498L663,454.5c-0.6-0.6-0.9-1.4-0.9-2.3c0-0.9,0.6-1.7,1.4-2l74.5-45.2c2.6-1.4,5.7-1.1,8,0.9
|
||||
l9.4,9.4c2,2,2.6,5.4,0.9,8l-45.5,74.2c-0.6,0.9-1.1,1.1-2,1.4C708,498.9,707.1,498.6,706.6,498z M669.6,453.1l38.4,38.4
|
||||
l43.5-71.7c0.3-0.3,0.3-0.9,0-0.9l-9.4-9.4c-0.3-0.3-0.6-0.3-0.9,0L669.6,453.1z" />
|
||||
<path class="st3" d="M706.6,498L663,454.5c-0.6-0.6-0.9-1.4-0.9-2.3s0.6-1.7,1.4-2L738,405c2.6-1.4,5.7-1.1,8,0.9l9.4,9.4
|
||||
c2,2,2.6,5.4,0.9,8l-45.5,74.2c-0.6,0.9-1.1,1.1-2,1.4C708,498.9,707.1,498.6,706.6,498z M669.6,453.1l38.4,38.4l43.5-71.7
|
||||
c0.3-0.3,0.3-0.9,0-0.9l-9.4-9.4c-0.3-0.3-0.6-0.3-0.9,0L669.6,453.1z" />
|
||||
<path class="st3" d="M655.6,447.1c-1.1-1.1-1.1-2.8,0-4s2.8-1.1,4,0l58,58l0,0c1.1,1.1,1.1,2.8,0,4s-2.8,1.1-4,0L655.6,447.1
|
||||
L655.6,447.1z" />
|
||||
</g>
|
||||
@@ -274,22 +269,21 @@
|
||||
c0.3-0.6,0.3-1.1,0.6-2l-23.3-23.3c-2.8-2.8-7.1-2.8-10,0l-2.3,2.3c-2.8,2.8-2.8,7.1,0,10L664.2,347.8L664.2,347.8z" />
|
||||
<path class="st3" d="M623.8,389.9l-0.3-0.3c-2.8-2.8-2.6-7.4,0.3-10.2c1.7-1.7,4.3-1.7,6,0c0.6,0.6,1.1,1.4,1.1,2.3l3.1-2.6
|
||||
c14.5-11.7,25-27.3,31.3-44.9l1.1-3.4c2.8-8.8,0.6-18.2-6-24.7l-64.3-64.3c-9.4-9.4-24.7-9.4-34.1,0l-35.8,35.8
|
||||
c-1.7,1.7-4.3,1.7-6,0c-1.7-1.7-1.7-4.3,0-6l35.8-35.8c12.8-12.8,33.6-12.8,46.4,0l64.3,64.3c8.8,8.8,11.9,21.6,8,33.6l-1.1,3.4
|
||||
c-6.5,19.1-18.2,35.8-33.8,48.6l-5.7,4.8C630.6,392.8,626.6,392.5,623.8,389.9z M644.8,388.2c-1.7-1.7-1.7-4.3,0-6l18.2-18.2
|
||||
c1.7-1.7,4.3-1.7,6,0c1.7,1.7,1.7,4.3,0,6l-18.2,18.2C649.4,389.9,646.5,389.9,644.8,388.2z" />
|
||||
<path class="st3" d="M599.6,361.2l-46.9-46.9c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l47.2,46.9c1.7,1.7,1.7,4.3,0,6
|
||||
c-1.7,1.7-4.3,1.7-6,0s-1.7-4.3,0-6l35.8-35.8c12.8-12.8,33.6-12.8,46.4,0l64.3,64.3c8.8,8.8,11.9,21.6,8,33.6l-1.1,3.4
|
||||
c-6.5,19.1-18.2,35.8-33.8,48.6l-5.7,4.8C630.6,392.8,626.6,392.5,623.8,389.9z M644.8,388.2c-1.7-1.7-1.7-4.3,0-6L663,364
|
||||
c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-18.2,18.2C649.4,389.9,646.5,389.9,644.8,388.2z" />
|
||||
<path class="st3" d="M599.6,361.2l-46.9-46.9c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l47.2,46.9c1.7,1.7,1.7,4.3,0,6
|
||||
C604.2,362.9,601.3,362.9,599.6,361.2z M531.3,292.7L516,277.3c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l15.4,15.4c1.7,1.7,1.7,4.3,0,6
|
||||
C535.6,294.4,533,294.4,531.3,292.7z M671.9,299.2l-66.6-66.6c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l66.6,66.6c1.7,1.7,1.7,4.3,0,6
|
||||
S673.6,300.9,671.9,299.2z M640.3,379.7c-1.7-1.7-1.7-4-0.3-6c7.7-8.8,5.4-26.5-4.6-36.4l-87.6-87.6c-1.7-1.7-1.7-4.3,0-6
|
||||
s4.3-1.7,6,0l87.6,87.6c13.1,13.1,15.4,35.8,4.8,48.1C644.8,381.1,642.3,381.4,640.3,379.7L640.3,379.7z M565.2,246.9l-6.3-6.3
|
||||
c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l6.3,6.3c1.7,1.7,1.7,4.3,0,6C569.7,248.6,566.9,248.6,565.2,246.9z M577.4,242.9
|
||||
l-6.3-6.3c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l6.3,6.3c1.7,1.7,1.7,4.3,0,6C581.7,244.6,579.1,244.6,577.4,242.9z" />
|
||||
c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l6.3,6.3c1.7,1.7,1.7,4.3,0,6C569.7,248.6,566.9,248.6,565.2,246.9z M577.4,242.9l-6.3-6.3
|
||||
c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l6.3,6.3c1.7,1.7,1.7,4.3,0,6C581.7,244.6,579.1,244.6,577.4,242.9z" />
|
||||
<path class="st2" d="M623.5,359.5c-5.4-5.4-13.9-5.4-19.3,0c-5.4,5.4-5.4,13.9,0,19.3l0,0c5.4,5.4,13.9,5.4,19.3,0
|
||||
C628.6,373.4,628.6,364.9,623.5,359.5L623.5,359.5z" />
|
||||
<path class="st3" d="M601,382c-7.1-7.1-6.8-18.5,0-25.3c6.8-6.8,18.5-6.8,25.3,0c6.8,6.8,7.1,18.5,0,25.3
|
||||
C619.5,389.1,608.1,388.8,601,382z M620.4,362.6c-3.7-3.7-9.7-3.7-13.4,0c-3.7,3.7-3.7,9.7,0,13.4c3.7,3.7,9.7,3.7,13.4,0
|
||||
C624.1,372.3,624.1,366.3,620.4,362.6z" />
|
||||
<path class="st2" d="M535.3,290.7c-5.4,5.4-5.4,13.9,0,19.3c5.4,5.4,13.9,5.4,19.3,0c2.3-2.3,3.7-5.1,4-8.2l-15.1-15.1
|
||||
<path class="st3" d="M601,382c-7.1-7.1-6.8-18.5,0-25.3s18.5-6.8,25.3,0s7.1,18.5,0,25.3C619.5,389.1,608.1,388.8,601,382z
|
||||
M620.4,362.6c-3.7-3.7-9.7-3.7-13.4,0s-3.7,9.7,0,13.4c3.7,3.7,9.7,3.7,13.4,0C624.1,372.3,624.1,366.3,620.4,362.6z" />
|
||||
<path class="st2" d="M535.3,290.7c-5.4,5.4-5.4,13.9,0,19.3s13.9,5.4,19.3,0c2.3-2.3,3.7-5.1,4-8.2l-15.1-15.1
|
||||
C540.4,287.3,537.6,288.4,535.3,290.7z" />
|
||||
<path class="st3" d="M532.5,313.1c-7.1-7.1-6.8-18.5,0-25.3c2.8-2.8,6.5-4.6,10.8-5.1c1.4,0,2.6,0.3,3.4,1.1l15.1,15.1
|
||||
c0.9,0.9,1.4,2.3,1.1,3.4c-0.3,4-2.3,8-5.1,10.8C550.7,320.3,539.3,320,532.5,313.1z M542.2,291.5c-1.4,0.3-2.6,1.1-3.7,2.3
|
||||
@@ -300,7 +294,7 @@
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M623.8,385.4l-2.6-2.6c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l2.6,2.6c1.7,1.7,1.7,4.3,0,6
|
||||
C628.1,387.1,625.5,387.1,623.8,385.4z" />
|
||||
S625.5,387.1,623.8,385.4z" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
@@ -308,35 +302,35 @@
|
||||
C611.3,101.5,575.1,152.2,554.4,172.9z" />
|
||||
<path class="st5" d="M514.8,63.4l48.1-11.7c1.7-0.3,2.6,1.7,1.1,2.6l-28.4,18.8l9.7,9.7c0.9,0.9,0.3,2.3-0.9,2.3l-43.8,6.8
|
||||
c-1.4,0.3-2.3-1.7-1.1-2.6l23.6-14.5l-9.4-9.4C513.4,64.8,513.7,63.7,514.8,63.4z" />
|
||||
<path class="st3" d="M595.6,115.5l-15.1-15.1c-7.1-7.1-7.1-18.8,0-26.2l8-8c1.7-1.7,4.3-1.7,6,0c1.7,1.7,1.7,4.3,0,6l-8,8
|
||||
<path class="st3" d="M595.6,115.5l-15.1-15.1c-7.1-7.1-7.1-18.8,0-26.2l8-8c1.7-1.7,4.3-1.7,6,0s1.7,4.3,0,6l-8,8
|
||||
c-4,4-4,10.2,0,13.9l15.1,15.1c1.7,1.7,1.7,4.3,0,6C599.9,117.2,597.3,117.2,595.6,115.5z" />
|
||||
<path class="st3" d="M548.4,178.9L447.7,78.2c-1.7-1.7-1.7-4.3,0-6l73.4-73.4c7.1-7.1,18.8-7.1,26.2,0l70.3,70.3l0,0l8,8
|
||||
c1.7,1.7,1.7,4.3,0,6c-1.7,1.7-4.3,1.7-6,0l-2.3-2.3c-8.2,31.3-41.5,76.2-60,94.7l-3.1,3.1C552.7,180.6,549.8,180.6,548.4,178.9z
|
||||
c1.7,1.7,1.7,4.3,0,6s-4.3,1.7-6,0l-2.3-2.3c-8.2,31.3-41.5,76.2-60,94.7l-3.1,3.1C552.7,180.6,549.8,180.6,548.4,178.9z
|
||||
M456.8,75.4l94.4,94.4c21.6-21.6,54.6-69.1,58.9-96.1L541.3,4.8c-4-4-10.2-4-13.9,0C527.1,5.1,456.8,75.4,456.8,75.4z" />
|
||||
<path class="st3" d="M548.4,178.9L443.7,74.2c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l104.7,104.7c1.7,1.7,1.7,4.3,0,6
|
||||
<path class="st3" d="M548.4,178.9L443.7,74.2c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l104.7,104.7c1.7,1.7,1.7,4.3,0,6
|
||||
C552.7,180.6,549.8,180.6,548.4,178.9z" />
|
||||
<path class="st2" d="M472.2,85.6c-5.4-5.4-13.9-5.4-19.3,0c-5.4,5.4-5.4,13.9,0,19.3l0,0c5.4,5.4,13.9,5.4,19.3,0
|
||||
<path class="st2" d="M472.2,85.6c-5.4-5.4-13.9-5.4-19.3,0s-5.4,13.9,0,19.3l0,0c5.4,5.4,13.9,5.4,19.3,0
|
||||
C477.6,99.8,477.6,91,472.2,85.6L472.2,85.6z M536.5,150.2c-5.4-5.4-13.9-5.4-19.3,0c-5.4,5.4-5.4,13.9,0,19.3l0,0
|
||||
c5.4,5.4,13.9,5.4,19.3,0C541.9,164.1,541.9,155.3,536.5,150.2L536.5,150.2z" />
|
||||
<path class="st3" d="M450,108.1c-7.1-7.1-6.8-18.5,0-25.3c6.8-6.8,18.5-6.8,25.3,0c6.8,6.8,6.8,18.5,0,25.3
|
||||
<path class="st3" d="M450,108.1c-7.1-7.1-6.8-18.5,0-25.3c6.8-6.8,18.5-6.8,25.3,0s6.8,18.5,0,25.3
|
||||
C468.2,114.9,456.8,115.2,450,108.1z M469,88.7c-3.7-3.7-9.7-3.7-13.4,0s-3.7,9.7,0,13.4c3.7,3.7,9.7,3.7,13.4,0
|
||||
C472.7,98.4,473,92.4,469,88.7z" />
|
||||
<g>
|
||||
<path class="st3" d="M514.3,172.3c-7.1-7.1-6.8-18.5,0-25.3c6.8-6.8,18.5-6.8,25.3,0c7.1,7.1,6.8,18.5,0,25.3
|
||||
C532.8,179.5,521.4,179.5,514.3,172.3z M533.6,153c-3.7-3.7-9.7-3.7-13.4,0c-3.7,3.7-3.7,9.7,0,13.4c3.7,3.7,9.7,3.7,13.4,0
|
||||
<path class="st3" d="M514.3,172.3c-7.1-7.1-6.8-18.5,0-25.3s18.5-6.8,25.3,0c7.1,7.1,6.8,18.5,0,25.3
|
||||
C532.8,179.5,521.4,179.5,514.3,172.3z M533.6,153c-3.7-3.7-9.7-3.7-13.4,0s-3.7,9.7,0,13.4s9.7,3.7,13.4,0
|
||||
C537,162.7,537.3,156.7,533.6,153z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M514.3,172.3l-84.5-84.5c-1.7-1.7-1.7-4.3,0-6c1.7-1.7,4.3-1.7,6,0l84.5,84.5c1.7,1.7,1.7,4.3,0,6
|
||||
<path class="st3" d="M514.3,172.3l-84.5-84.5c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l84.5,84.5c1.7,1.7,1.7,4.3,0,6
|
||||
C518.5,174.1,516,174.1,514.3,172.3z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M415.6,73.9c-0.3-0.3-0.3-0.3-0.6-0.6c0-0.3-0.3-0.6-0.3-0.9s-0.3-0.6-0.3-0.9s0-0.6,0-0.9s0-0.6,0-0.9
|
||||
c0-0.3,0-0.6,0.3-0.9c0.3,0,0.3-0.3,0.6-0.6c0.3-0.3,0.3-0.6,0.6-0.6c0.3,0,0.3-0.3,0.6-0.6c0.3-0.3,0.6-0.3,0.9-0.3
|
||||
s0.6,0,0.9-0.3s0.6,0,0.9,0c0.3,0,0.6,0,0.9,0c0.3,0,0.6,0,0.9,0.3c0.3,0.3,0.6,0.3,0.9,0.3c0.6,0.3,0.9,0.6,1.1,1.1
|
||||
c0.3,0.3,0.3,0.6,0.3,0.9s0.3,0.6,0.3,0.9c0,0.3,0,0.6,0,0.9c0,0.3,0,0.6,0,0.9s0,0.6-0.3,0.9c0,0.3-0.3,0.6-0.3,0.9
|
||||
c-0.3,0.3-0.3,0.6-0.6,0.6c-0.3,0-0.3,0.3-0.6,0.6c-0.3,0.3-0.6,0.3-0.9,0.3s-0.6,0.3-0.9,0.3c-0.3,0-0.6,0-0.9,0
|
||||
c-0.3,0-0.6,0-0.9,0s-0.6,0-0.9-0.3c-0.3,0-0.6-0.3-0.9-0.3C416.1,74.5,415.9,73.9,415.6,73.9z" />
|
||||
s0-0.6,0.3-0.9c0.3,0,0.3-0.3,0.6-0.6c0.3-0.3,0.3-0.6,0.6-0.6c0.3,0,0.3-0.3,0.6-0.6c0.3-0.3,0.6-0.3,0.9-0.3s0.6,0,0.9-0.3
|
||||
s0.6,0,0.9,0s0.6,0,0.9,0s0.6,0,0.9,0.3s0.6,0.3,0.9,0.3c0.6,0.3,0.9,0.6,1.1,1.1c0.3,0.3,0.3,0.6,0.3,0.9s0.3,0.6,0.3,0.9
|
||||
s0,0.6,0,0.9s0,0.6,0,0.9s0,0.6-0.3,0.9c0,0.3-0.3,0.6-0.3,0.9c-0.3,0.3-0.3,0.6-0.6,0.6c-0.3,0-0.3,0.3-0.6,0.6
|
||||
c-0.3,0.3-0.6,0.3-0.9,0.3s-0.6,0.3-0.9,0.3s-0.6,0-0.9,0s-0.6,0-0.9,0s-0.6,0-0.9-0.3c-0.3,0-0.6-0.3-0.9-0.3
|
||||
C416.1,74.5,415.9,73.9,415.6,73.9z" />
|
||||
</g>
|
||||
</g>
|
||||
<path class="st3"
|
||||
@@ -350,8 +344,8 @@
|
||||
<path class="st3" d="M78.2,8.2l-8.8-8.5c-1.1-1.1-1.1-2.8,0-4c1.1-1.1,2.8-1.1,4,0l8.8,8.8c1.1,1.1,1.1,2.8,0,4
|
||||
C81.4,9.6,79.4,9.4,78.2,8.2z" />
|
||||
<g>
|
||||
<path class="st2" d="M57.7,69.9L44.7,56.6c-3.7-3.7-3.7-10,0-13.7L82.2,5.4c3.7-3.7,10-3.7,13.7,0l13.4,13.4
|
||||
c3.7,3.7,3.7,10,0,13.7L71.4,69.9C67.7,73.6,61.7,73.6,57.7,69.9z" />
|
||||
<path class="st2" d="M57.7,69.9l-13-13.3c-3.7-3.7-3.7-10,0-13.7L82.2,5.4c3.7-3.7,10-3.7,13.7,0l13.4,13.4c3.7,3.7,3.7,10,0,13.7
|
||||
L71.4,69.9C67.7,73.6,61.7,73.6,57.7,69.9z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st11" d="M95.6,18.2c-4.3-4.3-11.1-4.3-15.1,0c-4.3,4.3-4.3,11.1,0,15.1l0,0c4.3,4.3,11.1,4.3,15.1,0
|
||||
@@ -371,8 +365,8 @@
|
||||
C32.4,85.3,30.4,85.3,29.3,84.2z" />
|
||||
</g>
|
||||
<g>
|
||||
<path class="st5" d="M802.4,224.7l-70.5-70.5l-46.9,46.9c-4.3,4.3-4.3,11.4,0,15.6l54.9,54.9c4.3,4.3,11.4,4.3,15.6,0L802.4,224.7
|
||||
z" />
|
||||
<path class="st5" d="M802.4,224.7l-70.5-70.5L685,201.1c-4.3,4.3-4.3,11.4,0,15.6l54.9,54.9c4.3,4.3,11.4,4.3,15.6,0L802.4,224.7z
|
||||
" />
|
||||
<path class="st2" d="M731,207.9l33.3-8.2c1.1-0.3,1.7,1.1,0.9,1.7l-19.6,13.1l6.8,6.8c0.6,0.6,0.3,1.4-0.6,1.7l-30.2,4.8
|
||||
c-1.1,0.3-1.4-1.1-0.6-1.7l16.2-9.7l-6.5-6.5C729.9,209,730.2,208.2,731,207.9z" />
|
||||
<path class="st6" d="M794.7,217l-45.5,45.5c-0.9,0.9-2.3,0.9-3.1,0L712.5,229c-2.3-2.3-5.7-2.3-8,0l0,0c-2.3,2.3-2.3,5.7,0,8
|
||||
@@ -383,11 +377,11 @@
|
||||
c-3.1,3.1-3.1,8,0,11.1l54.9,54.9c3.1,3.1,8,3.1,11.1,0l46.9-46.9c1.4-1.4,3.4-1.4,4.8,0c1.4,1.4,1.4,3.4,0,4.8l-46.9,46.9
|
||||
C752.1,279.6,743.3,279.6,737.6,274.2z" />
|
||||
<path class="st2" d="M714,188c-1.4-1.4-1.4-3.4,0-4.8l23.6-23.6c1.4-1.4,3.4-1.4,4.8,0c1.4,1.4,1.4,3.4,0,4.8L718.8,188
|
||||
C717.4,189.1,715.4,189.1,714,188z M702.3,199.7c-0.3-0.3-0.3-0.3-0.3-0.6c-0.3-0.3-0.3-0.3-0.3-0.6c0-0.3,0-0.3-0.3-0.6
|
||||
C717.4,189.1,715.4,189.1,714,188z M702.3,199.7c-0.3-0.3-0.3-0.3-0.3-0.6c-0.3-0.3-0.3-0.3-0.3-0.6s0-0.3-0.3-0.6
|
||||
c0-0.3,0-0.3,0-0.6s0-0.6,0-0.6c0-0.3,0-0.3,0.3-0.6c0-0.3,0.3-0.3,0.3-0.6s0.3,0,0.3-0.3c0.3-0.3,0.3-0.3,0.6-0.3
|
||||
c0.3,0,0.3-0.3,0.6-0.3s0.6-0.3,0.6-0.3c0.3,0,0.3,0,0.6,0s0.6,0,0.6,0s0.3,0,0.6,0.3c0.3,0,0.3,0.3,0.6,0.3
|
||||
c0.9,0.6,1.4,1.7,1.4,2.8c0,0.3,0,0.6,0,0.6c0,0.3,0,0.3-0.3,0.6c0,0.3-0.3,0.3-0.3,0.6s-0.6,0.3-0.6,0.3
|
||||
c-0.6,0.6-1.4,1.1-2.3,0.9C703.7,200.8,702.9,200.2,702.3,199.7z" />
|
||||
s0.3-0.3,0.6-0.3s0.6-0.3,0.6-0.3c0.3,0,0.3,0,0.6,0s0.6,0,0.6,0s0.3,0,0.6,0.3c0.3,0,0.3,0.3,0.6,0.3c0.9,0.6,1.4,1.7,1.4,2.8
|
||||
c0,0.3,0,0.6,0,0.6c0,0.3,0,0.3-0.3,0.6c0,0.3-0.3,0.3-0.3,0.6s-0.6,0.3-0.6,0.3c-0.6,0.6-1.4,1.1-2.3,0.9
|
||||
C703.7,200.8,702.9,200.2,702.3,199.7z" />
|
||||
<path class="st3" d="M804.1,230.9l-78.2-78.2c-1.4-1.4-1.4-3.4,0-4.8l9.4-9.4c1.4-1.4,3.4-1.4,4.8,0l78.2,78.2
|
||||
c1.4,1.4,1.4,3.4,0,4.8l-9.4,9.4C807.5,232.4,805.3,232.4,804.1,230.9z M732.7,150.4l73.7,73.7l4.8-4.8l-73.7-73.7L732.7,150.4z" />
|
||||
<path class="st3" d="M758.6,166.7c-1.4-1.4-1.4-3.4,0-4.8l7.1-7.1l-7.1-7.1l-0.9,0.9c-1.4,1.4-3.4,1.4-4.8,0
|
||||
@@ -401,20 +395,20 @@
|
||||
<path class="st8" d="M909.4,164.4C909.4,164.1,909.4,164.1,909.4,164.4c-1.1-0.3-2-0.3-2.8-0.3c-8.8,0.3-18.2,2.8-28.2,6.8l0,0
|
||||
l31,31C919.6,191.7,919.6,174.6,909.4,164.4 M991,246.6c-10.2-7.7-25-6.8-34.7,2.6l19.3,19.3l10.8-10.8
|
||||
C989.6,254.6,991,250.6,991,246.6" />
|
||||
<path class="st3" d="M972.8,271.6l-97.6-97.6c-1.1-1.1-1.4-2.3-1.1-3.7c0.3-1.4,1.1-2.6,2.6-3.1c27.9-11.9,48.6-9.4,65.7,8
|
||||
l0.3,0.3c1.7,1.7,1.7,4.3,0,6c-1.7,1.7-4.3,1.7-6,0l-0.3-0.3c-13.4-13.4-29.3-16.2-50.6-8.2l89.9,89.9l8-8
|
||||
c4.3-4.3,4.3-11.4,0-15.6l-34.4-34.7c-1.1-1.1-1.4-2.3-1.1-3.7l1.7-10.8c0.3-2.3,2.6-4,4.8-3.4c2.3,0.3,4,2.6,3.4,4.8l-1.4,8.5
|
||||
l33.3,33c7.7,7.7,7.7,20.2,0,27.9l-10.8,10.8C977.4,273.3,974.5,273.3,972.8,271.6z" />
|
||||
<path class="st2" d="M901.4,172.3c-6-6-15.6-6-21.6,0c-6,6-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0
|
||||
C907.4,188,907.4,178.3,901.4,172.3L901.4,172.3z M957.7,216.4c-3.4-3.4-8.8-3.4-11.9,0c-3.4,3.4-3.4,8.8,0,11.9
|
||||
c3.4,3.1,8.8,3.4,11.9,0C960.9,225,960.9,219.6,957.7,216.4z" />
|
||||
<path class="st3" d="M876.7,197.1c-7.7-7.7-7.7-20.2,0-27.9c7.7-7.7,20.2-7.7,27.9,0s7.7,20.2,0,27.9
|
||||
C896.6,204.8,884.3,204.8,876.7,197.1z M898.3,175.2c-4.3-4.3-11.4-4.3-15.6,0c-4.3,4.3-4.3,11.4,0,15.6c4.3,4.3,11.4,4.3,15.6,0
|
||||
C902.5,186.6,902.5,179.7,898.3,175.2z" />
|
||||
<path class="st2" d="M986.2,257.1c-6-6-15.6-6-21.6,0c-6,6-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0
|
||||
C992.1,272.8,992.1,263.1,986.2,257.1L986.2,257.1z" />
|
||||
<path class="st3" d="M972.8,271.6L875.2,174c-1.1-1.1-1.4-2.3-1.1-3.7c0.3-1.4,1.1-2.6,2.6-3.1c27.9-11.9,48.6-9.4,65.7,8l0.3,0.3
|
||||
c1.7,1.7,1.7,4.3,0,6s-4.3,1.7-6,0l-0.3-0.3c-13.4-13.4-29.3-16.2-50.6-8.2l89.9,89.9l8-8c4.3-4.3,4.3-11.4,0-15.6l-34.4-34.7
|
||||
c-1.1-1.1-1.4-2.3-1.1-3.7l1.7-10.8c0.3-2.3,2.6-4,4.8-3.4c2.3,0.3,4,2.6,3.4,4.8l-1.4,8.5l33.3,33c7.7,7.7,7.7,20.2,0,27.9
|
||||
l-10.8,10.8C977.4,273.3,974.5,273.3,972.8,271.6z" />
|
||||
<path class="st2" d="M901.4,172.3c-6-6-15.6-6-21.6,0s-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0C907.4,188,907.4,178.3,901.4,172.3
|
||||
L901.4,172.3z M957.7,216.4c-3.4-3.4-8.8-3.4-11.9,0c-3.4,3.4-3.4,8.8,0,11.9c3.4,3.1,8.8,3.4,11.9,0
|
||||
C960.9,225,960.9,219.6,957.7,216.4z" />
|
||||
<path class="st3" d="M876.7,197.1c-7.7-7.7-7.7-20.2,0-27.9s20.2-7.7,27.9,0s7.7,20.2,0,27.9C896.6,204.8,884.3,204.8,876.7,197.1
|
||||
z M898.3,175.2c-4.3-4.3-11.4-4.3-15.6,0c-4.3,4.3-4.3,11.4,0,15.6c4.3,4.3,11.4,4.3,15.6,0C902.5,186.6,902.5,179.7,898.3,175.2z
|
||||
" />
|
||||
<path class="st2" d="M986.2,257.1c-6-6-15.6-6-21.6,0s-6,15.6,0,21.6l0,0c6,6,15.6,6,21.6,0C992.1,272.8,992.1,263.1,986.2,257.1
|
||||
L986.2,257.1z" />
|
||||
<path class="st3" d="M961.4,281.9c-7.7-7.7-7.7-20.2,0-27.9c7.7-7.7,20.2-7.7,27.9,0c7.7,7.7,7.7,20.2,0,27.9
|
||||
C981.6,289.5,969.1,289.5,961.4,281.9z M983.3,260.2c-4.3-4.3-11.4-4.3-15.6,0s-4.3,11.4,0,15.6c4.3,4.3,11.4,4.3,15.6,0
|
||||
C981.6,289.5,969.1,289.5,961.4,281.9z M983.3,260.2c-4.3-4.3-11.4-4.3-15.6,0c-4.2,4.3-4.3,11.4,0,15.6c4.3,4.3,11.4,4.3,15.6,0
|
||||
C987.6,271.6,987.6,264.5,983.3,260.2z" />
|
||||
<path class="st3" d="M961.4,281.9l-98.1-98.1c-1.7-1.7-1.7-4.3,0-6s4.3-1.7,6,0l98.1,98.1c1.7,1.7,1.7,4.3,0,6
|
||||
S963.1,283.6,961.4,281.9z M851.3,171.8c-0.9-0.9-1.4-2-1.1-3.1c0-1.1,0.3-2.3,1.1-3.1c0.9-0.9,2-1.4,3.1-1.1c2.3,0,4.3,2,4.3,4.3
|
||||
@@ -423,7 +417,7 @@
|
||||
<g>
|
||||
<path class="st3" d="M789.9,46.3c0-8.5,6.8-15.4,15.4-15.4c8.5,0,15.4,6.8,15.4,15.4s-6.8,15.4-15.4,15.4 M752.6,8.8
|
||||
c0-8.5,6.8-15.4,15.4-15.4s15.4,6.8,15.4,15.4s-6.8,15.4-15.4,15.4" />
|
||||
<path class="st11" d="M782.2,91.6l-59.7-59.7c-0.6-0.6-0.6-1.7,0-2.6l19.3-19.3c0.6-0.6,1.7-0.6,2.6,0l59.7,59.7
|
||||
<path class="st11" d="M782.2,91.6l-59.7-59.7c-0.6-0.6-0.6-1.7,0-2.6L741.8,10c0.6-0.6,1.7-0.6,2.6,0l59.7,59.7
|
||||
c0.6,0.6,0.6,1.7,0,2.6l-19.3,19.3C783.9,92.4,782.8,92.4,782.2,91.6z" />
|
||||
<path class="st2" d="M725.6,26.4l-3.1,3.1c-0.6,0.6-0.6,1.7,0,2.6l5.1,5.1l40.4-3.4l0,0L757.2,23L725.6,26.4z M737.3,46.9
|
||||
l-0.6-0.6l11.4,11.4l40.4-3.1l0,0l-10.8-10.8L737.3,46.9z M801,75.6l3.1-3.1c0.6-0.6,0.6-1.7,0-2.6l-5.1-5.1L758.6,68l0,0
|
||||
@@ -437,7 +431,7 @@
|
||||
</g>
|
||||
<rect y="-34.4" class="st13" width="1024" height="568.9" />
|
||||
</g>
|
||||
<g id="Ebene_2">
|
||||
<g id="Ebene_2_1_">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
@@ -455,19 +449,36 @@
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st17" d="M267.6,140.1c-37,0-67,30-67,67c0,50.5,56.4,76.9,63.2,148.9c0.2,2.1,1.9,3.6,4,3.6c2.1,0,3.8-1.5,4-3.6
|
||||
<path class="st17" d="M267.6,140.1c-37,0-67,30-67,67c0,50.5,56.4,76.9,63.2,148.9c0.2,2.1,1.9,3.6,4,3.6s3.8-1.5,4-3.6
|
||||
c6.8-72,63.2-98.4,63.2-148.9C334.5,169.9,304.5,140.1,267.6,140.1z" />
|
||||
<path class="st18" d="M267.6,141.6c36.8,0,66.5,29.6,67,66.1c0-0.2,0-0.4,0-0.6c0-37-30-67-67-67s-67,29.8-67,67
|
||||
c0,0.2,0,0.4,0,0.6C201,171.2,230.8,141.6,267.6,141.6L267.6,141.6z" />
|
||||
<path class="st19" d="M271.6,354.4c-0.2,2.1-1.9,3.6-4,3.6s-3.8-1.5-4-3.6c-6.5-71.8-62.5-98.2-63-148.1c0,0.4,0,0.6,0,1.1
|
||||
c0,50.5,56.4,76.9,63.2,148.9c0.2,2.1,1.9,3.6,4,3.6c2.1,0,3.8-1.5,4-3.6c6.8-72,63.2-98.4,63.2-148.9c0-0.4,0-0.6,0-1.1
|
||||
c0,50.5,56.4,76.9,63.2,148.9c0.2,2.1,1.9,3.6,4,3.6s3.8-1.5,4-3.6c6.8-72,63.2-98.4,63.2-148.9c0-0.4,0-0.6,0-1.1
|
||||
C334.1,256.1,278.1,282.5,271.6,354.4L271.6,354.4z" />
|
||||
</g>
|
||||
<path class="st20"
|
||||
d="M252.2,174.4v40.6h11v33.2l25.8-44.4h-14.8l14.8-29.6C289.1,174.4,252.2,174.4,252.2,174.4z" />
|
||||
d="M252.2,174.4V215h11v33.2l25.8-44.4h-14.8l14.8-29.6C289.1,174.4,252.2,174.4,252.2,174.4z" />
|
||||
</g>
|
||||
</g>
|
||||
<text transform="matrix(1 0 0 1 417.7245 289.5375)" class="st2 st21 st22">EVMap</text>
|
||||
<g class="st21">
|
||||
<path class="st2"
|
||||
d="M483.6,243h-45.4v39.6h52.2v6.9H430v-97.1h60.1v6.9h-51.9v36.7h45.4V243z" />
|
||||
<path class="st2"
|
||||
d="M536.9,277.5l0.5,2.1l0.6-2.1l30.5-85.1h9l-36.1,97.1h-7.9l-36.1-97.1h8.9L536.9,277.5z" />
|
||||
<path class="st2" d="M602.7,192.5l35.8,85.7l35.9-85.7h10.9v97.1h-8.2v-42.3l0.7-43.3l-36.1,85.6h-6.3l-36-85.3l0.7,42.7v42.5
|
||||
h-8.2v-97.1H602.7z" />
|
||||
<path class="st2" d="M753.7,289.5c-0.8-2.3-1.3-5.6-1.5-10.1c-2.8,3.6-6.4,6.5-10.7,8.4c-4.3,2-8.9,3-13.8,3
|
||||
c-6.9,0-12.5-1.9-16.8-5.8c-4.3-3.9-6.4-8.8-6.4-14.7c0-7,2.9-12.6,8.8-16.7c5.8-4.1,14-6.1,24.4-6.1h14.5v-8.2
|
||||
c0-5.2-1.6-9.2-4.8-12.2c-3.2-3-7.8-4.4-13.9-4.4c-5.6,0-10.2,1.4-13.8,4.3c-3.6,2.8-5.5,6.3-5.5,10.3l-8-0.1
|
||||
c0-5.7,2.7-10.7,8-14.9c5.3-4.2,11.9-6.3,19.7-6.3c8,0,14.4,2,19,6c4.6,4,7,9.6,7.2,16.8v34.1c0,7,0.7,12.2,2.2,15.7v0.8H753.7z
|
||||
M728.6,283.8c5.3,0,10.1-1.3,14.3-3.9c4.2-2.6,7.3-6,9.2-10.3v-15.9h-14.3c-8,0.1-14.2,1.5-18.7,4.4c-4.5,2.8-6.7,6.7-6.7,11.6
|
||||
c0,4,1.5,7.4,4.5,10.1S723.8,283.8,728.6,283.8z" />
|
||||
<path class="st2" d="M839.3,254.2c0,11.2-2.5,20.2-7.5,26.8c-5,6.6-11.6,9.9-20,9.9c-9.9,0-17.4-3.5-22.7-10.4v36.8h-7.9v-99.9
|
||||
h7.4l0.4,10.2c5.2-7.7,12.7-11.5,22.6-11.5c8.6,0,15.4,3.3,20.3,9.8c4.9,6.5,7.4,15.6,7.4,27.2V254.2z M831.3,252.8
|
||||
c0-9.2-1.9-16.5-5.7-21.8c-3.8-5.3-9-8-15.8-8c-4.9,0-9.1,1.2-12.6,3.5c-3.5,2.4-6.2,5.8-8.1,10.3v34.6c1.9,4.1,4.6,7.3,8.2,9.5
|
||||
c3.6,2.2,7.8,3.3,12.6,3.3c6.7,0,11.9-2.7,15.7-8C829.4,270.7,831.3,263,831.3,252.8z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB |
27
_img/map_marker_charging_multiple.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 233.8 368.4" style="enable-background:new 0 0 233.8 368.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#B5B5B5;}
|
||||
.st2{fill:#808080;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M109.8,0h13.6c33.9,1.9,67.1,18.5,87.7,45.8c13.5,17.2,21,38.6,22.7,60.3v8.1c-0.8,42.1-27.7,76.6-51,109.4
|
||||
c-26.2,37-50.4,77.3-57.1,122.9c-1.8,7.7,0.4,18.5-8.9,22c-2.2-1.7-4.7-3.1-6.2-5.4c-2.7-25.5-9.1-50.7-20-73.9
|
||||
c-12.3-27.1-29.5-51.6-47-75.6C33,199,23,184.2,14.7,168.3c-13-23.8-17.9-51.9-12.5-78.6C6.6,68.6,17.6,49.1,32.8,34
|
||||
C53.3,14,81.1,1.8,109.8,0z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<polygon class="st1"
|
||||
points="143.2,109.4 123.5,143.2 123.5,181.3 166.9,106.9 144.7,106.9 " />
|
||||
<path class="st1"
|
||||
d="M122.2,101.9h16.7h5.7l22.3-44.6c0,0-10.2,0-22.4,0l-1.1,2.2L122.2,101.9z" />
|
||||
<path class="st2" d="M138.9,57.3c-9.7,0-19.8,0-26.4,0c-2.5,0-5.1,0-7.6,0c-8.2,0-16.1,0-21.4,0c-4.1,0-6.6,0-6.6,0v68.2h18.6v55.8
|
||||
l43.4-74.4h-24.8L138.9,57.3z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
6
_img/paypal.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<svg fill="#000000" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24px"
|
||||
height="24px">
|
||||
<path
|
||||
d="M20.055,7.713c-0.677-0.842-1.673-1.41-2.764-1.615C16.773,3.971,14.877,3,13.045,3H7.057C6.425,3,5.878,3.409,5.689,4.009 c-0.015,0.04-0.026,0.082-0.036,0.125L3.034,16.262c-0.009,0.041-0.015,0.083-0.019,0.125C3.006,16.449,3,16.513,3,16.56 C3,17.354,3.648,18,4.444,18h2.316l-0.267,1.262c-0.008,0.04-0.014,0.081-0.018,0.121c-0.009,0.063-0.016,0.126-0.016,0.173 C6.461,20.353,7.109,21,7.905,21h3.259c0.056,0,0.111-0.005,0.166-0.015c0.549-0.063,1.011-0.437,1.191-0.963 c0.021-0.05,0.038-0.103,0.05-0.156L13.475,16h1.398c3.365,0,5.38-1.445,5.989-4.295C21.278,9.752,20.653,8.456,20.055,7.713z M5.137,16L7.512,5h5.533c0.293,0,1.5,0.061,2.078,1.013h-4.706c-0.626,0-1.17,0.401-1.363,0.99c-0.019,0.049-0.033,0.1-0.043,0.151 l-1.034,5.093L7.183,16H5.137z M18.906,11.287C18.5,13.188,17.293,14,14.873,14h-1.857c-0.823,0-1.338,0.652-1.405,1.198L10.721,19 H8.594l1.271-6h1.444c4.259,0,5.665-2.394,6.094-4.402c0.027-0.128,0.045-0.256,0.057-0.382c0.378,0.151,0.749,0.393,1.038,0.751 C18.971,9.557,19.108,10.337,18.906,11.287z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
130
_img/powered_by_chargeprice.svg
Normal file
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="363.80554mm"
|
||||
height="72.554214mm" viewBox="0 0 1289.0747 257.0819" id="svg4985" version="1.1"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)" sodipodi:docname="logo_text_small2.svg"
|
||||
inkscape:export-filename="/home/nih/Desktop/cp/logos/powered_by/logo_text_white_small.png"
|
||||
inkscape:export-xdpi="42.009968" inkscape:export-ydpi="42.009968">
|
||||
<defs id="defs4987">
|
||||
<inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 152.57443 : 1"
|
||||
inkscape:vp_y="0 : 1000.0001 : 0" inkscape:vp_z="305.14877 : 152.57443 : 1"
|
||||
inkscape:persp3d-origin="152.57439 : 101.71629 : 1" id="perspective4145" />
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" inkscape:cx="572.73795"
|
||||
inkscape:cy="173.48708" inkscape:document-units="px" inkscape:current-layer="layer4"
|
||||
showgrid="false" fit-margin-top="0" fit-margin-left="0" fit-margin-right="0"
|
||||
fit-margin-bottom="0" inkscape:window-width="1857" inkscape:window-height="1052"
|
||||
inkscape:window-x="63" inkscape:window-y="0" inkscape:window-maximized="1" />
|
||||
<metadata id="metadata4990">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g inkscape:groupmode="layer" id="layer4" inkscape:label="Layer 2"
|
||||
transform="translate(-220.97188,-392.31605)">
|
||||
<g aria-label="chargeprice" transform="matrix(0.93750004,0,0,0.93750004,231.60533,392.30136)"
|
||||
style="font-style:normal;font-weight:normal;font-size:25px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="flowRoot825">
|
||||
<path
|
||||
d="m 350.50175,132.92433 q 15.168,0 25.152,4.8 10.176,4.8 10.176,12.288 0,3.264 -2.112,5.952 -2.112,2.496 -5.376,2.496 -2.496,0 -4.032,-0.768 -1.344,-0.768 -3.84,-2.496 -1.152,-1.152 -3.648,-2.688 -2.304,-1.152 -6.528,-1.92 -4.224,-0.768 -7.68,-0.768 -9.984,0 -17.664,4.608 -7.68,4.608 -11.904,12.864 -4.224,8.064 -4.224,18.048 0,10.176 4.032,18.24 4.224,8.064 11.712,12.672 7.488,4.608 17.088,4.608 9.984,0 16.128,-3.072 1.344,-0.768 3.648,-2.496 1.92,-1.536 3.264,-2.304 1.536,-0.768 3.648,-0.768 3.84,0 5.952,2.496 2.304,2.304 2.304,6.144 0,4.032 -5.184,8.064 -4.992,3.84 -13.632,6.336 -8.448,2.496 -18.24,2.496 -14.592,0 -25.728,-6.72 -11.136,-6.912 -17.28,-18.816 -5.952,-12.096 -5.952,-26.88 0,-14.784 6.336,-26.688 6.336,-12.096 17.664,-18.816 11.328,-6.912 25.92,-6.912 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path824" />
|
||||
<path
|
||||
d="m 455.46275,133.50033 q 33.792,0 33.792,41.856 v 51.264 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -3.84,0 -6.528,-2.688 -2.496,-2.688 -2.496,-6.528 v -51.264 q 0,-24.96 -21.12,-24.96 -11.328,0 -18.816,7.296 -7.488,7.104 -7.488,17.664 v 51.264 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -4.032,0 -6.528,-2.496 -2.496,-2.688 -2.496,-6.72 v -123.648 q 0,-3.840001 2.496,-6.528001 2.688,-2.688 6.528,-2.688 4.032,0 6.528,2.688 2.688,2.688 2.688,6.528001 v 48.576 q 4.8,-7.488 13.44,-12.672 8.64,-5.376 18.432,-5.376 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path826" />
|
||||
<path
|
||||
d="m 597.95075,133.88433 q 4.032,0 6.528,2.688 2.688,2.496 2.688,6.72 v 83.328 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -4.032,0 -6.528,-2.496 -2.496,-2.688 -2.496,-6.72 v -4.992 q -4.992,6.72 -13.632,11.52 -8.64,4.608 -18.624,4.608 -13.056,0 -23.808,-6.72 -10.56,-6.72 -16.704,-18.624 -5.952,-12.096 -5.952,-27.072 0,-14.976 5.952,-26.88 6.144,-12.096 16.704,-18.816 10.56,-6.72 23.232,-6.72 10.176,0 18.816,4.224 8.832,4.224 14.016,10.752 v -4.608 q 0,-4.032 2.496,-6.72 2.496,-2.688 6.528,-2.688 z m -39.168,86.976 q 9.024,0 15.936,-4.608 7.104,-4.608 10.944,-12.672 4.032,-8.064 4.032,-18.24 0,-9.984 -4.032,-18.048 -3.84,-8.064 -10.944,-12.672 -6.912,-4.8 -15.936,-4.8 -9.024,0 -16.128,4.608 -6.912,4.608 -10.944,12.672 -3.84,8.064 -3.84,18.24 0,10.176 3.84,18.24 4.032,8.064 10.944,12.672 7.104,4.608 16.128,4.608 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path828" />
|
||||
<path
|
||||
d="m 684.21275,132.92433 q 4.992,0 8.64,2.688 3.648,2.496 3.648,6.336 0,4.608 -2.496,7.104 -2.304,2.304 -5.76,2.304 -1.728,0 -5.184,-1.152 -4.032,-1.344 -6.336,-1.344 -5.952,0 -11.712,4.224 -5.568,4.032 -9.216,11.328 -3.456,7.104 -3.456,15.936 v 46.272 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -4.032,0 -6.528,-2.496 -2.496,-2.688 -2.496,-6.72 v -82.176 q 0,-3.84 2.496,-6.528 2.688,-2.688 6.528,-2.688 4.032,0 6.528,2.688 2.688,2.688 2.688,6.528 v 9.792 q 4.224,-9.408 12.672,-15.168 8.448,-5.952 19.2,-6.144 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path830" />
|
||||
<path
|
||||
d="m 794.32175,133.88433 q 4.032,0 6.528,2.688 2.688,2.496 2.688,6.72 v 84.48 q 0,15.552 -6.72,25.92 -6.528,10.56 -17.856,15.552 -11.328,4.992 -25.536,4.992 -7.68,0 -18.048,-2.688 -10.176,-2.688 -13.056,-5.568 -5.952,-3.072 -5.952,-7.68 0,-1.152 0.768,-3.072 2.112,-4.8 7.104,-4.8 2.496,0 5.376,1.152 15.36,5.952 24,5.952 15.36,0 23.424,-7.488 8.256,-7.296 8.256,-20.16 v -10.368 q -4.032,7.488 -13.632,12.864 -9.408,5.376 -19.968,5.376 -13.248,0 -24.192,-6.72 -10.944,-6.72 -17.28,-18.624 -6.144,-12.096 -6.144,-27.072 0,-14.976 6.144,-26.88 6.336,-12.096 17.088,-18.816 10.944,-6.72 24,-6.72 10.56,0 19.584,4.8 9.216,4.8 14.4,11.712 v -6.144 q 0,-4.032 2.496,-6.72 2.496,-2.688 6.528,-2.688 z m -40.512,86.976 q 9.408,0 16.704,-4.416 7.296,-4.608 11.328,-12.672 4.224,-8.256 4.224,-18.432 0,-10.176 -4.224,-18.24 -4.032,-8.064 -11.328,-12.672 -7.296,-4.608 -16.704,-4.608 -9.216,0 -16.512,4.608 -7.296,4.608 -11.52,12.864 -4.032,8.064 -4.032,18.048 0,9.984 4.032,18.24 4.224,8.064 11.52,12.672 7.296,4.608 16.512,4.608 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path832" />
|
||||
<path
|
||||
d="m 915.17075,181.69233 q -0.192,3.456 -2.88,5.952 -2.688,2.304 -6.336,2.304 h -67.584 q 1.344,14.016 10.56,22.464 9.408,8.448 22.848,8.448 9.216,0 14.976,-2.688 5.76,-2.688 10.176,-6.912 2.88,-1.728 5.568,-1.728 3.264,0 5.376,2.304 2.304,2.304 2.304,5.376 0,4.032 -3.84,7.296 -5.568,5.568 -14.784,9.408 -9.216,3.84 -18.816,3.84 -15.552,0 -27.456,-6.528 -11.712,-6.528 -18.24,-18.24 -6.336,-11.712 -6.336,-26.496 0,-16.128 6.528,-28.224 6.72,-12.288 17.472,-18.816 10.944,-6.528 23.424,-6.528 12.288,0 23.04,6.336 10.752,6.336 17.28,17.472 6.528,11.136 6.72,24.96 z m -47.04,-31.872 q -10.752,0 -18.624,6.144 -7.872,5.952 -10.368,18.624 h 56.64 v -1.536 q -0.96,-10.176 -9.216,-16.704 -8.064,-6.528 -18.432,-6.528 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path834" />
|
||||
<path
|
||||
d="m 986.84675,133.50033 q 13.056,0 23.61605,6.72 10.5599,6.528 16.512,18.432 6.144,11.904 6.144,26.88 0,14.976 -6.144,26.88 -5.9521,11.712 -16.512,18.432 -10.56005,6.72 -23.23205,6.72 -9.984,0 -18.624,-4.416 -8.64,-4.416 -14.016,-10.752 v 42.624 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -3.84,0 -6.528,-2.688 -2.496,-2.496 -2.496,-6.528 v -120.768 q 0,-4.032 2.496,-6.72 2.496,-2.688 6.528,-2.688 4.032,0 6.528,2.688 2.688,2.688 2.688,6.72 v 5.568 q 4.608,-6.72 13.44,-11.52 8.832,-4.8 18.816,-4.8 z m -2.112,87.168 q 8.832,0 15.93595,-4.608 7.1041,-4.608 10.944,-12.48 4.032,-8.064 4.032,-18.048 0,-9.984 -4.032,-17.856 -3.8399,-8.064 -10.944,-12.672 -7.10395,-4.608 -15.93595,-4.608 -9.024,0 -16.128,4.608 -7.104,4.416 -11.136,12.48 -3.84,8.064 -3.84,18.048 0,9.984 3.84,18.048 4.032,8.064 11.136,12.672 7.104,4.416 16.128,4.416 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path836" />
|
||||
<path
|
||||
d="m 1104.2128,132.92433 q 4.9919,0 8.64,2.688 3.648,2.496 3.648,6.336 0,4.608 -2.496,7.104 -2.304,2.304 -5.76,2.304 -1.728,0 -5.184,-1.152 -4.032,-1.344 -6.336,-1.344 -5.952,0 -11.712,4.224 -5.568,4.032 -9.216,11.328 -3.456,7.104 -3.456,15.936 v 46.272 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -4.032,0 -6.528,-2.496 -2.496,-2.688 -2.496,-6.72 v -82.176 q 0,-3.84 2.496,-6.528 2.688,-2.688 6.528,-2.688 4.032,0 6.528,2.688 2.688,2.688 2.688,6.528 v 9.792 q 4.224,-9.408 12.672,-15.168 8.448,-5.952 19.2,-6.144 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path838" />
|
||||
<path
|
||||
d="m 1150.5058,226.62033 q 0,3.84 -2.688,6.528 -2.496,2.688 -6.528,2.688 -3.84,0 -6.5281,-2.688 -2.4959,-2.688 -2.4959,-6.528 v -83.136 q 0,-3.84 2.4959,-6.528 2.6881,-2.688 6.5281,-2.688 4.032,0 6.528,2.688 2.688,2.688 2.688,6.528 z m -9.216,-105.024 q -5.568,0 -8.064,-1.92 -2.304,-2.112 -2.304,-6.528 v -3.072 q 0,-4.608 2.496,-6.528 2.688,-1.92 8.064,-1.92 5.3759,0 7.68,2.112 2.496,1.92 2.496,6.336 v 3.072 q 0,4.608 -2.496,6.528 -2.496,1.92 -7.872,1.92 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path840" />
|
||||
<path
|
||||
d="m 1220.5018,132.92433 q 15.168,0 25.152,4.8 10.1759,4.8 10.1759,12.288 0,3.264 -2.1119,5.952 -2.112,2.496 -5.376,2.496 -2.496,0 -4.0321,-0.768 -1.3439,-0.768 -3.8399,-2.496 -1.152,-1.152 -3.648,-2.688 -2.304,-1.152 -6.528,-1.92 -4.224,-0.768 -7.68,-0.768 -9.984,0 -17.664,4.608 -7.68,4.608 -11.904,12.864 -4.224,8.064 -4.224,18.048 0,10.176 4.032,18.24 4.224,8.064 11.712,12.672 7.488,4.608 17.088,4.608 9.984,0 16.128,-3.072 1.344,-0.768 3.648,-2.496 1.92,-1.536 3.264,-2.304 1.536,-0.768 3.648,-0.768 3.84,0 5.952,2.496 2.304,2.304 2.304,6.144 0,4.032 -5.184,8.064 -4.992,3.84 -13.632,6.336 -8.448,2.496 -18.24,2.496 -14.592,0 -25.728,-6.72 -11.136,-6.912 -17.28,-18.816 -5.952,-12.096 -5.952,-26.88 0,-14.784 6.336,-26.688 6.336,-12.096 17.664,-18.816 11.328,-6.912 25.92,-6.912 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path842" />
|
||||
<path
|
||||
d="m 1363.6708,181.69233 q -0.192,3.456 -2.88,5.952 -2.688,2.304 -6.3361,2.304 h -67.5839 q 1.344,14.016 10.56,22.464 9.4079,8.448 22.848,8.448 9.216,0 14.976,-2.688 5.76,-2.688 10.176,-6.912 2.88,-1.728 5.568,-1.728 3.264,0 5.376,2.304 2.304,2.304 2.304,5.376 0,4.032 -3.8401,7.296 -5.5679,5.568 -14.7839,9.408 -9.2161,3.84 -18.816,3.84 -15.552,0 -27.456,-6.528 -11.712,-6.528 -18.24,-18.24 -6.336,-11.712 -6.336,-26.496 0,-16.128 6.528,-28.224 6.72,-12.288 17.472,-18.816 10.944,-6.528 23.424,-6.528 12.288,0 23.04,6.336 10.752,6.336 17.28,17.472 6.528,11.136 6.72,24.96 z m -47.04,-31.872 q -10.752,0 -18.624,6.144 -7.872,5.952 -10.368,18.624 h 56.64 v -1.536 q -0.96,-10.176 -9.216,-16.704 -8.064,-6.528 -18.432,-6.528 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:500;font-stretch:normal;font-size:192px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Medium'"
|
||||
id="path844" />
|
||||
</g>
|
||||
<g aria-label="POWERED BY" transform="matrix(0.93750004,0,0,0.93750004,231.12524,266.21949)"
|
||||
style="font-style:normal;font-weight:normal;font-size:25px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
id="flowRoot825-6">
|
||||
<path
|
||||
d="m 331.92042,135.56966 q 5.44,0 10.34666,3.30667 4.90667,3.2 7.89334,8.74667 2.98666,5.44 2.98666,11.94666 0,6.4 -2.98666,11.94667 -2.98667,5.54666 -7.89334,8.85333 -4.90666,3.2 -10.34666,3.2 h -18.56 v 20.16 q 0,2.88 -1.70667,4.69333 -1.70667,1.81334 -4.48,1.81334 -2.66667,0 -4.37333,-1.81334 -1.70667,-1.92 -1.70667,-4.69333 v -61.65333 q 0,-2.77333 1.81333,-4.58667 1.92,-1.92 4.69334,-1.92 z m 0,35.84 q 2.02666,0 3.94666,-1.70667 2.02667,-1.70666 3.2,-4.37333 1.28,-2.77333 1.28,-5.76 0,-2.98667 -1.28,-5.65333 -1.17333,-2.77333 -3.2,-4.37333 -1.92,-1.70667 -3.94666,-1.70667 h -18.56 v 23.57333 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path883" />
|
||||
<path
|
||||
d="m 434.16208,172.90299 q 0,10.56 -4.69333,19.41334 -4.69333,8.74666 -13.01333,13.86666 -8.21334,5.12 -18.56,5.12 -10.34667,0 -18.66667,-5.12 -8.21333,-5.12 -12.90667,-13.86666 -4.58666,-8.85334 -4.58666,-19.41334 0,-10.56 4.58666,-19.30666 4.69334,-8.85333 12.90667,-13.97333 8.32,-5.12 18.66667,-5.12 10.34666,0 18.56,5.12 8.32,5.12 13.01333,13.97333 4.69333,8.74666 4.69333,19.30666 z m -13.86666,0 q 0,-7.14666 -2.88,-12.90666 -2.88,-5.86667 -8,-9.28 -5.12,-3.41333 -11.52,-3.41333 -6.50667,0 -11.62667,3.41333 -5.01333,3.30666 -7.89333,9.17333 -2.77334,5.86667 -2.77334,13.01333 0,7.14667 2.77334,13.01334 2.88,5.86666 7.89333,9.28 5.12,3.30666 11.62667,3.30666 6.4,0 11.52,-3.41333 5.12,-3.41333 8,-9.17333 2.88,-5.86667 2.88,-13.01334 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path885" />
|
||||
<path
|
||||
d="m 529.83207,135.24966 q 2.56,0 4.69333,2.02667 2.24,1.92 2.24,4.90667 0,0.96 -0.32,2.13333 l -21.01333,61.86666 q -0.64,1.81334 -2.24,2.88 -1.6,1.06667 -3.52,1.17334 -1.92,0 -3.62667,-1.06667 -1.70666,-1.06667 -2.66666,-3.09333 l -15.14667,-34.45334 -15.25333,34.45334 q -0.96,2.02666 -2.66667,3.09333 -1.70666,1.06667 -3.62666,1.06667 -1.92,-0.10667 -3.52,-1.17334 -1.6,-1.06666 -2.24,-2.88 l -21.01334,-61.86666 q -0.32,-1.17333 -0.32,-2.13333 0,-2.98667 2.13334,-4.90667 2.24,-2.02667 4.90666,-2.02667 2.13334,0 3.84,1.17334 1.70667,1.06666 2.34667,2.98666 l 15.89333,48.21333 13.86667,-33.28 q 0.85333,-1.91999 2.45333,-2.98666 1.6,-1.17333 3.62667,-1.06667 2.02667,-0.10666 3.52,1.06667 1.6,1.06667 2.45333,2.98666 l 13.12,32.96 15.78667,-47.89333 q 0.64,-1.92 2.34666,-2.98666 1.81334,-1.17334 3.94667,-1.17334 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path887" />
|
||||
<path
|
||||
d="m 588.75041,197.96966 q 2.77333,0 4.58666,1.92 1.92,1.81333 1.92,4.26667 0,2.66666 -1.92,4.37333 -1.81333,1.70667 -4.58666,1.70667 h -35.73334 q -2.77333,0 -4.69333,-1.81334 -1.81333,-1.92 -1.81333,-4.69333 v -61.65333 q 0,-2.77333 1.81333,-4.58667 1.92,-1.92 4.69333,-1.92 h 35.73334 q 2.77333,0 4.58666,1.81334 1.92,1.70666 1.92,4.48 0,2.66666 -1.81333,4.37333 -1.81333,1.6 -4.69333,1.6 h -28.90667 v 18.13333 h 24.10667 q 2.77333,0 4.58666,1.81333 1.92,1.70667 1.92,4.48 0,2.66667 -1.81333,4.37334 -1.81333,1.6 -4.69333,1.6 h -24.10667 v 19.73333 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path889" />
|
||||
<path
|
||||
d="m 666.11206,199.78299 q 1.38667,0.85334 2.13333,2.24 0.85334,1.38667 0.85334,2.88 0,1.92 -1.28,3.52 -1.6,1.92 -4.90667,1.92 -2.56,0 -4.69333,-1.17333 -7.68,-4.37333 -7.68,-17.81333 0,-3.84 -2.56,-6.08 -2.45333,-2.24 -7.14667,-2.24 H 620.8854 v 20.69333 q 0,2.88 -1.6,4.69333 -1.49334,1.81334 -4.05334,1.81334 -3.09333,0 -5.44,-1.81334 -2.24,-1.92 -2.24,-4.69333 v -61.65333 q 0,-2.77333 1.81334,-4.58667 1.92,-1.92 4.69333,-1.92 h 30.72 q 5.54667,0 10.45333,2.98667 4.90667,2.98667 7.78667,8.21333 2.98666,5.22667 2.98666,11.73333 0,5.33334 -2.88,10.45334 -2.88,5.01333 -7.46666,8 6.72,4.69333 7.36,12.58666 0.32,1.70667 0.32,3.30667 0.42666,3.30667 0.85333,4.8 0.42667,1.38667 1.92,2.13333 z M 644.2454,172.04966 q 1.92,0 3.73333,-1.81333 1.81333,-1.81334 2.98667,-4.8 1.17333,-3.09334 1.17333,-6.61334 0,-2.98666 -1.17333,-5.43999 -1.17334,-2.56 -2.98667,-4.05334 -1.81333,-1.49333 -3.73333,-1.49333 h -23.36 v 24.21333 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path891" />
|
||||
<path
|
||||
d="m 723.12541,197.96966 q 2.77333,0 4.58666,1.92 1.92,1.81333 1.92,4.26667 0,2.66666 -1.92,4.37333 -1.81333,1.70667 -4.58666,1.70667 h -35.73334 q -2.77333,0 -4.69333,-1.81334 -1.81333,-1.92 -1.81333,-4.69333 v -61.65333 q 0,-2.77333 1.81333,-4.58667 1.92,-1.92 4.69333,-1.92 h 35.73334 q 2.77333,0 4.58666,1.81334 1.92,1.70666 1.92,4.48 0,2.66666 -1.81333,4.37333 -1.81333,1.6 -4.69333,1.6 h -28.90667 v 18.13333 h 24.10667 q 2.77333,0 4.58666,1.81333 1.92,1.70667 1.92,4.48 0,2.66667 -1.81333,4.37334 -1.81333,1.6 -4.69333,1.6 h -24.10667 v 19.73333 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path893" />
|
||||
<path
|
||||
d="m 773.92706,135.56966 q 10.02667,0 17.17333,5.01334 7.25334,4.90666 10.98667,13.43999 3.84,8.42667 3.84,18.88 0,10.45334 -3.84,18.98667 -3.73333,8.42667 -10.98667,13.44 -7.14666,4.90667 -17.17333,4.90667 h -25.49333 q -2.77333,0 -4.69333,-1.81334 -1.81334,-1.92 -1.81334,-4.69333 v -61.65333 q 0,-2.77333 1.81334,-4.58667 1.92,-1.92 4.69333,-1.92 z m -1.06666,62.4 q 9.6,0 14.4,-7.04 4.79999,-7.14667 4.79999,-18.02667 0,-10.88 -4.90666,-17.92 -4.8,-7.14666 -14.29333,-7.14666 h -17.6 v 50.13333 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path895" />
|
||||
<path
|
||||
d="m 892.76875,168.84966 q 5.65333,2.24 9.17333,6.82667 3.62667,4.58666 3.62667,11.84 0,12.69333 -7.25333,17.70666 -7.25334,5.01334 -17.28,5.01334 h -26.56 q -2.77334,0 -4.69334,-1.81334 -1.81333,-1.92 -1.81333,-4.69333 v -61.65333 q 0,-2.77333 1.81333,-4.58667 1.92,-1.92 4.69334,-1.92 h 26.88 q 20.26666,0 20.26666,18.98667 0,4.8 -2.34666,8.53333 -2.24,3.62667 -6.50667,5.76 z m -5.01333,-11.94667 q 0,-4.37333 -2.24,-6.50666 -2.13334,-2.24 -6.08,-2.24 h -17.6 v 16.64 h 17.92 q 3.2,0 5.54666,-2.13334 2.45334,-2.13333 2.45334,-5.76 z m -6.72,41.06667 q 5.01333,0 7.78666,-2.66667 2.88,-2.66666 2.88,-7.78666 0,-6.29334 -3.30666,-8.21334 -3.30667,-1.92 -8.10667,-1.92 h -18.45333 v 20.58667 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path897" />
|
||||
<path
|
||||
d="m 969.23371,141.863 q 0,2.13333 -1.17334,3.94666 l -22.29333,31.89333 v 26.02667 q 0,2.77333 -1.81333,4.69333 -1.81333,1.81334 -4.37333,1.81334 -2.66667,0 -4.58667,-1.81334 -1.81333,-1.92 -1.81333,-4.69333 v -27.52 l -22.18667,-29.44 q -1.92,-2.56 -1.92,-5.01333 0,-2.77333 2.13333,-4.58667 2.24,-1.92 4.69334,-1.92 2.98666,0 5.22666,2.98667 l 18.77334,25.92 17.59999,-25.70667 q 2.24,-3.2 5.33334,-3.2 2.56,0 4.48,1.92 1.92,1.92 1.92,4.69334 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:106.66666412px;font-family:Quicksand;-inkscape-font-specification:'Quicksand Bold'"
|
||||
id="path899" />
|
||||
</g>
|
||||
</g>
|
||||
<g inkscape:groupmode="layer" id="layer2" inkscape:label="Layer 3"
|
||||
transform="translate(-27.815468,21.198496)" />
|
||||
<g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1"
|
||||
transform="translate(-220.97188,-392.31605)">
|
||||
<path inkscape:connector-curvature="0" style="fill:#000016;fill-opacity:1"
|
||||
d="m 467.91504,565.96351 -11.49602,-6.03301 10.02002,-5.23601 c 3.41201,-1.785 3.41501,-4.70701 0.01,-6.49601 l -10.76202,-5.64801 10.75602,-5.62101 c 3.41201,-1.78501 3.41501,-4.70701 0.01,-6.49602 L 380.3709,485.25335 c -3.40901,-1.789 -8.99702,-1.809 -12.41802,-0.041 L 223.53563,559.8065 c -3.422,1.766 -3.418,4.65001 0.01,6.41001 l 11.01802,5.66202 -11.02302,5.69301 c -3.422,1.766 -3.418,4.65001 0.01,6.41001 l 11.75602,6.04101 -10.28901,5.31401 c -3.42201,1.76801 -3.41801,4.65201 0.01,6.41201 l 88.01014,45.22309 c 3.42601,1.76001 9.02002,1.74001 12.43002,-0.043 l 142.46424,-74.46914 c 3.40901,-1.78301 3.41201,-4.70701 0,-6.49602 z m -93.03415,-58.62311 -21.59604,24.90005 c -1.08,1.246 -0.764,2.883 0.703,3.637 l 17.18203,8.82802 c 1.467,0.754 1.469,1.99201 0,2.74801 l -53.92109,27.85005 c -1.467,0.758 -1.781,0.357 -0.699,-0.889 l 21.59404,-24.90005 c 1.082,-1.246 0.766,-2.885 -0.70101,-3.63901 l -17.18202,-8.82801 c -1.46701,-0.75401 -1.46901,-1.99001 0,-2.74801 l 53.92209,-27.85005 c 1.465,-0.75601 1.78,-0.35501 0.699,0.891 z"
|
||||
id="path11" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 22 KiB |
BIN
_img/screenshots/android_auto/de/11_android_auto_map.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
_img/screenshots/android_auto/de/12_android_auto_detail.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
_img/screenshots/android_auto/de/13_android_auto_prices.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
_img/screenshots/android_auto/de/14_vehicle_data.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
_img/screenshots/android_auto/en/11_android_auto_map.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
_img/screenshots/android_auto/en/12_android_auto_detail.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
_img/screenshots/android_auto/en/13_android_auto_prices.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
_img/screenshots/android_auto/en/14_vehicle_data.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 909 KiB |
|
Before Width: | Height: | Size: 943 KiB |
BIN
_img/screenshots/phone/de/google/01_map.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
_img/screenshots/phone/de/google/02_detail.png
Normal file
|
After Width: | Height: | Size: 972 KiB |
BIN
_img/screenshots/phone/de/google/03_prices.png
Normal file
|
After Width: | Height: | Size: 352 KiB |
BIN
_img/screenshots/phone/de/google/04_favorites.png
Normal file
|
After Width: | Height: | Size: 81 KiB |
BIN
_img/screenshots/phone/de/google/05_filters.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
_img/screenshots/phone/de/mapbox/01_map.png
Normal file
|
After Width: | Height: | Size: 875 KiB |
BIN
_img/screenshots/phone/de/mapbox/02_detail.png
Normal file
|
After Width: | Height: | Size: 837 KiB |
BIN
_img/screenshots/phone/de/mapbox/03_prices.png
Normal file
|
After Width: | Height: | Size: 341 KiB |
BIN
_img/screenshots/phone/de/mapbox/04_favorites.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
_img/screenshots/phone/de/mapbox/05_filters.png
Normal file
|
After Width: | Height: | Size: 134 KiB |
BIN
_img/screenshots/phone/en/google/01_map.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
_img/screenshots/phone/en/google/02_detail.png
Normal file
|
After Width: | Height: | Size: 972 KiB |
BIN
_img/screenshots/phone/en/google/03_prices.png
Normal file
|
After Width: | Height: | Size: 343 KiB |
BIN
_img/screenshots/phone/en/google/04_favorites.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
_img/screenshots/phone/en/google/05_filters.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
_img/screenshots/phone/en/mapbox/01_map.png
Normal file
|
After Width: | Height: | Size: 864 KiB |
BIN
_img/screenshots/phone/en/mapbox/02_detail.png
Normal file
|
After Width: | Height: | Size: 837 KiB |
BIN
_img/screenshots/phone/en/mapbox/03_prices.png
Normal file
|
After Width: | Height: | Size: 330 KiB |
BIN
_img/screenshots/phone/en/mapbox/04_favorites.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
_img/screenshots/phone/en/mapbox/05_filters.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
186
app/build.gradle
@@ -1,26 +1,35 @@
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-android-extensions'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "29.0.3"
|
||||
compileSdkVersion 31
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.vonforst.evmap"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 29
|
||||
versionCode 20
|
||||
versionName "0.2.2"
|
||||
targetSdkVersion 31
|
||||
versionCode 67
|
||||
versionName "1.1.3"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
release
|
||||
release {
|
||||
def isRunningOnTravis = System.getenv("CI") == "true"
|
||||
if (isRunningOnTravis) {
|
||||
// configure keystore
|
||||
storeFile = file("../_ci/keystore.jks")
|
||||
storePassword = System.getenv("keystore_password")
|
||||
keyAlias = System.getenv("keystore_alias")
|
||||
keyPassword = System.getenv("keystore_alias_password")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -35,13 +44,15 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
def isRunningOnTravis = System.getenv("CI") == "true"
|
||||
if (isRunningOnTravis) {
|
||||
// configure keystore
|
||||
signingConfigs.release.storeFile = file("../_ci/keystore.jks")
|
||||
signingConfigs.release.storePassword = System.getenv("keystore_password")
|
||||
signingConfigs.release.keyAlias = System.getenv("keystore_alias")
|
||||
signingConfigs.release.keyPassword = System.getenv("keystore_alias_password")
|
||||
flavorDimensions "dependencies"
|
||||
productFlavors {
|
||||
foss {
|
||||
dimension "dependencies"
|
||||
}
|
||||
google {
|
||||
dimension "dependencies"
|
||||
versionNameSuffix "-google"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -56,95 +67,148 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
dataBinding = true
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
// add API keys from environment variable if not set in apikeys.xml
|
||||
applicationVariants.all { variant ->
|
||||
ext.env = System.getenv()
|
||||
def goingelectricKey = env.GOINGELECTRIC_API_KEY
|
||||
def goingelectricKey = env.GOINGELECTRIC_API_KEY ?: project.findProperty("GOINGELECTRIC_API_KEY")
|
||||
if (goingelectricKey != null) {
|
||||
variant.resValue "string", "goingelectric_key", goingelectricKey
|
||||
}
|
||||
def googleMapsKey = env.GOOGLE_MAPS_API_KEY
|
||||
if (googleMapsKey != null) {
|
||||
def openchargemapKey = env.OPENCHARGEMAP_API_KEY ?: project.findProperty("OPENCHARGEMAP_API_KEY")
|
||||
if (openchargemapKey == null && project.hasProperty("OPENCHARGEMAP_API_KEY_ENCRYPTED")) {
|
||||
openchargemapKey = decode(project.findProperty("OPENCHARGEMAP_API_KEY_ENCRYPTED"), "FmK.d,-f*p+rD+WK!eds")
|
||||
}
|
||||
if (openchargemapKey != null) {
|
||||
variant.resValue "string", "openchargemap_key", openchargemapKey
|
||||
}
|
||||
def googleMapsKey = env.GOOGLE_MAPS_API_KEY ?: project.findProperty("GOOGLE_MAPS_API_KEY")
|
||||
if (googleMapsKey != null && variant.flavorName == 'google') {
|
||||
variant.resValue "string", "google_maps_key", googleMapsKey
|
||||
}
|
||||
def mapboxKey = env.MAPBOX_API_KEY ?: project.findProperty("MAPBOX_API_KEY")
|
||||
if (mapboxKey == null && project.hasProperty("MAPBOX_API_KEY_ENCRYPTED")) {
|
||||
mapboxKey = decode(project.findProperty("MAPBOX_API_KEY_ENCRYPTED"), "FmK.d,-f*p+rD+WK!eds")
|
||||
}
|
||||
if (mapboxKey != null) {
|
||||
variant.resValue "string", "mapbox_key", mapboxKey
|
||||
}
|
||||
def chargepriceKey = env.CHARGEPRICE_API_KEY ?: project.findProperty("CHARGEPRICE_API_KEY")
|
||||
if (chargepriceKey == null && project.hasProperty("CHARGEPRICE_API_KEY_ENCRYPTED")) {
|
||||
chargepriceKey = decode(project.findProperty("CHARGEPRICE_API_KEY_ENCRYPTED"), "FmK.d,-f*p+rD+WK!eds")
|
||||
}
|
||||
if (chargepriceKey != null) {
|
||||
variant.resValue "string", "chargeprice_key", chargepriceKey
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
disable 'NullSafeMutableLiveData'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation 'androidx.appcompat:appcompat:1.3.1'
|
||||
implementation 'androidx.core:core-ktx:1.7.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
|
||||
implementation "androidx.activity:activity-ktx:1.4.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.3.6"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.core:core:1.3.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.2.0-beta01'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||
implementation 'androidx.browser:browser:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.4.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'com.github.johan12345:CustomBottomSheetBehavior:f69f532660'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.7.2'
|
||||
implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
|
||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||
implementation 'com.github.MikeOrtiz:TouchImageView:2.3.3'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.0'
|
||||
implementation 'com.squareup.moshi:moshi-kotlin:1.12.0'
|
||||
implementation 'com.squareup.moshi:moshi-adapters:1.12.0'
|
||||
implementation 'moe.banana:moshi-jsonapi:3.5.0'
|
||||
implementation 'moe.banana:moshi-jsonapi-retrofit-converter:3.5.0'
|
||||
implementation 'io.coil-kt:coil:1.1.0'
|
||||
implementation 'com.github.johan12345:StfalconImageViewer:5082ebd392'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libs_version"
|
||||
implementation "com.mikepenz:aboutlibraries:$about_libs_version"
|
||||
implementation 'com.airbnb.android:lottie:3.4.0'
|
||||
implementation 'io.michaelrocks:bimap:1.0.2'
|
||||
implementation 'com.airbnb.android:lottie:4.1.0'
|
||||
implementation 'io.michaelrocks.bimap:bimap:1.1.0'
|
||||
implementation 'com.mapzen.android:lost:3.0.2'
|
||||
implementation 'com.google.guava:guava:29.0-android'
|
||||
implementation 'com.github.pengrad:mapscaleview:1.6.0'
|
||||
implementation 'com.github.romandanylyk:PageIndicatorView:b1bad589b5'
|
||||
|
||||
// Google Maps v3 Beta
|
||||
implementation 'com.google.android.libraries.maps:maps:3.1.0-beta'
|
||||
implementation name:'places-maps-sdk-3.1.0-beta', ext:'aar'
|
||||
implementation 'com.google.maps.android:android-maps-utils-v3:1.3.3'
|
||||
implementation 'com.android.volley:volley:1.1.1'
|
||||
implementation 'com.google.android.gms:play-services-base:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-basement:17.3.0'
|
||||
implementation 'com.google.android.gms:play-services-gcm:17.0.0'
|
||||
implementation 'com.google.android.gms:play-services-location:17.0.0'
|
||||
implementation 'com.google.android.gms:play-services-tasks:17.1.0'
|
||||
implementation 'com.google.auto.value:auto-value-annotations:1.6.3'
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.google.android.datatransport:transport-runtime:2.2.3'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
// Android Auto
|
||||
googleImplementation 'androidx.car.app:app:1.1.0-rc01'
|
||||
googleImplementation 'androidx.car.app:app-projected:1.1.0-rc01'
|
||||
|
||||
// AnyMaps
|
||||
def anyMapsVersion = '751daec281'
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-base:$anyMapsVersion"
|
||||
googleImplementation "com.github.johan12345.AnyMaps:anymaps-google:$anyMapsVersion"
|
||||
googleImplementation 'com.google.android.gms:play-services-maps:18.0.0'
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-mapbox:$anyMapsVersion"
|
||||
|
||||
// Google Places
|
||||
implementation 'com.google.android.libraries.places:places:2.5.0'
|
||||
googleImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'
|
||||
|
||||
// Mapbox Geocoding
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:5.5.0'
|
||||
|
||||
// navigation library
|
||||
def nav_version = "2.3.0"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
|
||||
// viewmodel library
|
||||
def lifecycle_version = "2.2.0"
|
||||
def lifecycle_version = "2.4.0"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// room library
|
||||
def room_version = "2.2.5"
|
||||
def room_version = "2.3.0"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
|
||||
// billing library
|
||||
def billing_version = "3.0.0"
|
||||
implementation "com.android.billingclient:billing:$billing_version"
|
||||
implementation "com.android.billingclient:billing-ktx:$billing_version"
|
||||
def billing_version = "4.0.0"
|
||||
googleImplementation "com.android.billingclient:billing:$billing_version"
|
||||
googleImplementation "com.android.billingclient:billing-ktx:$billing_version"
|
||||
|
||||
// ACRA (crash reporting)
|
||||
def acraVersion = "5.8.4"
|
||||
implementation("ch.acra:acra-mail:$acraVersion")
|
||||
implementation("ch.acra:acra-dialog:$acraVersion")
|
||||
implementation("ch.acra:acra-limiter:$acraVersion")
|
||||
|
||||
// debug tools
|
||||
implementation 'com.facebook.stetho:stetho:1.5.1'
|
||||
implementation 'com.facebook.stetho:stetho-okhttp3:1.5.1'
|
||||
testImplementation 'junit:junit:4.13'
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:3.14.7"
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
|
||||
//noinspection GradleDependency
|
||||
testImplementation 'org.json:json:20080701'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.9.2"
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.12.0"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
}
|
||||
|
||||
private static String decode(String s, String key) {
|
||||
return new String(xorWithKey(s.decodeBase64(), key.getBytes()), "UTF-8");
|
||||
}
|
||||
|
||||
private static byte[] xorWithKey(byte[] a, byte[] key) {
|
||||
byte[] out = new byte[a.length];
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
out[i] = (byte) (a[i] ^ key[i%key.length]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
107
app/src/debug/res/drawable/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,107 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="198.3471"
|
||||
android:viewportHeight="198.3471">
|
||||
<group
|
||||
android:translateX="3.1735537"
|
||||
android:translateY="3.1735537">
|
||||
<group>
|
||||
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
|
||||
<path
|
||||
android:pathData="M106.2,74.3h-7"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#212121"
|
||||
android:fillAlpha="0.2" />
|
||||
</group>
|
||||
<group>
|
||||
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
|
||||
<path
|
||||
android:pathData="M106.2,60.3c0,0 -17.5,0 -17.5,0"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#212121"
|
||||
android:fillAlpha="0.2" />
|
||||
</group>
|
||||
<group>
|
||||
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
|
||||
<path
|
||||
android:pathData="M93.9,79.5L88.7,79.5"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:fillAlpha="0.2" />
|
||||
</group>
|
||||
<group>
|
||||
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M94,79v16.2"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#212121"
|
||||
android:fillAlpha="0.2"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
<group>
|
||||
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M106.2,60.3L99.2,74.3"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:fillAlpha="0.2"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
<group>
|
||||
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
|
||||
<path
|
||||
android:strokeWidth="1"
|
||||
android:pathData="M106.2,74.3L93.9,95.2"
|
||||
android:strokeAlpha="0.2"
|
||||
android:strokeLineJoin="round"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFF"
|
||||
android:fillAlpha="0.2"
|
||||
android:strokeLineCap="round" />
|
||||
</group>
|
||||
<path
|
||||
android:pathData="M67.6,120.6L65.7,104l-2.9,0.3l1.9,16.6L67.6,120.6zM77.9,119.4l-1.9,-16.6l-2.9,0.3l1.9,16.6L77.9,119.4z"
|
||||
android:fillColor="#808080" />
|
||||
<path
|
||||
android:pathData="M83.3,142c-0.9,1.1 -1.6,1.8 -1.7,1.9c-2.6,2.1 -4.7,2.7 -6.4,1.9c-3,-1.5 -2.8,-7.1 -2.7,-7.7l2.1,0.1c-0.1,1.6 0.2,5 1.6,5.7c0.8,0.4 2.2,-0.1 4,-1.6l0,0c0,0 5.8,-5.8 4.6,-10.4c-1.4,-5.5 5,-13.4 7.1,-16.1l0.3,-0.3l1.7,1.3l-0.3,0.4c-6.5,8 -7.2,12.1 -6.7,14.2C87.9,135.4 85.2,139.7 83.3,142z"
|
||||
android:fillColor="#9e9e9e" />
|
||||
<path
|
||||
android:pathData="M61.2,120.4l0.8,6.8l6.3,4.2l8.5,-0.9l5.2,-5.5l-0.8,-6.8L61.2,120.4z"
|
||||
android:fillColor="#9e9e9e" />
|
||||
<path
|
||||
android:pathData="M76.7,130.5l-8.5,0.9l1.8,7.5l6.7,-0.8L76.7,130.5L76.7,130.5zM82.8,112.5l0.7,6.2l-24.4,2.8l-0.7,-6.2L82.8,112.5z"
|
||||
android:fillColor="#666666" />
|
||||
<path
|
||||
android:pathData="M101.9,44.1c-17.5,0 -31.7,14.2 -31.7,31.7c0,23.9 26.7,36.4 29.9,70.5c0.1,1 0.9,1.7 1.9,1.7s1.8,-0.7 1.9,-1.7c3.2,-34.1 29.9,-46.6 29.9,-70.5C133.6,58.2 119.4,44.1 101.9,44.1z"
|
||||
android:fillColor="#737373" />
|
||||
<path
|
||||
android:pathData="M101.9,44.8c17.4,0 31.5,14 31.7,31.3c0,-0.1 0,-0.2 0,-0.3c0,-17.5 -14.2,-31.7 -31.7,-31.7S70.2,58.2 70.2,75.8c0,0.1 0,0.2 0,0.3C70.4,58.8 84.5,44.8 101.9,44.8L101.9,44.8z"
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillAlpha="0.2" />
|
||||
<path
|
||||
android:pathData="M103.8,145.5c-0.1,1 -0.9,1.7 -1.9,1.7s-1.8,-0.7 -1.9,-1.7c-3.1,-34 -29.6,-46.5 -29.8,-70.1c0,0.2 0,0.3 0,0.5c0,23.9 26.7,36.4 29.9,70.5c0.1,1 0.9,1.7 1.9,1.7s1.8,-0.7 1.9,-1.7c3.2,-34.1 29.9,-46.6 29.9,-70.5c0,-0.2 0,-0.3 0,-0.5C133.4,99 106.9,111.5 103.8,145.5L103.8,145.5z"
|
||||
android:fillColor="#303030"
|
||||
android:fillAlpha="0.2" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M94.6,60.3v19.2h5.2v15.7l12.2,-21h-7l7,-14C112.1,60.3 94.6,60.3 94.6,60.3z"
|
||||
android:strokeAlpha="0.45"
|
||||
android:fillAlpha="0.45" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EV Map (debug)</string>
|
||||
<string name="app_name">EVMap (debug)</string>
|
||||
</resources>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EV Map (debug)</string>
|
||||
<string name="app_name">EVMap (debug)</string>
|
||||
</resources>
|
||||
12
app/src/foss/java/net/vonforst/evmap/Inits.kt
Normal file
@@ -0,0 +1,12 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
|
||||
fun init(context: Context) {
|
||||
|
||||
}
|
||||
|
||||
fun checkPlayServices(activity: Activity): Boolean {
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package net.vonforst.evmap.autocomplete
|
||||
|
||||
import android.content.Context
|
||||
|
||||
fun getAutocompleteProviders(context: Context) = listOf(MapboxAutocompleteProvider(context))
|
||||
@@ -0,0 +1,42 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.databinding.FragmentDonateBinding
|
||||
|
||||
class DonateFragment : Fragment() {
|
||||
private lateinit var binding: FragmentDonateBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = FragmentDonateBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
(requireActivity() as AppCompatActivity).setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.btnDonate.setOnClickListener {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.paypal_link))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.toolbar.setupWithNavController(
|
||||
findNavController(),
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
|
||||
class OnboardingViewPagerAdapter(fragment: Fragment) :
|
||||
FragmentStateAdapter(fragment) {
|
||||
override fun getItemCount(): Int = 3
|
||||
|
||||
override fun createFragment(position: Int): Fragment = when (position) {
|
||||
0 -> WelcomeFragment()
|
||||
1 -> IconsFragment()
|
||||
2 -> DataSourceSelectFragment()
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
51
app/src/foss/res/layout/fragment_donate.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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:id="@+id/linearLayout2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDonate"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/donate_paypal"
|
||||
app:icon="@drawable/ic_paypal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/donations_info"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnDonate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
14
app/src/foss/res/values-de/values.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.\n\nGoogle zieht von der Spende 30% Gebühren ab.</string>
|
||||
<string name="donate_paypal">Mit PayPal spenden</string>
|
||||
</resources>
|
||||
20
app/src/foss/res/values/values.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="pref_search_provider_default" translatable="false">mapbox</string>
|
||||
<string name="pref_map_provider_default" translatable="false">mapbox</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.</string>
|
||||
<string name="donate_paypal">Donate with PayPal</string>
|
||||
<string name="paypal_link" translatable="false">https://paypal.me/johan98</string>
|
||||
</resources>
|
||||
52
app/src/google/AndroidManifest.xml
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="net.vonforst.evmap">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="androidx.car.app.MAP_TEMPLATES" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.CAR_FUEL" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.CAR_SPEED" />
|
||||
|
||||
<uses-sdk tools:overrideLibrary="androidx.car.app,androidx.car.app.projected" />
|
||||
|
||||
<queries>
|
||||
<package android:name="com.google.android.projection.gearhead" />
|
||||
</queries>
|
||||
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="@string/google_maps_key" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/automotive_app_desc" />
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.car.app.theme"
|
||||
android:resource="@style/CarAppTheme" />
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.car.app.minCarApiLevel"
|
||||
android:value="1" />
|
||||
|
||||
<service
|
||||
android:name=".auto.CarAppService"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="androidx.car.app.CarAppService"
|
||||
android:category="androidx.car.app.category.CHARGING" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".auto.CarLocationService"
|
||||
android:foregroundServiceType="location"
|
||||
android:enabled="true"
|
||||
android:exported="false" />
|
||||
</application>
|
||||
</manifest>
|
||||
35
app/src/google/java/net/vonforst/evmap/Inits.kt
Normal file
@@ -0,0 +1,35 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.maps.MapsInitializer
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.utils.LocaleContextWrapper
|
||||
|
||||
fun init(context: Context) {
|
||||
Places.initialize(context, context.getString(R.string.google_maps_key))
|
||||
|
||||
val localeContext = LocaleContextWrapper.wrap(
|
||||
context.applicationContext, PreferenceDataSource(context).language
|
||||
)
|
||||
MapsInitializer.initialize(localeContext, MapsInitializer.Renderer.LATEST, null)
|
||||
}
|
||||
|
||||
fun checkPlayServices(activity: Activity): Boolean {
|
||||
val request = 9000
|
||||
val apiAvailability = GoogleApiAvailability.getInstance()
|
||||
val resultCode = apiAvailability.isGooglePlayServicesAvailable(activity)
|
||||
if (resultCode != ConnectionResult.SUCCESS) {
|
||||
if (apiAvailability.isUserResolvableError(resultCode)) {
|
||||
apiAvailability.getErrorDialog(activity, resultCode, request)?.show()
|
||||
} else {
|
||||
Log.d("EVMap", "This device is not supported.")
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.vonforst.evmap.adapter
|
||||
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.viewmodel.DonationItem
|
||||
|
||||
class DonationAdapter() : DataBindingAdapter<DonationItem>() {
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_donation
|
||||
}
|
||||
147
app/src/google/java/net/vonforst/evmap/auto/CarAppService.kt
Normal file
@@ -0,0 +1,147 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.*
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.location.Location
|
||||
import android.os.IBinder
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.Session
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.CarHardwareLocation
|
||||
import androidx.car.app.hardware.info.CarSensors
|
||||
import androidx.car.app.validation.HostValidator
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import net.vonforst.evmap.utils.checkAnyLocationPermission
|
||||
|
||||
|
||||
interface LocationAwareScreen {
|
||||
fun updateLocation(location: Location)
|
||||
}
|
||||
|
||||
class CarAppService : androidx.car.app.CarAppService() {
|
||||
override fun createHostValidator(): HostValidator {
|
||||
return if (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0) {
|
||||
HostValidator.ALLOW_ALL_HOSTS_VALIDATOR
|
||||
} else {
|
||||
HostValidator.Builder(applicationContext)
|
||||
.addAllowedHosts(androidx.car.app.R.array.hosts_allowlist_sample)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateSession(): Session {
|
||||
return EVMapSession(this)
|
||||
}
|
||||
}
|
||||
|
||||
class EVMapSession(val cas: CarAppService) : Session(), LifecycleObserver {
|
||||
var mapScreen: LocationAwareScreen? = null
|
||||
set(value) {
|
||||
field = value
|
||||
location?.let { value?.updateLocation(it) }
|
||||
}
|
||||
private var location: Location? = null
|
||||
private var locationService: CarLocationService? = null
|
||||
private val hardwareMan: CarHardwareManager by lazy {
|
||||
carContext.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager
|
||||
}
|
||||
|
||||
private val serviceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName, ibinder: IBinder) {
|
||||
val binder: CarLocationService.LocalBinder = ibinder as CarLocationService.LocalBinder
|
||||
locationService = binder.service
|
||||
locationService?.requestLocationUpdates()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
locationService = null
|
||||
}
|
||||
}
|
||||
private var serviceBound = false
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
return WelcomeScreen(carContext, this)
|
||||
}
|
||||
|
||||
fun locationPermissionGranted() = carContext.checkAnyLocationPermission()
|
||||
|
||||
private val locationReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val location = intent.getParcelableExtra(CarLocationService.EXTRA_LOCATION) as Location?
|
||||
updateLocation(location)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateLocation(location: Location?) {
|
||||
val mapScreen = mapScreen
|
||||
if (location != null && mapScreen != null) {
|
||||
mapScreen.updateLocation(location)
|
||||
}
|
||||
this.location = location
|
||||
}
|
||||
|
||||
private fun onCarHardwareLocationReceived(loc: CarHardwareLocation) {
|
||||
updateLocation(loc.location.value)
|
||||
|
||||
// we successfully received a location from the car hardware,
|
||||
// so we don't need the smartphone location anymore.
|
||||
unbindLocationService()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
fun bindLocationService() {
|
||||
if (!locationPermissionGranted()) return
|
||||
if (supportsCarApiLevel3(carContext)) {
|
||||
val exec = ContextCompat.getMainExecutor(carContext)
|
||||
hardwareMan.carSensors.addCarHardwareLocationListener(
|
||||
CarSensors.UPDATE_RATE_NORMAL,
|
||||
exec,
|
||||
::onCarHardwareLocationReceived
|
||||
)
|
||||
}
|
||||
serviceBound = cas.bindService(
|
||||
Intent(cas, CarLocationService::class.java),
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
private fun onStop() {
|
||||
if (supportsCarApiLevel3(carContext)) {
|
||||
hardwareMan.carSensors.removeCarHardwareLocationListener(::onCarHardwareLocationReceived)
|
||||
}
|
||||
unbindLocationService()
|
||||
}
|
||||
|
||||
private fun unbindLocationService() {
|
||||
locationService?.removeLocationUpdates()
|
||||
if (serviceBound) {
|
||||
cas.unbindService(serviceConnection)
|
||||
serviceBound = false
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
private fun registerBroadcastReceiver() {
|
||||
LocalBroadcastManager.getInstance(cas).registerReceiver(
|
||||
locationReceiver,
|
||||
IntentFilter(CarLocationService.ACTION_BROADCAST)
|
||||
);
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
private fun unregisterBroadcastReceiver() {
|
||||
LocalBroadcastManager.getInstance(cas).unregisterReceiver(locationReceiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.location.Location
|
||||
import android.os.*
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.google.android.gms.location.*
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.R
|
||||
|
||||
|
||||
class CarLocationService : Service() {
|
||||
private lateinit var serviceHandler: Handler
|
||||
private lateinit var locationRequest: LocationRequest
|
||||
private lateinit var notificationManager: NotificationManager
|
||||
private lateinit var locationCallback: LocationCallback
|
||||
private lateinit var fusedLocationClient: FusedLocationProviderClient
|
||||
private val binder: IBinder = LocalBinder(this)
|
||||
private var location: Location? = null
|
||||
|
||||
private val UPDATE_INTERVAL_IN_MILLISECONDS = 10000L
|
||||
private val FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS = UPDATE_INTERVAL_IN_MILLISECONDS / 2
|
||||
|
||||
private val CHANNEL_ID = "car_location"
|
||||
private val NOTIFICATION_ID = 1000
|
||||
private val TAG = "CarLocationService"
|
||||
|
||||
companion object {
|
||||
const val ACTION_BROADCAST: String = BuildConfig.APPLICATION_ID + ".car_location_broadcast"
|
||||
const val EXTRA_LOCATION: String = BuildConfig.APPLICATION_ID + ".location"
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
|
||||
locationCallback = object : LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
super.onLocationResult(locationResult)
|
||||
onNewLocation(locationResult.lastLocation)
|
||||
}
|
||||
}
|
||||
createLocationRequest()
|
||||
getLastLocation()
|
||||
val handlerThread = HandlerThread(TAG)
|
||||
handlerThread.start()
|
||||
serviceHandler = Handler(handlerThread.looper)
|
||||
notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
// Android O requires a Notification Channel.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name: CharSequence = getString(R.string.app_name)
|
||||
// Create the channel for the notification
|
||||
val mChannel =
|
||||
NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_DEFAULT)
|
||||
|
||||
// Set the Notification Channel for the Notification Manager.
|
||||
notificationManager.createNotificationChannel(mChannel)
|
||||
}
|
||||
|
||||
startForeground(NOTIFICATION_ID, getNotification())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the [NotificationCompat] used as part of the foreground service.
|
||||
*/
|
||||
private fun getNotification(): Notification {
|
||||
val builder: NotificationCompat.Builder = NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentText(getString(R.string.auto_location_service))
|
||||
.setContentTitle(getString(R.string.app_name))
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationManagerCompat.IMPORTANCE_HIGH)
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setTicker(getString(R.string.auto_location_service))
|
||||
.setWhen(System.currentTimeMillis())
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent?): IBinder {
|
||||
return binder
|
||||
}
|
||||
|
||||
private fun createLocationRequest() {
|
||||
locationRequest = LocationRequest()
|
||||
locationRequest.interval = UPDATE_INTERVAL_IN_MILLISECONDS
|
||||
locationRequest.fastestInterval = FASTEST_UPDATE_INTERVAL_IN_MILLISECONDS
|
||||
locationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
|
||||
}
|
||||
|
||||
private fun onNewLocation(location: Location) {
|
||||
Log.i(TAG, "New location: $location")
|
||||
this.location = location
|
||||
|
||||
// Notify anyone listening for broadcasts about the new location.
|
||||
val intent = Intent(ACTION_BROADCAST)
|
||||
intent.putExtra(EXTRA_LOCATION, location)
|
||||
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
|
||||
}
|
||||
|
||||
private fun getLastLocation() {
|
||||
try {
|
||||
fusedLocationClient.lastLocation
|
||||
.addOnCompleteListener { task ->
|
||||
if (task.isSuccessful && task.result != null) {
|
||||
location = task.result
|
||||
} else {
|
||||
Log.w(TAG, "Failed to get location.")
|
||||
}
|
||||
}
|
||||
} catch (unlikely: SecurityException) {
|
||||
Log.e(TAG, "Lost location permission.$unlikely")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a request for location updates. Note that in this sample we merely log the
|
||||
* [SecurityException].
|
||||
*/
|
||||
fun requestLocationUpdates() {
|
||||
Log.i(TAG, "Requesting location updates")
|
||||
startService(Intent(applicationContext, CarLocationService::class.java))
|
||||
try {
|
||||
fusedLocationClient.requestLocationUpdates(
|
||||
locationRequest,
|
||||
locationCallback, Looper.myLooper()
|
||||
)
|
||||
} catch (unlikely: SecurityException) {
|
||||
Log.e(TAG, "Lost location permission. Could not request updates. $unlikely")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes location updates. Note that in this sample we merely log the
|
||||
* [SecurityException].
|
||||
*/
|
||||
fun removeLocationUpdates() {
|
||||
Log.i(TAG, "Removing location updates")
|
||||
try {
|
||||
fusedLocationClient.removeLocationUpdates(locationCallback)
|
||||
stopSelf()
|
||||
} catch (unlikely: SecurityException) {
|
||||
Log.e(TAG, "Lost location permission. Could not remove updates. $unlikely")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
Log.i(TAG, "Service started")
|
||||
// Tells the system to not try to recreate the service after it has been killed.
|
||||
return START_NOT_STICKY
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
serviceHandler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
class LocalBinder(val service: CarLocationService) : Binder()
|
||||
}
|
||||
295
app/src/google/java/net/vonforst/evmap/auto/ChargepriceScreen.kt
Normal file
@@ -0,0 +1,295 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.Model
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import moe.banana.jsonapi2.HasMany
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
import moe.banana.jsonapi2.JsonBuffer
|
||||
import moe.banana.jsonapi2.ResourceIdentifier
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.currency
|
||||
import java.io.IOException
|
||||
|
||||
class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(ctx) {
|
||||
private val prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
ChargepriceApi.create(carContext.getString(R.string.chargeprice_key))
|
||||
}
|
||||
private var prices: List<ChargePrice>? = null
|
||||
private var meta: ChargepriceChargepointMeta? = null
|
||||
private val maxRows = 6
|
||||
private var errorMessage: String? = null
|
||||
private val batteryRange = listOf(20.0, 80.0)
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
if (prices == null) loadData()
|
||||
|
||||
return ListTemplate.Builder().apply {
|
||||
setTitle(
|
||||
carContext.getString(
|
||||
R.string.chargeprice_battery_range,
|
||||
batteryRange[0],
|
||||
batteryRange[1]
|
||||
) + " · " + carContext.getString(R.string.powered_by_chargeprice)
|
||||
)
|
||||
setHeaderAction(Action.BACK)
|
||||
if (prices == null && errorMessage == null) {
|
||||
setLoading(true)
|
||||
} else {
|
||||
setSingleList(ItemList.Builder().apply {
|
||||
setNoItemsMessage(
|
||||
errorMessage ?: carContext.getString(R.string.chargeprice_no_tariffs_found)
|
||||
)
|
||||
prices?.take(maxRows)?.forEach { price ->
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(formatProvider(price))
|
||||
addText(formatPrice(price))
|
||||
}.build())
|
||||
}
|
||||
}.build())
|
||||
}
|
||||
setActionStrip(
|
||||
ActionStrip.Builder().addAction(
|
||||
Action.Builder().setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_chargeprice
|
||||
)
|
||||
).build()
|
||||
).setOnClickListener {
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
CustomTabColorSchemeParams.Builder()
|
||||
.setToolbarColor(
|
||||
ContextCompat.getColor(
|
||||
carContext,
|
||||
R.color.colorPrimary
|
||||
)
|
||||
)
|
||||
.build()
|
||||
)
|
||||
.build().intent
|
||||
intent.data =
|
||||
Uri.parse("https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=${getDataAdapter()}")
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
carContext.startActivity(intent)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.opened_on_phone,
|
||||
CarToast.LENGTH_LONG
|
||||
).show()
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.no_browser_app_found,
|
||||
CarToast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}.build()
|
||||
).build()
|
||||
)
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun formatProvider(price: ChargePrice): String {
|
||||
if (!price.tariffName.startsWith(price.provider)) {
|
||||
return price.provider + " " + price.tariffName
|
||||
} else {
|
||||
return price.tariffName
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatPrice(price: ChargePrice): String {
|
||||
val totalPrice = carContext.getString(
|
||||
R.string.charge_price_format,
|
||||
price.chargepointPrices.first().price,
|
||||
currency(price.currency)
|
||||
)
|
||||
val kwhPrice = if (price.chargepointPrices.first().price > 0f) {
|
||||
carContext.getString(
|
||||
if (price.chargepointPrices[0].priceDistribution.isOnlyKwh) {
|
||||
R.string.charge_price_kwh_format
|
||||
} else {
|
||||
R.string.charge_price_average_format
|
||||
},
|
||||
price.chargepointPrices.get(0).price / meta!!.energy,
|
||||
currency(price.currency)
|
||||
)
|
||||
} else null
|
||||
val monthlyFees = if (price.totalMonthlyFee > 0 || price.monthlyMinSales > 0) {
|
||||
price.formatMonthlyFees(carContext)
|
||||
} else null
|
||||
var text = totalPrice
|
||||
if (kwhPrice != null && monthlyFees != null) {
|
||||
text += " ($kwhPrice, $monthlyFees)"
|
||||
} else if (kwhPrice != null) {
|
||||
text += " ($kwhPrice)"
|
||||
} else if (monthlyFees != null) {
|
||||
text += " ($monthlyFees)"
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
private fun loadData() {
|
||||
if (supportsCarApiLevel3(carContext)) {
|
||||
val exec = ContextCompat.getMainExecutor(carContext)
|
||||
val hardwareMan =
|
||||
carContext.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager
|
||||
hardwareMan.carInfo.fetchModel(exec) { model ->
|
||||
loadPrices(model)
|
||||
}
|
||||
} else {
|
||||
loadPrices(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadPrices(model: Model?) {
|
||||
val dataAdapter = getDataAdapter() ?: return
|
||||
val manufacturer = model?.manufacturer?.value
|
||||
val modelName = model?.name?.value
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
var vehicles = api.getVehicles().filter {
|
||||
it.id in prefs.chargepriceMyVehicles
|
||||
}
|
||||
if (vehicles.isEmpty()) {
|
||||
errorMessage = carContext.getString(R.string.chargeprice_select_car_first)
|
||||
invalidate()
|
||||
return@launch
|
||||
} else if (vehicles.size > 1) {
|
||||
if (manufacturer != null && modelName != null) {
|
||||
vehicles = vehicles.filter {
|
||||
it.brand == manufacturer && it.name.startsWith(modelName)
|
||||
}
|
||||
if (vehicles.isEmpty()) {
|
||||
errorMessage = carContext.getString(
|
||||
R.string.auto_chargeprice_vehicle_unknown,
|
||||
manufacturer,
|
||||
modelName
|
||||
)
|
||||
invalidate()
|
||||
return@launch
|
||||
} else if (vehicles.size > 1) {
|
||||
errorMessage = carContext.getString(
|
||||
R.string.auto_chargeprice_vehicle_ambiguous,
|
||||
manufacturer,
|
||||
modelName
|
||||
)
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
} else {
|
||||
errorMessage =
|
||||
carContext.getString(R.string.auto_chargeprice_vehicle_unavailable)
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
}
|
||||
val car = vehicles[0]
|
||||
|
||||
val cpStation = ChargepriceStation.fromEvmap(charger, car.compatibleEvmapConnectors)
|
||||
val result = api.getChargePrices(ChargepriceRequest().apply {
|
||||
this.dataAdapter = dataAdapter
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
tariffs = if (!prefs.chargepriceMyTariffsAll) {
|
||||
val myTariffs = prefs.chargepriceMyTariffs ?: emptySet()
|
||||
HasMany<ChargepriceTariff>(*myTariffs.map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
it
|
||||
)
|
||||
}.toTypedArray()).apply {
|
||||
meta = JsonBuffer.create(
|
||||
ChargepriceApi.moshi.adapter(ChargepriceRequestTariffMeta::class.java),
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS)
|
||||
)
|
||||
}
|
||||
} else null
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange,
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency
|
||||
)
|
||||
}, ChargepriceApi.getChargepriceLanguage())
|
||||
|
||||
val myTariffs = prefs.chargepriceMyTariffs
|
||||
|
||||
// choose the highest power chargepoint compatible with the car
|
||||
val chargepoint = cpStation.chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull { it.power }
|
||||
if (chargepoint == null) {
|
||||
errorMessage =
|
||||
carContext.getString(R.string.chargeprice_no_compatible_connectors)
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
meta =
|
||||
(result.meta.get<ChargepriceMeta>(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta).chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull {
|
||||
it.power
|
||||
}
|
||||
|
||||
prices = result.map { cp ->
|
||||
val filteredPrices =
|
||||
cp.chargepointPrices.filter {
|
||||
it.plug == chargepoint.plug && it.power == chargepoint.power
|
||||
}
|
||||
if (filteredPrices.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
cp.clone().apply {
|
||||
chargepointPrices = filteredPrices
|
||||
}
|
||||
}
|
||||
}.filterNotNull()
|
||||
.sortedBy { it.chargepointPrices.first().price }
|
||||
.sortedByDescending {
|
||||
prefs.chargepriceMyTariffsAll ||
|
||||
myTariffs != null && it.tariff?.get()?.id in myTariffs
|
||||
}
|
||||
invalidate()
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.chargeprice_connection_error,
|
||||
CarToast.LENGTH_LONG
|
||||
)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDataAdapter(): String? = when (charger.dataSource) {
|
||||
"goingelectric" -> ChargepriceApi.DATA_SOURCE_GOINGELECTRIC
|
||||
"openchargemap" -> ChargepriceApi.DATA_SOURCE_OPENCHARGEMAP
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.nameForPlugType
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.ChargerIconGenerator
|
||||
import net.vonforst.evmap.ui.availabilityText
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import net.vonforst.evmap.viewmodel.getReferenceData
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) {
|
||||
var charger: ChargeLocation? = null
|
||||
var photo: Bitmap? = null
|
||||
private var availability: ChargeLocationStatus? = null
|
||||
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
createApi(prefs.dataSource, ctx)
|
||||
}
|
||||
private val referenceData = api.getReferenceData(lifecycleScope, carContext)
|
||||
|
||||
private val imageSize = 128 // images should be 128dp according to docs
|
||||
|
||||
private val iconGen =
|
||||
ChargerIconGenerator(carContext, null, oversize = 1.4f, height = imageSize)
|
||||
|
||||
init {
|
||||
referenceData.observe(this) {
|
||||
loadCharger()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
if (charger == null) loadCharger()
|
||||
|
||||
return PaneTemplate.Builder(
|
||||
Pane.Builder().apply {
|
||||
charger?.let { charger ->
|
||||
addRow(Row.Builder().apply {
|
||||
setTitle(charger.address.toString())
|
||||
|
||||
val icon = iconGen.getBitmap(
|
||||
tint = getMarkerTint(charger),
|
||||
fault = charger.faultReport != null,
|
||||
multi = charger.isMulti()
|
||||
)
|
||||
setImage(
|
||||
CarIcon.Builder(IconCompat.createWithBitmap(icon)).build(),
|
||||
Row.IMAGE_TYPE_LARGE
|
||||
)
|
||||
|
||||
val chargepointsText = SpannableStringBuilder()
|
||||
charger.chargepointsMerged.forEachIndexed { i, cp ->
|
||||
if (i > 0) chargepointsText.append(" · ")
|
||||
chargepointsText.append(
|
||||
"${cp.count}× ${
|
||||
nameForPlugType(
|
||||
carContext.stringProvider(),
|
||||
cp.type
|
||||
)
|
||||
} ${cp.formatPower()}"
|
||||
)
|
||||
availability?.status?.get(cp)?.let { status ->
|
||||
chargepointsText.append(
|
||||
" (${availabilityText(status)}/${cp.count})",
|
||||
ForegroundCarColorSpan.create(carAvailabilityColor(status)),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
}
|
||||
addText(chargepointsText)
|
||||
}.build())
|
||||
addRow(Row.Builder().apply {
|
||||
photo?.let {
|
||||
setImage(
|
||||
CarIcon.Builder(IconCompat.createWithBitmap(photo)).build(),
|
||||
Row.IMAGE_TYPE_LARGE
|
||||
)
|
||||
}
|
||||
val operatorText = StringBuilder().apply {
|
||||
charger.operator?.let { append(it) }
|
||||
charger.network?.let {
|
||||
if (isNotEmpty()) append(" · ")
|
||||
append(it)
|
||||
}
|
||||
}.ifEmpty {
|
||||
carContext.getString(R.string.unknown_operator)
|
||||
}
|
||||
setTitle(operatorText)
|
||||
|
||||
charger.cost?.let { addText(it.getStatusText(carContext, emoji = true)) }
|
||||
charger.faultReport?.created?.let {
|
||||
addText(
|
||||
carContext.getString(
|
||||
R.string.auto_fault_report_date,
|
||||
it.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/*val types = charger.chargepoints.map { it.type }.distinct()
|
||||
if (types.size == 1) {
|
||||
setImage(
|
||||
CarIcon.of(IconCompat.createWithResource(carContext, iconForPlugType(types[0]))),
|
||||
Row.IMAGE_TYPE_ICON)
|
||||
}*/
|
||||
}.build())
|
||||
addAction(Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_navigation
|
||||
)
|
||||
).build()
|
||||
)
|
||||
.setTitle(carContext.getString(R.string.navigate))
|
||||
.setBackgroundColor(CarColor.PRIMARY)
|
||||
.setOnClickListener {
|
||||
navigateToCharger(charger)
|
||||
}
|
||||
.build())
|
||||
charger.chargepriceData?.country?.let { country ->
|
||||
if (ChargepriceApi.isCountrySupported(country, charger.dataSource)) {
|
||||
addAction(Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_chargeprice
|
||||
)
|
||||
).build()
|
||||
)
|
||||
.setTitle(carContext.getString(R.string.auto_prices))
|
||||
.setOnClickListener {
|
||||
screenManager.push(ChargepriceScreen(carContext, charger))
|
||||
}
|
||||
.build())
|
||||
}
|
||||
}
|
||||
} ?: setLoading(true)
|
||||
}.build()
|
||||
).apply {
|
||||
setTitle(chargerSparse.name)
|
||||
setHeaderAction(Action.BACK)
|
||||
setActionStrip(
|
||||
ActionStrip.Builder().addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.open_in_app))
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
val intent = Intent(carContext, MapsActivity::class.java)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(EXTRA_CHARGER_ID, chargerSparse.id)
|
||||
.putExtra(EXTRA_LAT, chargerSparse.coordinates.lat)
|
||||
.putExtra(EXTRA_LON, chargerSparse.coordinates.lng)
|
||||
carContext.startActivity(intent)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.opened_on_phone,
|
||||
CarToast.LENGTH_LONG
|
||||
).show()
|
||||
})
|
||||
.build()
|
||||
).build()
|
||||
)
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun navigateToCharger(charger: ChargeLocation) {
|
||||
val coord = charger.coordinates
|
||||
val intent =
|
||||
Intent(
|
||||
CarContext.ACTION_NAVIGATE,
|
||||
Uri.parse("geo:0,0?q=${coord.lat},${coord.lng}(${charger.name})")
|
||||
)
|
||||
carContext.startCarApp(intent)
|
||||
}
|
||||
|
||||
private fun loadCharger() {
|
||||
val referenceData = referenceData.value ?: return
|
||||
lifecycleScope.launch {
|
||||
val response = api.getChargepointDetail(referenceData, chargerSparse.id)
|
||||
if (response.status == Status.SUCCESS) {
|
||||
charger = response.data!!
|
||||
|
||||
val photo = charger?.photos?.firstOrNull()
|
||||
photo?.let {
|
||||
val size = (carContext.resources.displayMetrics.density * 64).roundToInt()
|
||||
val url = photo.getUrl(size = size)
|
||||
val request = ImageRequest.Builder(carContext).data(url).build()
|
||||
this@ChargerDetailScreen.photo =
|
||||
(carContext.imageLoader.execute(request).drawable as BitmapDrawable).bitmap
|
||||
}
|
||||
|
||||
availability = charger?.let { getAvailability(it).data }
|
||||
|
||||
invalidate()
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(carContext, R.string.connection_error, CarToast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
93
app/src/google/java/net/vonforst/evmap/auto/FilterScreen.kt
Normal file
@@ -0,0 +1,93 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.model.FILTERS_CUSTOM
|
||||
import net.vonforst.evmap.model.FILTERS_DISABLED
|
||||
import net.vonforst.evmap.model.FILTERS_FAVORITES
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.FilterProfile
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class FilterScreen(ctx: CarContext) : Screen(ctx) {
|
||||
private val prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(ctx)
|
||||
val filterProfiles: LiveData<List<FilterProfile>> by lazy {
|
||||
db.filterProfileDao().getProfiles(prefs.dataSource)
|
||||
}
|
||||
private val maxRows = 6
|
||||
private val checkIcon =
|
||||
CarIcon.Builder(IconCompat.createWithResource(carContext, R.drawable.ic_check)).build()
|
||||
private val emptyIcon: CarIcon
|
||||
|
||||
init {
|
||||
val size = (ctx.resources.displayMetrics.density * 24).roundToInt()
|
||||
emptyIcon = Bitmap.createBitmap(
|
||||
size,
|
||||
size,
|
||||
Bitmap.Config.ARGB_8888
|
||||
).asCarIcon()
|
||||
}
|
||||
|
||||
init {
|
||||
filterProfiles.observe(this) {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
val filterStatus =
|
||||
prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM || it == FILTERS_FAVORITES }
|
||||
?: FILTERS_DISABLED
|
||||
return ListTemplate.Builder().apply {
|
||||
filterProfiles.value?.let {
|
||||
setSingleList(buildFilterProfilesList(it.take(maxRows), filterStatus))
|
||||
} ?: setLoading(true)
|
||||
setTitle(carContext.getString(R.string.menu_filter))
|
||||
setHeaderAction(Action.BACK)
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun buildFilterProfilesList(
|
||||
profiles: List<FilterProfile>,
|
||||
filterStatus: Long
|
||||
): ItemList {
|
||||
return ItemList.Builder().apply {
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.no_filters))
|
||||
if (FILTERS_DISABLED == filterStatus) {
|
||||
setImage(checkIcon)
|
||||
} else {
|
||||
setImage(emptyIcon)
|
||||
}
|
||||
setOnClickListener {
|
||||
prefs.filterStatus = FILTERS_DISABLED
|
||||
screenManager.pop()
|
||||
}
|
||||
}.build())
|
||||
profiles.forEach {
|
||||
addItem(Row.Builder().apply {
|
||||
val name =
|
||||
it.name.ifEmpty { carContext.getString(R.string.unnamed_filter_profile) }
|
||||
setTitle(name)
|
||||
if (it.id == filterStatus) {
|
||||
setImage(checkIcon)
|
||||
} else {
|
||||
setImage(emptyIcon)
|
||||
}
|
||||
setOnClickListener {
|
||||
prefs.filterStatus = it.id
|
||||
screenManager.pop()
|
||||
}
|
||||
}.build())
|
||||
}
|
||||
setNoItemsMessage(carContext.getString(R.string.filterprofiles_empty_state))
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
383
app/src/google/java/net/vonforst/evmap/auto/MapScreen.kt
Normal file
@@ -0,0 +1,383 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.EnergyLevel
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.car2go.maps.model.LatLng
|
||||
import kotlinx.coroutines.*
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.FILTERS_CUSTOM
|
||||
import net.vonforst.evmap.model.FILTERS_DISABLED
|
||||
import net.vonforst.evmap.model.FILTERS_FAVORITES
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.availabilityText
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import net.vonforst.evmap.viewmodel.filtersWithValue
|
||||
import net.vonforst.evmap.viewmodel.getFilterValues
|
||||
import net.vonforst.evmap.viewmodel.getFilters
|
||||
import net.vonforst.evmap.viewmodel.getReferenceData
|
||||
import java.io.IOException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* Main map screen showing either nearby chargers or favorites
|
||||
*/
|
||||
class MapScreen(ctx: CarContext, val session: EVMapSession, val favorites: Boolean = false) :
|
||||
Screen(ctx), LocationAwareScreen {
|
||||
private var updateCoroutine: Job? = null
|
||||
private var numUpdates = 0
|
||||
|
||||
/* Updating map contents is disabled - if the user uses Chargeprice from the charger
|
||||
detail screen, this already means 4 steps, after which the app would crash.
|
||||
follow https://issuetracker.google.com/issues/176694222 for updates how to solve this. */
|
||||
private val maxNumUpdates = 1
|
||||
|
||||
private var location: Location? = null
|
||||
private var lastChargerUpdateLocation: Location? = null
|
||||
private var lastDistanceUpdateTime: Instant? = null
|
||||
private var chargers: List<ChargeLocation>? = null
|
||||
private var prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
createApi(prefs.dataSource, ctx)
|
||||
}
|
||||
private val searchRadius = 5 // kilometers
|
||||
private val chargerUpdateThreshold = 2000 // meters
|
||||
private val distanceUpdateThreshold = Duration.ofSeconds(15)
|
||||
private val availabilityUpdateThreshold = Duration.ofMinutes(1)
|
||||
private var availabilities: MutableMap<Long, Pair<ZonedDateTime, ChargeLocationStatus>> =
|
||||
HashMap()
|
||||
private val maxRows = if (ctx.carAppApiLevel >= 2) {
|
||||
ctx.constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_PLACE_LIST)
|
||||
} else 6
|
||||
|
||||
private val referenceData = api.getReferenceData(lifecycleScope, carContext)
|
||||
private val filterStatus = MutableLiveData<Long>().apply {
|
||||
value = prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM || it == FILTERS_FAVORITES }
|
||||
?: FILTERS_DISABLED
|
||||
}
|
||||
private val filterValues = db.filterValueDao().getFilterValues(filterStatus, prefs.dataSource)
|
||||
private val filters = api.getFilters(referenceData, carContext.stringProvider())
|
||||
private val filtersWithValue = filtersWithValue(filters, filterValues)
|
||||
|
||||
private val hardwareMan = ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager
|
||||
private var energyLevel: EnergyLevel? = null
|
||||
|
||||
init {
|
||||
filtersWithValue.observe(this) {
|
||||
loadChargers()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
session.mapScreen = this
|
||||
return PlaceListMapTemplate.Builder().apply {
|
||||
setTitle(
|
||||
carContext.getString(
|
||||
if (favorites) {
|
||||
R.string.auto_favorites
|
||||
} else {
|
||||
R.string.auto_chargers_closeby
|
||||
}
|
||||
)
|
||||
)
|
||||
location?.let {
|
||||
setAnchor(Place.Builder(CarLocation.create(it)).build())
|
||||
} ?: setLoading(true)
|
||||
chargers?.take(maxRows)?.let { chargerList ->
|
||||
val builder = ItemList.Builder()
|
||||
// only show the city if not all chargers are in the same city
|
||||
val showCity = chargerList.map { it.address.city }.distinct().size > 1
|
||||
chargerList.forEach { charger ->
|
||||
builder.addItem(formatCharger(charger, showCity))
|
||||
}
|
||||
builder.setNoItemsMessage(
|
||||
carContext.getString(
|
||||
if (favorites) {
|
||||
R.string.auto_no_favorites_found
|
||||
} else {
|
||||
R.string.auto_no_chargers_found
|
||||
}
|
||||
)
|
||||
)
|
||||
setItemList(builder.build())
|
||||
} ?: setLoading(true)
|
||||
setCurrentLocationEnabled(true)
|
||||
setHeaderAction(Action.BACK)
|
||||
if (!favorites) {
|
||||
val filtersCount = filtersWithValue.value?.count {
|
||||
!it.value.hasSameValueAs(it.filter.defaultValue())
|
||||
}
|
||||
|
||||
setActionStrip(
|
||||
ActionStrip.Builder()
|
||||
.addAction(
|
||||
Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_filter
|
||||
)
|
||||
)
|
||||
.setTint(if (filtersCount != null && filtersCount > 0) CarColor.SECONDARY else CarColor.DEFAULT)
|
||||
.build()
|
||||
)
|
||||
.setOnClickListener {
|
||||
screenManager.pushForResult(FilterScreen(carContext)) {
|
||||
chargers = null
|
||||
numUpdates = 0
|
||||
filterStatus.value =
|
||||
prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM || it == FILTERS_FAVORITES }
|
||||
?: FILTERS_DISABLED
|
||||
}
|
||||
session.mapScreen = null
|
||||
}
|
||||
.build())
|
||||
.build())
|
||||
}
|
||||
build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun formatCharger(charger: ChargeLocation, showCity: Boolean): Row {
|
||||
val markerTint = if (charger.maxPower > 100) {
|
||||
R.color.charger_100kw_dark // slightly darker color for better contrast
|
||||
} else {
|
||||
getMarkerTint(charger)
|
||||
}
|
||||
val color = ContextCompat.getColor(carContext, markerTint)
|
||||
val place =
|
||||
Place.Builder(CarLocation.create(charger.coordinates.lat, charger.coordinates.lng))
|
||||
.setMarker(
|
||||
PlaceMarker.Builder()
|
||||
.setColor(CarColor.createCustom(color, color))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
return Row.Builder().apply {
|
||||
// only show the city if not all chargers are in the same city (-> showCity == true)
|
||||
// and the city is not already contained in the charger name
|
||||
if (showCity && charger.address.city != null && charger.address.city !in charger.name) {
|
||||
setTitle(
|
||||
CarText.Builder("${charger.name} · ${charger.address.city}")
|
||||
.addVariant(charger.name)
|
||||
.build())
|
||||
} else {
|
||||
setTitle(charger.name)
|
||||
}
|
||||
|
||||
val text = SpannableStringBuilder()
|
||||
|
||||
// distance
|
||||
location?.let {
|
||||
val distanceMeters = distanceBetween(
|
||||
it.latitude, it.longitude,
|
||||
charger.coordinates.lat, charger.coordinates.lng
|
||||
)
|
||||
text.append(
|
||||
"distance",
|
||||
DistanceSpan.create(
|
||||
roundValueToDistance(
|
||||
distanceMeters,
|
||||
energyLevel?.distanceDisplayUnit?.value
|
||||
)
|
||||
),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
// power
|
||||
if (text.isNotEmpty()) text.append(" · ")
|
||||
text.append("${charger.maxPower.roundToInt()} kW")
|
||||
|
||||
// availability
|
||||
availabilities[charger.id]?.second?.let { av ->
|
||||
val status = av.status.values.flatten()
|
||||
val available = availabilityText(status)
|
||||
val total = charger.chargepoints.sumBy { it.count }
|
||||
|
||||
if (text.isNotEmpty()) text.append(" · ")
|
||||
text.append(
|
||||
"$available/$total",
|
||||
ForegroundCarColorSpan.create(carAvailabilityColor(status)),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
)
|
||||
}
|
||||
|
||||
addText(text)
|
||||
setMetadata(
|
||||
Metadata.Builder()
|
||||
.setPlace(place)
|
||||
.build()
|
||||
)
|
||||
|
||||
setOnClickListener {
|
||||
screenManager.push(ChargerDetailScreen(carContext, charger))
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
override fun updateLocation(location: Location) {
|
||||
if (location.latitude == this.location?.latitude
|
||||
&& location.longitude == this.location?.longitude
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.location = location
|
||||
if (updateCoroutine != null) {
|
||||
// don't update while still loading last update
|
||||
return
|
||||
}
|
||||
|
||||
val now = Instant.now()
|
||||
if (lastDistanceUpdateTime == null ||
|
||||
Duration.between(lastDistanceUpdateTime, now) > distanceUpdateThreshold
|
||||
) {
|
||||
lastDistanceUpdateTime = now
|
||||
// update displayed distances
|
||||
invalidate()
|
||||
}
|
||||
|
||||
if (lastChargerUpdateLocation == null ||
|
||||
location.distanceTo(lastChargerUpdateLocation) > chargerUpdateThreshold
|
||||
) {
|
||||
lastChargerUpdateLocation = location
|
||||
// update displayed chargers
|
||||
loadChargers()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadChargers() {
|
||||
val location = location ?: return
|
||||
val referenceData = referenceData.value ?: return
|
||||
val filters = filtersWithValue.value ?: return
|
||||
|
||||
numUpdates++
|
||||
println(numUpdates)
|
||||
if (numUpdates > maxNumUpdates) {
|
||||
/*CarToast.makeText(carContext, R.string.auto_no_refresh_possible, CarToast.LENGTH_LONG)
|
||||
.show()*/
|
||||
return
|
||||
}
|
||||
updateCoroutine = lifecycleScope.launch {
|
||||
try {
|
||||
// load chargers
|
||||
if (favorites) {
|
||||
chargers = db.chargeLocationsDao().getAllChargeLocationsAsync().sortedBy {
|
||||
distanceBetween(
|
||||
location.latitude, location.longitude,
|
||||
it.coordinates.lat, it.coordinates.lng
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val response = api.getChargepointsRadius(
|
||||
referenceData,
|
||||
LatLng.fromLocation(location),
|
||||
searchRadius,
|
||||
zoom = 16f,
|
||||
filters
|
||||
)
|
||||
chargers = response.data?.filterIsInstance(ChargeLocation::class.java)
|
||||
chargers?.let {
|
||||
if (it.size < maxRows) {
|
||||
// try again with larger radius
|
||||
val response = api.getChargepointsRadius(
|
||||
referenceData,
|
||||
LatLng.fromLocation(location),
|
||||
searchRadius * 10,
|
||||
zoom = 16f,
|
||||
filters
|
||||
)
|
||||
chargers =
|
||||
response.data?.filterIsInstance(ChargeLocation::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove outdated availabilities
|
||||
availabilities = availabilities.filter {
|
||||
Duration.between(
|
||||
it.value.first,
|
||||
ZonedDateTime.now()
|
||||
) > availabilityUpdateThreshold
|
||||
}.toMutableMap()
|
||||
|
||||
// update availabilities
|
||||
chargers?.take(maxRows)?.map {
|
||||
lifecycleScope.async {
|
||||
// update only if not yet stored
|
||||
if (!availabilities.containsKey(it.id)) {
|
||||
val date = ZonedDateTime.now()
|
||||
val availability = getAvailability(it).data
|
||||
if (availability != null) {
|
||||
availabilities[it.id] = date to availability
|
||||
}
|
||||
}
|
||||
}
|
||||
}?.awaitAll()
|
||||
|
||||
updateCoroutine = null
|
||||
lastDistanceUpdateTime = Instant.now()
|
||||
invalidate()
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(carContext, R.string.connection_error, CarToast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onEnergyLevelUpdated(energyLevel: EnergyLevel) {
|
||||
this.energyLevel = energyLevel
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
private fun setupListeners() {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
carContext,
|
||||
"com.google.android.gms.permission.CAR_FUEL"
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
)
|
||||
return
|
||||
|
||||
println("Setting up energy level listener")
|
||||
|
||||
val exec = ContextCompat.getMainExecutor(carContext)
|
||||
hardwareMan.carInfo.addEnergyLevelListener(exec, ::onEnergyLevelUpdated)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
private fun removeListeners() {
|
||||
println("Removing energy level listener")
|
||||
hardwareMan.carInfo.removeEnergyLevelListener(::onEnergyLevelUpdated)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.*
|
||||
import net.vonforst.evmap.R
|
||||
|
||||
/**
|
||||
* Screen to grant permission
|
||||
*/
|
||||
class PermissionScreen(
|
||||
ctx: CarContext,
|
||||
@StringRes val message: Int,
|
||||
val permissions: List<String>
|
||||
) : Screen(ctx) {
|
||||
override fun onGetTemplate(): Template {
|
||||
return MessageTemplate.Builder(carContext.getString(message))
|
||||
.setTitle(carContext.getString(R.string.app_name))
|
||||
.setHeaderAction(Action.APP_ICON)
|
||||
.addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.grant_on_phone))
|
||||
.setBackgroundColor(CarColor.PRIMARY)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
requestPermissions()
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.cancel))
|
||||
.setOnClickListener {
|
||||
carContext.finishCarApp()
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun requestPermissions() {
|
||||
carContext.requestPermissions(permissions) { granted, rejected ->
|
||||
if (granted.containsAll(permissions)) {
|
||||
screenManager.pop()
|
||||
} else {
|
||||
requestPermissions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
149
app/src/google/java/net/vonforst/evmap/auto/Utils.kt
Normal file
@@ -0,0 +1,149 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.hardware.common.CarUnit
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Distance
|
||||
import androidx.car.app.versioning.CarAppApiLevels
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun carAvailabilityColor(status: List<ChargepointStatus>): CarColor {
|
||||
val unknown = status.any { it == ChargepointStatus.UNKNOWN }
|
||||
val available = status.count { it == ChargepointStatus.AVAILABLE }
|
||||
val allFaulted = status.all { it == ChargepointStatus.FAULTED }
|
||||
|
||||
return if (unknown) {
|
||||
CarColor.DEFAULT
|
||||
} else if (available > 0) {
|
||||
CarColor.GREEN
|
||||
} else if (allFaulted) {
|
||||
CarColor.RED
|
||||
} else {
|
||||
CarColor.BLUE
|
||||
}
|
||||
}
|
||||
|
||||
val CarContext.constraintManager
|
||||
get() = getCarService(CarContext.CONSTRAINT_SERVICE) as ConstraintManager
|
||||
|
||||
fun Bitmap.asCarIcon(): CarIcon = CarIcon.Builder(IconCompat.createWithBitmap(this)).build()
|
||||
|
||||
private const val kmPerMile = 1.609344
|
||||
private const val ftPerMile = 5280
|
||||
private const val ydPerMile = 1760
|
||||
|
||||
fun getDefaultDistanceUnit(): Int {
|
||||
return if (usesImperialUnits(Locale.getDefault())) {
|
||||
CarUnit.MILE
|
||||
} else {
|
||||
CarUnit.KILOMETER
|
||||
}
|
||||
}
|
||||
|
||||
fun usesImperialUnits(locale: Locale): Boolean {
|
||||
return locale.country in listOf("US", "GB", "MM", "LR")
|
||||
|| locale.country == "" && locale.language == "en"
|
||||
}
|
||||
|
||||
fun getDefaultSpeedUnit(): Int {
|
||||
return when (Locale.getDefault().country) {
|
||||
"US", "GB", "MM", "LR" -> CarUnit.MILES_PER_HOUR
|
||||
else -> CarUnit.KILOMETERS_PER_HOUR
|
||||
}
|
||||
}
|
||||
|
||||
fun formatCarUnitDistance(value: Float?, unit: Int?): String {
|
||||
if (value == null) return ""
|
||||
return when (unit ?: getDefaultDistanceUnit()) {
|
||||
// distance units: base unit is meters
|
||||
CarUnit.METER -> "%.0f m".format(value)
|
||||
CarUnit.KILOMETER -> "%.1f km".format(value / 1000)
|
||||
CarUnit.MILLIMETER -> "%.0f mm".format(value * 1000) // whoever uses that...
|
||||
CarUnit.MILE -> "%.1f mi".format(value / 1000 / kmPerMile)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
fun formatCarUnitSpeed(value: Float?, unit: Int?): String {
|
||||
if (value == null) return ""
|
||||
return when (unit ?: getDefaultSpeedUnit()) {
|
||||
// speed units: base unit is meters per second
|
||||
CarUnit.METERS_PER_SEC -> "%.0f m/s".format(value)
|
||||
CarUnit.KILOMETERS_PER_HOUR -> "%.0f km/h".format(value * 3.6)
|
||||
CarUnit.MILES_PER_HOUR -> "%.0f mph".format(value * 3.6 / kmPerMile)
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
fun roundValueToDistance(value: Double, unit: Int? = null): Distance {
|
||||
// value is in meters
|
||||
when (unit ?: getDefaultDistanceUnit()) {
|
||||
CarUnit.MILE -> {
|
||||
// imperial system
|
||||
val miles = value / 1000 / kmPerMile
|
||||
val yards = miles * ydPerMile
|
||||
val feet = miles * ftPerMile
|
||||
|
||||
return when (miles) {
|
||||
in 0.0..0.1 -> if (Locale.getDefault().country == "UK") {
|
||||
Distance.create(roundToMultipleOf(yards, 10.0), Distance.UNIT_YARDS)
|
||||
} else {
|
||||
Distance.create(roundToMultipleOf(feet, 10.0), Distance.UNIT_FEET)
|
||||
}
|
||||
in 0.1..10.0 -> Distance.create(
|
||||
roundToMultipleOf(miles, 0.1),
|
||||
Distance.UNIT_MILES_P1
|
||||
)
|
||||
else -> Distance.create(roundToMultipleOf(miles, 1.0), Distance.UNIT_MILES)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// metric system
|
||||
return when (value) {
|
||||
in 0.0..999.0 -> Distance.create(
|
||||
roundToMultipleOf(value, 10.0),
|
||||
Distance.UNIT_METERS
|
||||
)
|
||||
in 1000.0..10000.0 -> Distance.create(
|
||||
roundToMultipleOf(value / 1000, 0.1),
|
||||
Distance.UNIT_KILOMETERS_P1
|
||||
)
|
||||
else -> Distance.create(
|
||||
roundToMultipleOf(value / 1000, 1.0),
|
||||
Distance.UNIT_KILOMETERS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun roundToMultipleOf(num: Double, step: Double): Double {
|
||||
return (num / step).roundToInt() * step
|
||||
}
|
||||
|
||||
fun getAndroidAutoVersion(ctx: Context): List<String> {
|
||||
val info = ctx.packageManager.getPackageInfo("com.google.android.projection.gearhead", 0)
|
||||
return info.versionName.split(".")
|
||||
}
|
||||
|
||||
fun supportsCarApiLevel3(ctx: CarContext): Boolean {
|
||||
if (ctx.carAppApiLevel < CarAppApiLevels.LEVEL_3) return false
|
||||
ctx.hostInfo?.let { hostInfo ->
|
||||
if (hostInfo.packageName == "com.google.android.projection.gearhead") {
|
||||
val version = getAndroidAutoVersion(ctx)
|
||||
// Android Auto 6.7 is required. 6.6 reports supporting API Level 3,
|
||||
// but crashes when using it. See: https://issuetracker.google.com/issues/199509584
|
||||
if (version[0] < "6" || version[0] == "6" && version[1] < "7") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
215
app/src/google/java/net/vonforst/evmap/auto/VehicleDataScreen.kt
Normal file
@@ -0,0 +1,215 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.EnergyLevel
|
||||
import androidx.car.app.hardware.info.Model
|
||||
import androidx.car.app.hardware.info.Speed
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.ui.Gauge
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class VehicleDataScreen(ctx: CarContext) : Screen(ctx), LifecycleObserver {
|
||||
private val hardwareMan = ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager
|
||||
private var model: Model? = null
|
||||
private var energyLevel: EnergyLevel? = null
|
||||
private var speed: Speed? = null
|
||||
private var gauge = Gauge((ctx.resources.displayMetrics.density * 128).roundToInt(), ctx)
|
||||
private val maxSpeed = 160f / 3.6f // m/s, speed gauge will show max if speed is higher
|
||||
|
||||
private val permissions = listOf(
|
||||
"com.google.android.gms.permission.CAR_FUEL",
|
||||
"com.google.android.gms.permission.CAR_SPEED"
|
||||
)
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
if (!permissionsGranted()) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
screenManager.pushForResult(
|
||||
PermissionScreen(
|
||||
carContext,
|
||||
R.string.auto_vehicle_data_permission_needed,
|
||||
permissions
|
||||
)
|
||||
) {
|
||||
setupListeners()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val energyLevel = energyLevel
|
||||
val model = model
|
||||
val speed = speed
|
||||
|
||||
return GridTemplate.Builder().apply {
|
||||
setTitle(
|
||||
if (model != null && model.manufacturer.value != null && model.name.value != null) {
|
||||
"${model.manufacturer.value} ${model.name.value}"
|
||||
} else {
|
||||
carContext.getString(R.string.auto_vehicle_data)
|
||||
}
|
||||
)
|
||||
setHeaderAction(Action.BACK)
|
||||
if (!permissionsGranted()) {
|
||||
setLoading(true)
|
||||
} else {
|
||||
setSingleList(
|
||||
ItemList.Builder().apply {
|
||||
addItem(GridItem.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.auto_charging_level))
|
||||
if (energyLevel == null) {
|
||||
setLoading(true)
|
||||
} else if (energyLevel.batteryPercent.value != null && energyLevel.fuelPercent.value != null) {
|
||||
// both battery and fuel (Plug-in hybrid)
|
||||
setText(
|
||||
"\uD83D\uDD0C %.0f %% ⛽ %.0f %%".format(
|
||||
energyLevel.batteryPercent.value,
|
||||
energyLevel.fuelPercent.value
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
gauge.draw(
|
||||
energyLevel.batteryPercent.value,
|
||||
energyLevel.fuelPercent.value
|
||||
).asCarIcon()
|
||||
)
|
||||
} else if (energyLevel.batteryPercent.value != null) {
|
||||
// BEV
|
||||
setText("%.0f %%".format(energyLevel.batteryPercent.value))
|
||||
setImage(gauge.draw(energyLevel.batteryPercent.value).asCarIcon())
|
||||
} else if (energyLevel.fuelPercent.value != null) {
|
||||
// ICE
|
||||
setText("⛽ %.0f %%".format(energyLevel.fuelPercent.value))
|
||||
setImage(gauge.draw(energyLevel.fuelPercent.value).asCarIcon())
|
||||
} else {
|
||||
setText(carContext.getString(R.string.auto_no_data))
|
||||
setImage(gauge.draw(0f).asCarIcon())
|
||||
}
|
||||
}.build())
|
||||
addItem(GridItem.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.auto_range))
|
||||
if (energyLevel == null) {
|
||||
setLoading(true)
|
||||
} else if (energyLevel.rangeRemainingMeters.value != null) {
|
||||
setText(
|
||||
formatCarUnitDistance(
|
||||
energyLevel.rangeRemainingMeters.value,
|
||||
energyLevel.distanceDisplayUnit.value
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_car
|
||||
)
|
||||
).build()
|
||||
)
|
||||
} else {
|
||||
setText(carContext.getString(R.string.auto_no_data))
|
||||
setImage(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_car
|
||||
)
|
||||
).build()
|
||||
)
|
||||
}
|
||||
}.build())
|
||||
addItem(GridItem.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.auto_speed))
|
||||
if (speed == null) {
|
||||
setLoading(true)
|
||||
} else {
|
||||
val rawSpeed = speed.rawSpeedMetersPerSecond.value
|
||||
val displaySpeed = speed.displaySpeedMetersPerSecond.value
|
||||
if (rawSpeed != null) {
|
||||
setText(
|
||||
formatCarUnitSpeed(
|
||||
rawSpeed,
|
||||
speed.speedDisplayUnit.value
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
gauge.draw(min(rawSpeed / maxSpeed * 100, 100f)).asCarIcon()
|
||||
)
|
||||
} else if (displaySpeed != null) {
|
||||
setText(
|
||||
formatCarUnitSpeed(
|
||||
speed.displaySpeedMetersPerSecond.value,
|
||||
speed.speedDisplayUnit.value
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
gauge.draw(min(displaySpeed / maxSpeed * 100, 100f))
|
||||
.asCarIcon()
|
||||
)
|
||||
} else {
|
||||
setText(carContext.getString(R.string.auto_no_data))
|
||||
setImage(gauge.draw(0f).asCarIcon())
|
||||
}
|
||||
}
|
||||
}.build())
|
||||
}.build()
|
||||
)
|
||||
}
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun onEnergyLevelUpdated(energyLevel: EnergyLevel) {
|
||||
this.energyLevel = energyLevel
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private fun onSpeedUpdated(speed: Speed) {
|
||||
this.speed = speed
|
||||
invalidate()
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
|
||||
private fun setupListeners() {
|
||||
if (!permissionsGranted()) return
|
||||
|
||||
println("Setting up energy level listener")
|
||||
|
||||
val exec = ContextCompat.getMainExecutor(carContext)
|
||||
hardwareMan.carInfo.addEnergyLevelListener(exec, ::onEnergyLevelUpdated)
|
||||
hardwareMan.carInfo.addSpeedListener(exec, ::onSpeedUpdated)
|
||||
|
||||
hardwareMan.carInfo.fetchModel(exec) {
|
||||
this.model = it
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
|
||||
private fun removeListeners() {
|
||||
println("Removing energy level listener")
|
||||
hardwareMan.carInfo.removeEnergyLevelListener(::onEnergyLevelUpdated)
|
||||
hardwareMan.carInfo.removeSpeedListener(::onSpeedUpdated)
|
||||
}
|
||||
|
||||
private fun permissionsGranted(): Boolean =
|
||||
permissions.all {
|
||||
ContextCompat.checkSelfPermission(
|
||||
carContext,
|
||||
it
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
121
app/src/google/java/net/vonforst/evmap/auto/WelcomeScreen.kt
Normal file
@@ -0,0 +1,121 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.Manifest
|
||||
import android.location.Location
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import net.vonforst.evmap.R
|
||||
|
||||
/**
|
||||
* Welcome screen with selection between favorites and nearby chargers
|
||||
*/
|
||||
class WelcomeScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx), LocationAwareScreen {
|
||||
private var location: Location? = null
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
if (!session.locationPermissionGranted()) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
screenManager.pushForResult(
|
||||
PermissionScreen(
|
||||
carContext,
|
||||
R.string.auto_location_permission_needed,
|
||||
listOf(
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
)
|
||||
)
|
||||
) {
|
||||
session.bindLocationService()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session.mapScreen = this
|
||||
return PlaceListMapTemplate.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.app_name))
|
||||
if (!session.locationPermissionGranted()) {
|
||||
setLoading(true)
|
||||
} else {
|
||||
location?.let {
|
||||
setAnchor(Place.Builder(CarLocation.create(it)).build())
|
||||
}
|
||||
setItemList(ItemList.Builder().apply {
|
||||
addItem(
|
||||
Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.auto_chargers_closeby))
|
||||
.setImage(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_address
|
||||
)
|
||||
)
|
||||
.setTint(CarColor.DEFAULT).build()
|
||||
)
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener {
|
||||
screenManager.push(
|
||||
MapScreen(
|
||||
carContext,
|
||||
session,
|
||||
favorites = false
|
||||
)
|
||||
)
|
||||
}
|
||||
.build())
|
||||
addItem(
|
||||
Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.auto_favorites))
|
||||
.setImage(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_fav
|
||||
)
|
||||
)
|
||||
.setTint(CarColor.DEFAULT).build()
|
||||
)
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener {
|
||||
screenManager.push(MapScreen(carContext, session, favorites = true))
|
||||
}
|
||||
.build())
|
||||
if (supportsCarApiLevel3(carContext)) {
|
||||
addItem(
|
||||
Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.auto_vehicle_data))
|
||||
.setImage(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(carContext, R.drawable.ic_car)
|
||||
).setTint(CarColor.DEFAULT).build()
|
||||
)
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener {
|
||||
session.mapScreen = null
|
||||
screenManager.push(VehicleDataScreen(carContext))
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}.build())
|
||||
setCurrentLocationEnabled(true)
|
||||
}
|
||||
setHeaderAction(Action.APP_ICON)
|
||||
build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
override fun updateLocation(location: Location) {
|
||||
if (location.latitude == this.location?.latitude
|
||||
&& location.longitude == this.location?.longitude
|
||||
) {
|
||||
return
|
||||
}
|
||||
this.location = location
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package net.vonforst.evmap.autocomplete
|
||||
|
||||
import android.content.Context
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
fun getAutocompleteProviders(context: Context) =
|
||||
if (PreferenceDataSource(context).searchProvider == "google") {
|
||||
listOf(GooglePlacesAutocompleteProvider(context), MapboxAutocompleteProvider(context))
|
||||
} else {
|
||||
listOf(MapboxAutocompleteProvider(context), GooglePlacesAutocompleteProvider(context))
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package net.vonforst.evmap.autocomplete
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Typeface
|
||||
import android.text.style.CharacterStyle
|
||||
import android.text.style.StyleSpan
|
||||
import com.car2go.maps.google.adapter.AnyMapAdapter
|
||||
import com.car2go.maps.util.SphericalUtil
|
||||
import com.google.android.gms.common.api.ApiException
|
||||
import com.google.android.gms.maps.model.LatLng
|
||||
import com.google.android.gms.maps.model.LatLngBounds
|
||||
import com.google.android.gms.tasks.Tasks.await
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import com.google.android.libraries.places.api.model.AutocompleteSessionToken
|
||||
import com.google.android.libraries.places.api.model.Place
|
||||
import com.google.android.libraries.places.api.model.RectangularBounds
|
||||
import com.google.android.libraries.places.api.net.FetchPlaceRequest
|
||||
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest
|
||||
import com.google.android.libraries.places.api.net.PlacesStatusCodes
|
||||
import kotlinx.coroutines.tasks.await
|
||||
import net.vonforst.evmap.R
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
||||
class GooglePlacesAutocompleteProvider(val context: Context) : AutocompleteProvider {
|
||||
private var token = AutocompleteSessionToken.newInstance()
|
||||
private val client = Places.createClient(context)
|
||||
private val bold: CharacterStyle = StyleSpan(Typeface.BOLD)
|
||||
|
||||
override val id = "google"
|
||||
|
||||
override fun autocomplete(
|
||||
query: String,
|
||||
location: com.car2go.maps.model.LatLng?
|
||||
): List<AutocompletePlace> {
|
||||
val request = FindAutocompletePredictionsRequest.builder().apply {
|
||||
if (location != null) {
|
||||
setLocationBias(calcLocationBias(location))
|
||||
setOrigin(LatLng(location.latitude, location.longitude))
|
||||
}
|
||||
setSessionToken(token)
|
||||
setQuery(query)
|
||||
}.build()
|
||||
try {
|
||||
val result =
|
||||
await(client.findAutocompletePredictions(request)).autocompletePredictions
|
||||
return result.map {
|
||||
AutocompletePlace(
|
||||
it.getPrimaryText(bold),
|
||||
it.getSecondaryText(bold),
|
||||
it.placeId,
|
||||
it.distanceMeters?.toDouble(),
|
||||
it.placeTypes.map { AutocompletePlaceType.valueOf(it.name) })
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
val cause = e.cause
|
||||
if (cause is ApiException) {
|
||||
if (cause.statusCode == PlacesStatusCodes.OVER_QUERY_LIMIT) {
|
||||
throw ApiUnavailableException()
|
||||
}
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getDetails(id: String): PlaceWithBounds {
|
||||
val request =
|
||||
FetchPlaceRequest.builder(id, listOf(Place.Field.LAT_LNG, Place.Field.VIEWPORT)).build()
|
||||
try {
|
||||
val place = client.fetchPlace(request).await().place
|
||||
token = AutocompleteSessionToken.newInstance()
|
||||
return PlaceWithBounds(
|
||||
AnyMapAdapter.adapt(place.latLng),
|
||||
AnyMapAdapter.adapt(place.viewport)
|
||||
)
|
||||
} catch (e: ApiException) {
|
||||
if (e.statusCode == PlacesStatusCodes.OVER_QUERY_LIMIT) {
|
||||
throw ApiUnavailableException()
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAttributionString(): Int = R.string.places_powered_by_google
|
||||
|
||||
override fun getAttributionImage(dark: Boolean): Int =
|
||||
if (dark) R.drawable.places_powered_by_google_dark else R.drawable.places_powered_by_google_light
|
||||
|
||||
private fun calcLocationBias(location: com.car2go.maps.model.LatLng): RectangularBounds {
|
||||
val radius = 100e3 // meters
|
||||
val northEast =
|
||||
SphericalUtil.computeOffset(
|
||||
location,
|
||||
radius * sqrt(2.0),
|
||||
45.0
|
||||
)
|
||||
val southWest =
|
||||
SphericalUtil.computeOffset(
|
||||
location,
|
||||
radius * sqrt(2.0),
|
||||
225.0
|
||||
)
|
||||
return RectangularBounds.newInstance(
|
||||
LatLngBounds(
|
||||
AnyMapAdapter.adapt(southWest),
|
||||
AnyMapAdapter.adapt(northEast)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
@@ -28,7 +27,7 @@ class DonateFragment : Fragment() {
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
): View {
|
||||
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_donate, container, false)
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
@@ -36,14 +35,7 @@ class DonateFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val toolbar = view.findViewById(R.id.toolbar) as Toolbar
|
||||
(requireActivity() as AppCompatActivity).setSupportActionBar(toolbar)
|
||||
|
||||
val navController = findNavController()
|
||||
toolbar.setupWithNavController(
|
||||
navController,
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
(requireActivity() as AppCompatActivity).setSupportActionBar(binding.toolbar)
|
||||
|
||||
binding.productsList.apply {
|
||||
adapter = DonationAdapter().apply {
|
||||
@@ -54,6 +46,10 @@ class DonateFragment : Fragment() {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
||||
vm.products.observe(viewLifecycleOwner) {
|
||||
print(it)
|
||||
}
|
||||
|
||||
vm.purchaseSuccessful.observe(viewLifecycleOwner, Observer {
|
||||
Snackbar.make(view, R.string.donation_successful, Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
@@ -61,4 +57,12 @@ class DonateFragment : Fragment() {
|
||||
Snackbar.make(view, R.string.donation_failed, Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.toolbar.setupWithNavController(
|
||||
findNavController(),
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.animation.AnimatorSet
|
||||
import android.animation.ObjectAnimator
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import net.vonforst.evmap.databinding.FragmentOnboardingAndroidAutoBinding
|
||||
|
||||
class OnboardingViewPagerAdapter(fragment: Fragment) :
|
||||
FragmentStateAdapter(fragment) {
|
||||
override fun getItemCount(): Int = 4
|
||||
|
||||
override fun createFragment(position: Int): Fragment = when (position) {
|
||||
0 -> WelcomeFragment()
|
||||
1 -> IconsFragment()
|
||||
2 -> AndroidAutoFragment()
|
||||
3 -> DataSourceSelectFragment()
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
class AndroidAutoFragment : OnboardingPageFragment() {
|
||||
private lateinit var binding: FragmentOnboardingAndroidAutoBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentOnboardingAndroidAutoBinding.inflate(inflater, container, false)
|
||||
|
||||
binding.btnGetStarted.setOnClickListener {
|
||||
parent.goToNext()
|
||||
}
|
||||
binding.imgAndroidAuto.alpha = 0f
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@SuppressLint("Recycle")
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
val animators =
|
||||
listOf(
|
||||
ObjectAnimator.ofFloat(binding.imgAndroidAuto, "translationY", -20f, 0f).apply {
|
||||
interpolator = DecelerateInterpolator()
|
||||
},
|
||||
ObjectAnimator.ofFloat(binding.imgAndroidAuto, "alpha", 0f, 1f).apply {
|
||||
interpolator = DecelerateInterpolator()
|
||||
}
|
||||
)
|
||||
AnimatorSet().apply {
|
||||
playTogether(animators)
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
binding.imgAndroidAuto.alpha = 0f
|
||||
}
|
||||
}
|
||||
74
app/src/google/java/net/vonforst/evmap/ui/Gauge.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import net.vonforst.evmap.R
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class Gauge(val size: Int, ctx: Context) {
|
||||
val arcPaint = Paint().apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = size * 0.15f
|
||||
}
|
||||
val gaugePaint = Paint()
|
||||
val activeColor = ContextCompat.getColor(ctx, R.color.gauge_active)
|
||||
val middleColor = ContextCompat.getColor(ctx, R.color.gauge_middle)
|
||||
val inactiveColor = ContextCompat.getColor(ctx, R.color.gauge_inactive)
|
||||
|
||||
fun draw(valuePercent: Float?, secondValuePercent: Float? = null): Bitmap {
|
||||
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
|
||||
val angle = valuePercent?.let { 180f * it / 100 } ?: 0f
|
||||
val secondAngle = secondValuePercent?.let { 180f * it / 100 }
|
||||
|
||||
drawArc(angle, secondAngle, canvas)
|
||||
if (secondAngle != null) drawGauge(secondAngle, inactiveColor, canvas)
|
||||
drawGauge(angle, Color.WHITE, canvas)
|
||||
return bitmap
|
||||
}
|
||||
|
||||
private fun drawGauge(angle: Float, @ColorInt color: Int, canvas: Canvas) {
|
||||
gaugePaint.color = color
|
||||
canvas.save()
|
||||
canvas.rotate(angle - 90, size / 2f, 3 * size / 4f)
|
||||
canvas.drawCircle(size / 2f, 3 * size / 4f, size * 0.1F, gaugePaint)
|
||||
canvas.drawRect(size * 0.48f, 3 * size / 4f, size * 0.53f, size * 0.325f, gaugePaint)
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
private fun drawArc(angle: Float, secondAngle: Float?, canvas: Canvas) {
|
||||
val (angle1, angle2) = if (secondAngle != null) {
|
||||
min(angle, secondAngle) to max(angle, secondAngle)
|
||||
} else {
|
||||
angle to null
|
||||
}
|
||||
|
||||
arcPaint.color = activeColor
|
||||
val arcBounds = RectF(
|
||||
arcPaint.strokeWidth / 2,
|
||||
size / 4f + arcPaint.strokeWidth / 2,
|
||||
size - arcPaint.strokeWidth / 2,
|
||||
5 * size / 4f - arcPaint.strokeWidth / 2
|
||||
)
|
||||
|
||||
canvas.drawArc(arcBounds, 180f, angle1, false, arcPaint)
|
||||
if (angle2 != null) {
|
||||
arcPaint.color = middleColor
|
||||
canvas.drawArc(arcBounds, 180f + angle1, angle2 - angle1, false, arcPaint)
|
||||
}
|
||||
arcPaint.color = inactiveColor
|
||||
canvas.drawArc(
|
||||
arcBounds,
|
||||
180f + (angle2 ?: angle1),
|
||||
180f - (angle2 ?: angle1),
|
||||
false,
|
||||
arcPaint
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -54,12 +54,12 @@ class DonateViewModel(application: Application) : AndroidViewModel(application),
|
||||
.build()
|
||||
billingClient.querySkuDetailsAsync(params) { result, details ->
|
||||
if (result.responseCode == BillingClient.BillingResponseCode.OK && details != null) {
|
||||
products.value = Resource.success(details
|
||||
products.postValue(Resource.success(details
|
||||
.sortedBy { it.priceAmountMicros }
|
||||
.map { DonationItem(it) }
|
||||
)
|
||||
))
|
||||
} else {
|
||||
products.value = Resource.error(result.debugMessage, null)
|
||||
products.postValue(Resource.error(result.debugMessage, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/src/google/res/drawable/ic_car.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11L5,11z" />
|
||||
</vector>
|
||||
@@ -51,7 +51,7 @@
|
||||
android:id="@+id/products_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:data="@{vm.products.data}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcomeTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/welcome_android_auto"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
app:layout_constraintBottom_toTopOf="@+id/welcomeText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcomeText"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="56dp"
|
||||
android:breakStrategy="balanced"
|
||||
android:gravity="center"
|
||||
android:text="@string/welcome_android_auto_detail"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnGetStarted"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnGetStarted"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/sounds_cool"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/img_android_auto"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_marginTop="28dp"
|
||||
android:layout_marginBottom="28dp"
|
||||
android:background="@drawable/circle_bg_logo"
|
||||
android:backgroundTint="@color/android_auto_accent"
|
||||
android:scaleType="center"
|
||||
app:layout_constraintBottom_toTopOf="@+id/welcomeTitle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.7"
|
||||
app:srcCompat="@drawable/android_auto" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -25,8 +25,9 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView15"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@{item.sku.title}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
@@ -34,7 +35,7 @@
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Spende" />
|
||||
tools:text="Spende (extrem langer Beschreibungstext)" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView21"
|
||||
36
app/src/google/res/values-de/values.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.\n\nGoogle zieht von der Spende 15% Gebühren ab.</string>
|
||||
<string name="auto_location_service">EVMap läuft unter Android Auto und nutzt dafür deinen Standort.</string>
|
||||
<string name="auto_no_chargers_found">Keine Ladestationen in der Nähe gefunden</string>
|
||||
<string name="auto_no_favorites_found">Keine Favoriten gefunden</string>
|
||||
<string name="open_in_app">In App öffnen</string>
|
||||
<string name="opened_on_phone">Auf dem Telefon geöffnet</string>
|
||||
<string name="auto_location_permission_needed">Um EVMap auf Android Auto zu nutzen, braucht die App Zugriff auf deinen Standort.</string>
|
||||
<string name="auto_vehicle_data_permission_needed">Für diese Funktion benötigt EVMap Zugriff auf Daten deines Fahrzeugs.</string>
|
||||
<string name="grant_on_phone">Auf Telefon zulassen</string>
|
||||
<string name="auto_chargers_closeby">In der Nähe</string>
|
||||
<string name="auto_favorites">Favoriten</string>
|
||||
<string name="auto_fault_report_date">⚠️ Störungsmeldung (%s)</string>
|
||||
<string name="auto_no_refresh_possible">Weitere Aktualisierung nicht möglich. Bitte zurück gehen und neu starten.</string>
|
||||
<string name="auto_prices">Preise</string>
|
||||
<string name="auto_vehicle_data">Fahrzeugdaten</string>
|
||||
<string name="auto_charging_level">Ladezustand</string>
|
||||
<string name="auto_no_data">Nicht verfügbar</string>
|
||||
<string name="auto_range">Reichweite</string>
|
||||
<string name="auto_speed">Geschwindigkeit</string>
|
||||
<string name="welcome_android_auto">Android Auto-Unterstützung</string>
|
||||
<string name="welcome_android_auto_detail">Auf unterstützen Autos kannst du EVMap auch mit Android Auto nutzen. Öffne dazu einfach die EVMap-App aus dem Menü von Android Auto.</string>
|
||||
<string name="sounds_cool">klingt cool</string>
|
||||
<string name="auto_chargeprice_vehicle_unavailable">EVMap konnte das Fahrzeugmodell nicht erkennen.</string>
|
||||
<string name="auto_chargeprice_vehicle_unknown">Keins der in der App ausgewählten Fahrzeuge passt zu diesem Fahrzeug (%s %s).</string>
|
||||
<string name="auto_chargeprice_vehicle_ambiguous">Mehrere der in der App ausgewählten Fahrzeuge passen zu diesem Fahrzeug (%s %s).</string>
|
||||
</resources>
|
||||
7
app/src/google/res/values/colors.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="gauge_active">#00e676</color>
|
||||
<color name="gauge_middle">#087f23</color>
|
||||
<color name="gauge_inactive">#9e9e9e</color>
|
||||
<color name="charger_100kw_dark">#fdd835</color>
|
||||
</resources>
|
||||
10
app/src/google/res/values/styles.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="CarAppTheme">
|
||||
<item name="carColorPrimary">@color/colorPrimary</item>
|
||||
<item name="carColorPrimaryDark">@color/colorPrimaryVariant</item>
|
||||
<item name="carColorSecondary">@color/colorSecondary</item>
|
||||
<item name="carColorSecondaryDark">@color/colorSecondaryVariant</item>
|
||||
</style>
|
||||
</resources>
|
||||
46
app/src/google/res/values/values.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" tranlatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" tranlatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="pref_map_provider_default" translatable="false">google</string>
|
||||
<string name="pref_search_provider_default" translatable="false">mapbox</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.\n\nGoogle takes 15% off every donation.</string>
|
||||
<string name="auto_location_service">EVMap is running on Android Auto and using your location.</string>
|
||||
<string name="auto_no_chargers_found">No nearby chargers found</string>
|
||||
<string name="auto_no_favorites_found">No favorites found</string>
|
||||
<string name="open_in_app">Open in app</string>
|
||||
<string name="opened_on_phone">Opened on phone</string>
|
||||
<string name="auto_location_permission_needed">To run EVMap on Android Auto, you need to grant access to your location.</string>
|
||||
<string name="auto_vehicle_data_permission_needed">For this feature, EVMap needs access to your vehicle data.</string>
|
||||
<string name="grant_on_phone">Grant on phone</string>
|
||||
<string name="auto_chargers_closeby">Nearby chargers</string>
|
||||
<string name="auto_favorites">Favorites</string>
|
||||
<string name="auto_fault_report_date">⚠️ Fault report (%s)</string>
|
||||
<string name="auto_no_refresh_possible">Further updates not possible. Please go back and restart.</string>
|
||||
<string name="auto_prices">Pricing</string>
|
||||
<string name="auto_vehicle_data">Vehicle data</string>
|
||||
<string name="auto_charging_level">Charging level</string>
|
||||
<string name="auto_no_data">Unavailable</string>
|
||||
<string name="auto_range">Range</string>
|
||||
<string name="auto_speed">Speed</string>
|
||||
<string name="welcome_android_auto">Android Auto support</string>
|
||||
<string name="welcome_android_auto_detail">You can also use EVMap from within Android Auto on supported cars. Simply select the EVMap app in the Android Auto menu.</string>
|
||||
<string name="sounds_cool">sounds cool</string>
|
||||
<string name="auto_chargeprice_vehicle_unavailable">EVMap could not determine your vehicle model.</string>
|
||||
<string name="auto_chargeprice_vehicle_unknown">None of the vehicles selected in the app matches this vehicle (%s %s).</string>
|
||||
<string name="auto_chargeprice_vehicle_ambiguous">Mehrere der in der App ausgewählten Fahrzeuge passen zu diesem Fahrzeug (%s %s).</string>
|
||||
</resources>
|
||||
5
app/src/google/res/xml/automotive_app_desc.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<automotiveApp xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses
|
||||
name="template"
|
||||
tools:ignore="InvalidUsesTagAttribute" />
|
||||
</automotiveApp>
|
||||
@@ -2,8 +2,21 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.vonforst.evmap">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="geo" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="google.navigation" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".EvMapApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -13,28 +26,248 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<!--
|
||||
The API key for Google Maps-based APIs is defined as a string resource.
|
||||
(See the file "res/values/apikeys.xml").
|
||||
Note that the API key is linked to the encryption key used to sign the APK.
|
||||
You need a different API key for each encryption key, including the release key that is used to
|
||||
sign the APK for publishing.
|
||||
You can define the keys for the debug and release targets in src/debug/ and src/release/.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="@string/google_maps_key" />
|
||||
android:name="com.mapbox.ACCESS_TOKEN"
|
||||
android:value="@string/mapbox_key" />
|
||||
|
||||
<activity
|
||||
android:name=".MapsActivity"
|
||||
android:label="@string/title_activity_maps"
|
||||
android:theme="@style/AppTheme.LaunchScreen">
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.LaunchScreen"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="geo" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Deutschland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Oesterreich/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Schweiz/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Albanien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Andorra/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Aruba/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Belarus/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Belgien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Bosnien-und-Herzegowina/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Bulgarien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Daenemark/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Estland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Faeroeer/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Finnland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Frankreich/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Gibraltar/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Griechenland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Grossbritannien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Irland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Island/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Italien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Jordanien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Kasachstan/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Kroatien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Lettland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Liechtenstein/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Litauen/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Luxemburg/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Malta/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Marokko/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Mazedonien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Moldawien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Monaco/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Montenegro/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Niederlande/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Norwegen/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Polen/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Portugal/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Rumaenien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Russland/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/San-Marino/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Schweden/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Serbien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Slowakei/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Slowenien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Spanien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Tuerkei/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Tschechien/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/USA/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Ukraine/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
<data
|
||||
android:host="www.goingelectric.de"
|
||||
android:pathPattern="/stromtankstellen/Ungarn/..*/..*/..*/"
|
||||
android:scheme="https" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Override services of the com.mapzen.android.lost library with exported:false
|
||||
until https://github.com/lostzen/lost/pull/270 is merged -->
|
||||
<service
|
||||
android:name="com.mapzen.android.lost.internal.GeofencingIntentService"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="com.mapzen.lost.action.ACTION_GEOFENCING_SERVICE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
321
app/src/main/java/android/widget/Filter.java
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.widget;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* Copy of android.widget.Filter, exposing the hidden setDelayer() method.
|
||||
*
|
||||
* <p>A filter constrains data with a filtering pattern.</p>
|
||||
*
|
||||
* <p>Filters are usually created by {@link android.widget.Filterable}
|
||||
* classes.</p>
|
||||
*
|
||||
* <p>Filtering operations performed by calling {@link #filter(CharSequence)} or
|
||||
* {@link #filter(CharSequence, android.widget.Filter.FilterListener)} are
|
||||
* performed asynchronously. When these methods are called, a filtering request
|
||||
* is posted in a request queue and processed later. Any call to one of these
|
||||
* methods will cancel any previous non-executed filtering request.</p>
|
||||
*
|
||||
* @see android.widget.Filterable
|
||||
*/
|
||||
public abstract class Filter {
|
||||
private static final String LOG_TAG = "Filter";
|
||||
|
||||
private static final String THREAD_NAME = "Filter";
|
||||
private static final int FILTER_TOKEN = 0xD0D0F00D;
|
||||
private static final int FINISH_TOKEN = 0xDEADBEEF;
|
||||
|
||||
private Handler mThreadHandler;
|
||||
private Handler mResultHandler;
|
||||
|
||||
private Delayer mDelayer;
|
||||
|
||||
private final Object mLock = new Object();
|
||||
|
||||
/**
|
||||
* <p>Creates a new asynchronous filter.</p>
|
||||
*/
|
||||
public Filter() {
|
||||
mResultHandler = new ResultsHandler();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an interface that decides how long to delay the message for a given query. Useful
|
||||
* for heuristics such as posting a delay for the delete key to avoid doing any work while the
|
||||
* user holds down the delete key.
|
||||
*
|
||||
* @param delayer The delayer.
|
||||
* @hide
|
||||
*/
|
||||
public void setDelayer(Delayer delayer) {
|
||||
synchronized (mLock) {
|
||||
mDelayer = delayer;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous filtering operation. Calling this method
|
||||
* cancels all previous non-executed filtering requests and posts a new
|
||||
* filtering request that will be executed later.</p>
|
||||
*
|
||||
* @param constraint the constraint used to filter the data
|
||||
* @see #filter(CharSequence, android.widget.Filter.FilterListener)
|
||||
*/
|
||||
public final void filter(CharSequence constraint) {
|
||||
filter(constraint, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Starts an asynchronous filtering operation. Calling this method
|
||||
* cancels all previous non-executed filtering requests and posts a new
|
||||
* filtering request that will be executed later.</p>
|
||||
*
|
||||
* <p>Upon completion, the listener is notified.</p>
|
||||
*
|
||||
* @param constraint the constraint used to filter the data
|
||||
* @param listener a listener notified upon completion of the operation
|
||||
* @see #filter(CharSequence)
|
||||
* @see #performFiltering(CharSequence)
|
||||
* @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
|
||||
*/
|
||||
public final void filter(CharSequence constraint, FilterListener listener) {
|
||||
synchronized (mLock) {
|
||||
if (mThreadHandler == null) {
|
||||
HandlerThread thread = new HandlerThread(
|
||||
THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND);
|
||||
thread.start();
|
||||
mThreadHandler = new RequestHandler(thread.getLooper());
|
||||
}
|
||||
|
||||
final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint);
|
||||
|
||||
Message message = mThreadHandler.obtainMessage(FILTER_TOKEN);
|
||||
|
||||
RequestArguments args = new RequestArguments();
|
||||
// make sure we use an immutable copy of the constraint, so that
|
||||
// it doesn't change while the filter operation is in progress
|
||||
args.constraint = constraint != null ? constraint.toString() : null;
|
||||
args.listener = listener;
|
||||
message.obj = args;
|
||||
|
||||
mThreadHandler.removeMessages(FILTER_TOKEN);
|
||||
mThreadHandler.removeMessages(FINISH_TOKEN);
|
||||
mThreadHandler.sendMessageDelayed(message, delay);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Invoked in a worker thread to filter the data according to the
|
||||
* constraint. Subclasses must implement this method to perform the
|
||||
* filtering operation. Results computed by the filtering operation
|
||||
* must be returned as a {@link android.widget.Filter.FilterResults} that
|
||||
* will then be published in the UI thread through
|
||||
* {@link #publishResults(CharSequence,
|
||||
* android.widget.Filter.FilterResults)}.</p>
|
||||
*
|
||||
* <p><strong>Contract:</strong> When the constraint is null, the original
|
||||
* data must be restored.</p>
|
||||
*
|
||||
* @param constraint the constraint used to filter the data
|
||||
* @return the results of the filtering operation
|
||||
* @see #filter(CharSequence, android.widget.Filter.FilterListener)
|
||||
* @see #publishResults(CharSequence, android.widget.Filter.FilterResults)
|
||||
* @see android.widget.Filter.FilterResults
|
||||
*/
|
||||
protected abstract FilterResults performFiltering(CharSequence constraint);
|
||||
|
||||
/**
|
||||
* <p>Invoked in the UI thread to publish the filtering results in the
|
||||
* user interface. Subclasses must implement this method to display the
|
||||
* results computed in {@link #performFiltering}.</p>
|
||||
*
|
||||
* @param constraint the constraint used to filter the data
|
||||
* @param results the results of the filtering operation
|
||||
* @see #filter(CharSequence, android.widget.Filter.FilterListener)
|
||||
* @see #performFiltering(CharSequence)
|
||||
* @see android.widget.Filter.FilterResults
|
||||
*/
|
||||
protected abstract void publishResults(CharSequence constraint,
|
||||
FilterResults results);
|
||||
|
||||
/**
|
||||
* <p>Converts a value from the filtered set into a CharSequence. Subclasses
|
||||
* should override this method to convert their results. The default
|
||||
* implementation returns an empty String for null values or the default
|
||||
* String representation of the value.</p>
|
||||
*
|
||||
* @param resultValue the value to convert to a CharSequence
|
||||
* @return a CharSequence representing the value
|
||||
*/
|
||||
public CharSequence convertResultToString(Object resultValue) {
|
||||
return resultValue == null ? "" : resultValue.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Holds the results of a filtering operation. The results are the values
|
||||
* computed by the filtering operation and the number of these values.</p>
|
||||
*/
|
||||
protected static class FilterResults {
|
||||
public FilterResults() {
|
||||
// nothing to see here
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Contains all the values computed by the filtering operation.</p>
|
||||
*/
|
||||
public Object values;
|
||||
|
||||
/**
|
||||
* <p>Contains the number of values computed by the filtering
|
||||
* operation.</p>
|
||||
*/
|
||||
public int count;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Listener used to receive a notification upon completion of a filtering
|
||||
* operation.</p>
|
||||
*/
|
||||
public static interface FilterListener {
|
||||
/**
|
||||
* <p>Notifies the end of a filtering operation.</p>
|
||||
*
|
||||
* @param count the number of values computed by the filter
|
||||
*/
|
||||
public void onFilterComplete(int count);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Worker thread handler. When a new filtering request is posted from
|
||||
* {@link android.widget.Filter#filter(CharSequence, android.widget.Filter.FilterListener)},
|
||||
* it is sent to this handler.</p>
|
||||
*/
|
||||
private class RequestHandler extends Handler {
|
||||
public RequestHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Handles filtering requests by calling
|
||||
* {@link Filter#performFiltering} and then sending a message
|
||||
* with the results to the results handler.</p>
|
||||
*
|
||||
* @param msg the filtering request
|
||||
*/
|
||||
public void handleMessage(Message msg) {
|
||||
int what = msg.what;
|
||||
Message message;
|
||||
switch (what) {
|
||||
case FILTER_TOKEN:
|
||||
RequestArguments args = (RequestArguments) msg.obj;
|
||||
try {
|
||||
args.results = performFiltering(args.constraint);
|
||||
} catch (Exception e) {
|
||||
args.results = new FilterResults();
|
||||
Log.w(LOG_TAG, "An exception occured during performFiltering()!", e);
|
||||
} finally {
|
||||
message = mResultHandler.obtainMessage(what);
|
||||
message.obj = args;
|
||||
message.sendToTarget();
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
if (mThreadHandler != null) {
|
||||
Message finishMessage = mThreadHandler.obtainMessage(FINISH_TOKEN);
|
||||
mThreadHandler.sendMessageDelayed(finishMessage, 3000);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FINISH_TOKEN:
|
||||
synchronized (mLock) {
|
||||
if (mThreadHandler != null) {
|
||||
mThreadHandler.getLooper().quit();
|
||||
mThreadHandler = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Handles the results of a filtering operation. The results are
|
||||
* handled in the UI thread.</p>
|
||||
*/
|
||||
private class ResultsHandler extends Handler {
|
||||
/**
|
||||
* <p>Messages received from the request handler are processed in the
|
||||
* UI thread. The processing involves calling
|
||||
* {@link Filter#publishResults(CharSequence,
|
||||
* android.widget.Filter.FilterResults)}
|
||||
* to post the results back in the UI and then notifying the listener,
|
||||
* if any.</p>
|
||||
*
|
||||
* @param msg the filtering results
|
||||
*/
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
RequestArguments args = (RequestArguments) msg.obj;
|
||||
|
||||
publishResults(args.constraint, args.results);
|
||||
if (args.listener != null) {
|
||||
int count = args.results != null ? args.results.count : -1;
|
||||
args.listener.onFilterComplete(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>Holds the arguments of a filtering request as well as the results
|
||||
* of the request.</p>
|
||||
*/
|
||||
private static class RequestArguments {
|
||||
/**
|
||||
* <p>The constraint used to filter the data.</p>
|
||||
*/
|
||||
CharSequence constraint;
|
||||
|
||||
/**
|
||||
* <p>The listener to notify upon completion. Can be null.</p>
|
||||
*/
|
||||
FilterListener listener;
|
||||
|
||||
/**
|
||||
* <p>The results of the filtering operation.</p>
|
||||
*/
|
||||
FilterResults results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public interface Delayer {
|
||||
|
||||
/**
|
||||
* @param constraint The constraint passed to {@link Filter#filter(CharSequence)}
|
||||
* @return The delay that should be used for
|
||||
* {@link Handler#sendMessageDelayed(android.os.Message, long)}
|
||||
*/
|
||||
long getPostingDelay(CharSequence constraint);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A collection of ClusterItems that are nearby each other.
|
||||
*/
|
||||
public interface Cluster<T extends ClusterItem> {
|
||||
LatLng getPosition();
|
||||
|
||||
Collection<T> getItems();
|
||||
|
||||
int getSize();
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
|
||||
/**
|
||||
* ClusterItem represents a marker on the map.
|
||||
*/
|
||||
public interface ClusterItem {
|
||||
|
||||
/**
|
||||
* The position of this marker. This must always return the same value.
|
||||
*/
|
||||
@NonNull
|
||||
LatLng getPosition();
|
||||
|
||||
/**
|
||||
* The title of this marker.
|
||||
*/
|
||||
@Nullable
|
||||
String getTitle();
|
||||
|
||||
/**
|
||||
* The description of this marker.
|
||||
*/
|
||||
@Nullable
|
||||
String getSnippet();
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2020 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
/**
|
||||
* Base Algorithm class that implements lock/unlock functionality.
|
||||
*/
|
||||
public abstract class AbstractAlgorithm<T extends ClusterItem> implements Algorithm<T> {
|
||||
|
||||
private final ReadWriteLock mLock = new ReentrantReadWriteLock();
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
mLock.writeLock().lock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
mLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.google.maps.android.clustering.Cluster;
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Logic for computing clusters
|
||||
*/
|
||||
public interface Algorithm<T extends ClusterItem> {
|
||||
|
||||
/**
|
||||
* Adds an item to the algorithm
|
||||
*
|
||||
* @param item the item to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
boolean addItem(T item);
|
||||
|
||||
/**
|
||||
* Adds a collection of items to the algorithm
|
||||
*
|
||||
* @param items the items to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
boolean addItems(Collection<T> items);
|
||||
|
||||
void clearItems();
|
||||
|
||||
/**
|
||||
* Removes an item from the algorithm
|
||||
*
|
||||
* @param item the item to be removed
|
||||
* @return true if this algorithm contained the specified element (or equivalently, if this
|
||||
* algorithm changed as a result of the call).
|
||||
*/
|
||||
boolean removeItem(T item);
|
||||
|
||||
/**
|
||||
* Updates the provided item in the algorithm
|
||||
*
|
||||
* @param item the item to be updated
|
||||
* @return true if the item existed in the algorithm and was updated, or false if the item did
|
||||
* not exist in the algorithm and the algorithm contents remain unchanged.
|
||||
*/
|
||||
boolean updateItem(T item);
|
||||
|
||||
/**
|
||||
* Removes a collection of items from the algorithm
|
||||
*
|
||||
* @param items the items to be removed
|
||||
* @return true if this algorithm contents changed as a result of the call
|
||||
*/
|
||||
boolean removeItems(Collection<T> items);
|
||||
|
||||
Set<? extends Cluster<T>> getClusters(float zoom);
|
||||
|
||||
Collection<T> getItems();
|
||||
|
||||
void setMaxDistanceBetweenClusteredItems(int maxDistance);
|
||||
|
||||
int getMaxDistanceBetweenClusteredItems();
|
||||
|
||||
void lock();
|
||||
|
||||
void unlock();
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
import com.google.maps.android.clustering.Cluster;
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
import com.google.maps.android.geometry.Bounds;
|
||||
import com.google.maps.android.geometry.Point;
|
||||
import com.google.maps.android.projection.SphericalMercatorProjection;
|
||||
import com.google.maps.android.quadtree.PointQuadTree;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A simple clustering algorithm with O(nlog n) performance. Resulting clusters are not
|
||||
* hierarchical.
|
||||
* <p/>
|
||||
* High level algorithm:<br>
|
||||
* 1. Iterate over items in the order they were added (candidate clusters).<br>
|
||||
* 2. Create a cluster with the center of the item. <br>
|
||||
* 3. Add all items that are within a certain distance to the cluster. <br>
|
||||
* 4. Move any items out of an existing cluster if they are closer to another cluster. <br>
|
||||
* 5. Remove those items from the list of candidate clusters.
|
||||
* <p/>
|
||||
* Clusters have the center of the first element (not the centroid of the items within it).
|
||||
*/
|
||||
public class NonHierarchicalDistanceBasedAlgorithm<T extends ClusterItem> extends AbstractAlgorithm<T> {
|
||||
private static final int DEFAULT_MAX_DISTANCE_AT_ZOOM = 100; // essentially 100 dp.
|
||||
|
||||
private int mMaxDistance = DEFAULT_MAX_DISTANCE_AT_ZOOM;
|
||||
|
||||
/**
|
||||
* Any modifications should be synchronized on mQuadTree.
|
||||
*/
|
||||
private final Collection<QuadItem<T>> mItems = new LinkedHashSet<>();
|
||||
|
||||
/**
|
||||
* Any modifications should be synchronized on mQuadTree.
|
||||
*/
|
||||
private final PointQuadTree<QuadItem<T>> mQuadTree = new PointQuadTree<>(0, 1, 0, 1);
|
||||
|
||||
private static final SphericalMercatorProjection PROJECTION = new SphericalMercatorProjection(1);
|
||||
|
||||
/**
|
||||
* Adds an item to the algorithm
|
||||
*
|
||||
* @param item the item to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
@Override
|
||||
public boolean addItem(T item) {
|
||||
boolean result;
|
||||
final QuadItem<T> quadItem = new QuadItem<>(item);
|
||||
synchronized (mQuadTree) {
|
||||
result = mItems.add(quadItem);
|
||||
if (result) {
|
||||
mQuadTree.add(quadItem);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a collection of items to the algorithm
|
||||
*
|
||||
* @param items the items to be added
|
||||
* @return true if the algorithm contents changed as a result of the call
|
||||
*/
|
||||
@Override
|
||||
public boolean addItems(Collection<T> items) {
|
||||
boolean result = false;
|
||||
for (T item : items) {
|
||||
boolean individualResult = addItem(item);
|
||||
if (individualResult) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearItems() {
|
||||
synchronized (mQuadTree) {
|
||||
mItems.clear();
|
||||
mQuadTree.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item from the algorithm
|
||||
*
|
||||
* @param item the item to be removed
|
||||
* @return true if this algorithm contained the specified element (or equivalently, if this
|
||||
* algorithm changed as a result of the call).
|
||||
*/
|
||||
@Override
|
||||
public boolean removeItem(T item) {
|
||||
boolean result;
|
||||
// QuadItem delegates hashcode() and equals() to its item so,
|
||||
// removing any QuadItem to that item will remove the item
|
||||
final QuadItem<T> quadItem = new QuadItem<>(item);
|
||||
synchronized (mQuadTree) {
|
||||
result = mItems.remove(quadItem);
|
||||
if (result) {
|
||||
mQuadTree.remove(quadItem);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a collection of items from the algorithm
|
||||
*
|
||||
* @param items the items to be removed
|
||||
* @return true if this algorithm contents changed as a result of the call
|
||||
*/
|
||||
@Override
|
||||
public boolean removeItems(Collection<T> items) {
|
||||
boolean result = false;
|
||||
synchronized (mQuadTree) {
|
||||
for (T item : items) {
|
||||
// QuadItem delegates hashcode() and equals() to its item so,
|
||||
// removing any QuadItem to that item will remove the item
|
||||
final QuadItem<T> quadItem = new QuadItem<>(item);
|
||||
boolean individualResult = mItems.remove(quadItem);
|
||||
if (individualResult) {
|
||||
mQuadTree.remove(quadItem);
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the provided item in the algorithm
|
||||
*
|
||||
* @param item the item to be updated
|
||||
* @return true if the item existed in the algorithm and was updated, or false if the item did
|
||||
* not exist in the algorithm and the algorithm contents remain unchanged.
|
||||
*/
|
||||
@Override
|
||||
public boolean updateItem(T item) {
|
||||
// TODO - Can this be optimized to update the item in-place if the location hasn't changed?
|
||||
boolean result;
|
||||
synchronized (mQuadTree) {
|
||||
result = removeItem(item);
|
||||
if (result) {
|
||||
// Only add the item if it was removed (to help prevent accidental duplicates on map)
|
||||
result = addItem(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<? extends Cluster<T>> getClusters(float zoom) {
|
||||
final int discreteZoom = (int) zoom;
|
||||
|
||||
final double zoomSpecificSpan = mMaxDistance / Math.pow(2, discreteZoom) / 256;
|
||||
|
||||
final Set<QuadItem<T>> visitedCandidates = new HashSet<>();
|
||||
final Set<Cluster<T>> results = new HashSet<>();
|
||||
final Map<QuadItem<T>, Double> distanceToCluster = new HashMap<>();
|
||||
final Map<QuadItem<T>, StaticCluster<T>> itemToCluster = new HashMap<>();
|
||||
|
||||
synchronized (mQuadTree) {
|
||||
for (QuadItem<T> candidate : getClusteringItems(mQuadTree, zoom)) {
|
||||
if (visitedCandidates.contains(candidate)) {
|
||||
// Candidate is already part of another cluster.
|
||||
continue;
|
||||
}
|
||||
|
||||
Bounds searchBounds = createBoundsFromSpan(candidate.getPoint(), zoomSpecificSpan);
|
||||
Collection<QuadItem<T>> clusterItems;
|
||||
clusterItems = mQuadTree.search(searchBounds);
|
||||
if (clusterItems.size() == 1) {
|
||||
// Only the current marker is in range. Just add the single item to the results.
|
||||
results.add(candidate);
|
||||
visitedCandidates.add(candidate);
|
||||
distanceToCluster.put(candidate, 0d);
|
||||
continue;
|
||||
}
|
||||
StaticCluster<T> cluster = new StaticCluster<>(candidate.mClusterItem.getPosition());
|
||||
results.add(cluster);
|
||||
|
||||
for (QuadItem<T> clusterItem : clusterItems) {
|
||||
Double existingDistance = distanceToCluster.get(clusterItem);
|
||||
double distance = distanceSquared(clusterItem.getPoint(), candidate.getPoint());
|
||||
if (existingDistance != null) {
|
||||
// Item already belongs to another cluster. Check if it's closer to this cluster.
|
||||
if (existingDistance < distance) {
|
||||
continue;
|
||||
}
|
||||
// Move item to the closer cluster.
|
||||
itemToCluster.get(clusterItem).remove(clusterItem.mClusterItem);
|
||||
}
|
||||
distanceToCluster.put(clusterItem, distance);
|
||||
cluster.add(clusterItem.mClusterItem);
|
||||
itemToCluster.put(clusterItem, cluster);
|
||||
}
|
||||
visitedCandidates.addAll(clusterItems);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
protected Collection<QuadItem<T>> getClusteringItems(PointQuadTree<QuadItem<T>> quadTree, float zoom) {
|
||||
return mItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> getItems() {
|
||||
final Set<T> items = new LinkedHashSet<>();
|
||||
synchronized (mQuadTree) {
|
||||
for (QuadItem<T> quadItem : mItems) {
|
||||
items.add(quadItem.mClusterItem);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxDistanceBetweenClusteredItems(int maxDistance) {
|
||||
mMaxDistance = maxDistance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxDistanceBetweenClusteredItems() {
|
||||
return mMaxDistance;
|
||||
}
|
||||
|
||||
private double distanceSquared(Point a, Point b) {
|
||||
return (a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y);
|
||||
}
|
||||
|
||||
private Bounds createBoundsFromSpan(Point p, double span) {
|
||||
// TODO: Use a span that takes into account the visual size of the marker, not just its
|
||||
// LatLng.
|
||||
double halfSpan = span / 2;
|
||||
return new Bounds(
|
||||
p.x - halfSpan, p.x + halfSpan,
|
||||
p.y - halfSpan, p.y + halfSpan);
|
||||
}
|
||||
|
||||
protected static class QuadItem<T extends ClusterItem> implements PointQuadTree.Item, Cluster<T> {
|
||||
private final T mClusterItem;
|
||||
private final Point mPoint;
|
||||
private final LatLng mPosition;
|
||||
private Set<T> singletonSet;
|
||||
|
||||
private QuadItem(T item) {
|
||||
mClusterItem = item;
|
||||
mPosition = item.getPosition();
|
||||
mPoint = PROJECTION.toPoint(mPosition);
|
||||
singletonSet = Collections.singleton(mClusterItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPoint() {
|
||||
return mPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatLng getPosition() {
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<T> getItems() {
|
||||
return singletonSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mClusterItem.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof QuadItem<?>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((QuadItem<?>) other).mClusterItem.equals(mClusterItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.clustering.algo;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
import com.google.maps.android.clustering.Cluster;
|
||||
import com.google.maps.android.clustering.ClusterItem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A cluster whose center is determined upon creation.
|
||||
*/
|
||||
public class StaticCluster<T extends ClusterItem> implements Cluster<T> {
|
||||
private final LatLng mCenter;
|
||||
private final List<T> mItems = new ArrayList<T>();
|
||||
|
||||
public StaticCluster(LatLng center) {
|
||||
mCenter = center;
|
||||
}
|
||||
|
||||
public boolean add(T t) {
|
||||
return mItems.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LatLng getPosition() {
|
||||
return mCenter;
|
||||
}
|
||||
|
||||
public boolean remove(T t) {
|
||||
return mItems.remove(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<T> getItems() {
|
||||
return mItems;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSize() {
|
||||
return mItems.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StaticCluster{" +
|
||||
"mCenter=" + mCenter +
|
||||
", mItems.size=" + mItems.size() +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mCenter.hashCode() + mItems.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (!(other instanceof StaticCluster<?>)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((StaticCluster<?>) other).mCenter.equals(mCenter)
|
||||
&& ((StaticCluster<?>) other).mItems.equals(mItems);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.geometry;
|
||||
|
||||
/**
|
||||
* Represents an area in the cartesian plane.
|
||||
*/
|
||||
public class Bounds {
|
||||
public final double minX;
|
||||
public final double minY;
|
||||
|
||||
public final double maxX;
|
||||
public final double maxY;
|
||||
|
||||
public final double midX;
|
||||
public final double midY;
|
||||
|
||||
public Bounds(double minX, double maxX, double minY, double maxY) {
|
||||
this.minX = minX;
|
||||
this.minY = minY;
|
||||
this.maxX = maxX;
|
||||
this.maxY = maxY;
|
||||
|
||||
midX = (minX + maxX) / 2;
|
||||
midY = (minY + maxY) / 2;
|
||||
}
|
||||
|
||||
public boolean contains(double x, double y) {
|
||||
return minX <= x && x <= maxX && minY <= y && y <= maxY;
|
||||
}
|
||||
|
||||
public boolean contains(Point point) {
|
||||
return contains(point.x, point.y);
|
||||
}
|
||||
|
||||
public boolean intersects(double minX, double maxX, double minY, double maxY) {
|
||||
return minX < this.maxX && this.minX < maxX && minY < this.maxY && this.minY < maxY;
|
||||
}
|
||||
|
||||
public boolean intersects(Bounds bounds) {
|
||||
return intersects(bounds.minX, bounds.maxX, bounds.minY, bounds.maxY);
|
||||
}
|
||||
|
||||
public boolean contains(Bounds bounds) {
|
||||
return bounds.minX >= minX && bounds.maxX <= maxX && bounds.minY >= minY && bounds.maxY <= maxY;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.geometry;
|
||||
|
||||
public class Point {
|
||||
public final double x;
|
||||
public final double y;
|
||||
|
||||
public Point(double x, double y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Point{" +
|
||||
"x=" + x +
|
||||
", y=" + y +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.projection;
|
||||
|
||||
/**
|
||||
* @deprecated since 0.2. Use {@link com.google.maps.android.geometry.Point} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public class Point extends com.google.maps.android.geometry.Point {
|
||||
public Point(double x, double y) {
|
||||
super(x, y);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.projection;
|
||||
|
||||
import com.car2go.maps.model.LatLng;
|
||||
|
||||
public class SphericalMercatorProjection {
|
||||
final double mWorldWidth;
|
||||
|
||||
public SphericalMercatorProjection(final double worldWidth) {
|
||||
mWorldWidth = worldWidth;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
public Point toPoint(final LatLng latLng) {
|
||||
final double x = latLng.longitude / 360 + .5;
|
||||
final double siny = Math.sin(Math.toRadians(latLng.latitude));
|
||||
final double y = 0.5 * Math.log((1 + siny) / (1 - siny)) / -(2 * Math.PI) + .5;
|
||||
|
||||
return new Point(x * mWorldWidth, y * mWorldWidth);
|
||||
}
|
||||
|
||||
public LatLng toLatLng(com.google.maps.android.geometry.Point point) {
|
||||
final double x = point.x / mWorldWidth - 0.5;
|
||||
final double lng = x * 360;
|
||||
|
||||
double y = .5 - (point.y / mWorldWidth);
|
||||
final double lat = 90 - Math.toDegrees(Math.atan(Math.exp(-y * 2 * Math.PI)) * 2);
|
||||
|
||||
return new LatLng(lat, lng);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.quadtree;
|
||||
|
||||
import com.google.maps.android.geometry.Bounds;
|
||||
import com.google.maps.android.geometry.Point;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A quad tree which tracks items with a Point geometry.
|
||||
* See http://en.wikipedia.org/wiki/Quadtree for details on the data structure.
|
||||
* This class is not thread safe.
|
||||
*/
|
||||
public class PointQuadTree<T extends PointQuadTree.Item> {
|
||||
public interface Item {
|
||||
Point getPoint();
|
||||
}
|
||||
|
||||
/**
|
||||
* The bounds of this quad.
|
||||
*/
|
||||
private final Bounds mBounds;
|
||||
|
||||
/**
|
||||
* The depth of this quad in the tree.
|
||||
*/
|
||||
private final int mDepth;
|
||||
|
||||
/**
|
||||
* Maximum number of elements to store in a quad before splitting.
|
||||
*/
|
||||
private final static int MAX_ELEMENTS = 50;
|
||||
|
||||
/**
|
||||
* The elements inside this quad, if any.
|
||||
*/
|
||||
private Set<T> mItems;
|
||||
|
||||
/**
|
||||
* Maximum depth.
|
||||
*/
|
||||
private final static int MAX_DEPTH = 40;
|
||||
|
||||
/**
|
||||
* Child quads.
|
||||
*/
|
||||
private List<PointQuadTree<T>> mChildren = null;
|
||||
|
||||
/**
|
||||
* Creates a new quad tree with specified bounds.
|
||||
*
|
||||
* @param minX
|
||||
* @param maxX
|
||||
* @param minY
|
||||
* @param maxY
|
||||
*/
|
||||
public PointQuadTree(double minX, double maxX, double minY, double maxY) {
|
||||
this(new Bounds(minX, maxX, minY, maxY));
|
||||
}
|
||||
|
||||
public PointQuadTree(Bounds bounds) {
|
||||
this(bounds, 0);
|
||||
}
|
||||
|
||||
private PointQuadTree(double minX, double maxX, double minY, double maxY, int depth) {
|
||||
this(new Bounds(minX, maxX, minY, maxY), depth);
|
||||
}
|
||||
|
||||
private PointQuadTree(Bounds bounds, int depth) {
|
||||
mBounds = bounds;
|
||||
mDepth = depth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert an item.
|
||||
*/
|
||||
public void add(T item) {
|
||||
Point point = item.getPoint();
|
||||
if (this.mBounds.contains(point.x, point.y)) {
|
||||
insert(point.x, point.y, item);
|
||||
}
|
||||
}
|
||||
|
||||
private void insert(double x, double y, T item) {
|
||||
if (this.mChildren != null) {
|
||||
if (y < mBounds.midY) {
|
||||
if (x < mBounds.midX) { // top left
|
||||
mChildren.get(0).insert(x, y, item);
|
||||
} else { // top right
|
||||
mChildren.get(1).insert(x, y, item);
|
||||
}
|
||||
} else {
|
||||
if (x < mBounds.midX) { // bottom left
|
||||
mChildren.get(2).insert(x, y, item);
|
||||
} else {
|
||||
mChildren.get(3).insert(x, y, item);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (mItems == null) {
|
||||
mItems = new LinkedHashSet<>();
|
||||
}
|
||||
mItems.add(item);
|
||||
if (mItems.size() > MAX_ELEMENTS && mDepth < MAX_DEPTH) {
|
||||
split();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this quad.
|
||||
*/
|
||||
private void split() {
|
||||
mChildren = new ArrayList<PointQuadTree<T>>(4);
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.minX, mBounds.midX, mBounds.minY, mBounds.midY, mDepth + 1));
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.midX, mBounds.maxX, mBounds.minY, mBounds.midY, mDepth + 1));
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.minX, mBounds.midX, mBounds.midY, mBounds.maxY, mDepth + 1));
|
||||
mChildren.add(new PointQuadTree<T>(mBounds.midX, mBounds.maxX, mBounds.midY, mBounds.maxY, mDepth + 1));
|
||||
|
||||
Set<T> items = mItems;
|
||||
mItems = null;
|
||||
|
||||
for (T item : items) {
|
||||
// re-insert items into child quads.
|
||||
insert(item.getPoint().x, item.getPoint().y, item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given item from the set.
|
||||
*
|
||||
* @return whether the item was removed.
|
||||
*/
|
||||
public boolean remove(T item) {
|
||||
Point point = item.getPoint();
|
||||
if (this.mBounds.contains(point.x, point.y)) {
|
||||
return remove(point.x, point.y, item);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean remove(double x, double y, T item) {
|
||||
if (this.mChildren != null) {
|
||||
if (y < mBounds.midY) {
|
||||
if (x < mBounds.midX) { // top left
|
||||
return mChildren.get(0).remove(x, y, item);
|
||||
} else { // top right
|
||||
return mChildren.get(1).remove(x, y, item);
|
||||
}
|
||||
} else {
|
||||
if (x < mBounds.midX) { // bottom left
|
||||
return mChildren.get(2).remove(x, y, item);
|
||||
} else {
|
||||
return mChildren.get(3).remove(x, y, item);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mItems == null) {
|
||||
return false;
|
||||
} else {
|
||||
return mItems.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all points from the quadTree
|
||||
*/
|
||||
public void clear() {
|
||||
mChildren = null;
|
||||
if (mItems != null) {
|
||||
mItems.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for all items within a given bounds.
|
||||
*/
|
||||
public Collection<T> search(Bounds searchBounds) {
|
||||
final List<T> results = new ArrayList<T>();
|
||||
search(searchBounds, results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private void search(Bounds searchBounds, Collection<T> results) {
|
||||
if (!mBounds.intersects(searchBounds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.mChildren != null) {
|
||||
for (PointQuadTree<T> quad : mChildren) {
|
||||
quad.search(searchBounds, results);
|
||||
}
|
||||
} else if (mItems != null) {
|
||||
if (searchBounds.contains(mBounds)) {
|
||||
results.addAll(mItems);
|
||||
} else {
|
||||
for (T item : mItems) {
|
||||
if (searchBounds.contains(item.getPoint())) {
|
||||
results.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
268
app/src/main/java/com/google/maps/android/ui/IconGenerator.java
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import net.vonforst.evmap.R;
|
||||
|
||||
/**
|
||||
* IconGenerator generates icons that contain text (or custom content) within an info
|
||||
* window-like shape.
|
||||
* <p/>
|
||||
* The icon {@link Bitmap}s generated by the factory should be used in conjunction with a
|
||||
* BitmapDescriptorFactory.
|
||||
* <p/>
|
||||
* This class is not thread safe.
|
||||
*/
|
||||
public class IconGenerator {
|
||||
private final Context mContext;
|
||||
|
||||
private ViewGroup mContainer;
|
||||
private RotationLayout mRotationLayout;
|
||||
private TextView mTextView;
|
||||
private View mContentView;
|
||||
|
||||
private int mRotation;
|
||||
|
||||
private float mAnchorU = 0.5f;
|
||||
private float mAnchorV = 1f;
|
||||
|
||||
/**
|
||||
* Creates a new IconGenerator with the default style.
|
||||
*/
|
||||
public IconGenerator(Context context) {
|
||||
mContext = context;
|
||||
mContainer = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.amu_text_bubble, null);
|
||||
mRotationLayout = (RotationLayout) mContainer.getChildAt(0);
|
||||
mContentView = mTextView = (TextView) mRotationLayout.findViewById(R.id.amu_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content, then creates an icon with the current style.
|
||||
*
|
||||
* @param text the text content to display inside the icon.
|
||||
*/
|
||||
public Bitmap makeIcon(CharSequence text) {
|
||||
if (mTextView != null) {
|
||||
mTextView.setText(text);
|
||||
}
|
||||
|
||||
return makeIcon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an icon with the current content and style.
|
||||
* <p/>
|
||||
* This method is useful if a custom view has previously been set, or if text content is not
|
||||
* applicable.
|
||||
*/
|
||||
public Bitmap makeIcon() {
|
||||
int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
|
||||
mContainer.measure(measureSpec, measureSpec);
|
||||
|
||||
int measuredWidth = mContainer.getMeasuredWidth();
|
||||
int measuredHeight = mContainer.getMeasuredHeight();
|
||||
|
||||
mContainer.layout(0, 0, measuredWidth, measuredHeight);
|
||||
|
||||
if (mRotation == 1 || mRotation == 3) {
|
||||
measuredHeight = mContainer.getMeasuredWidth();
|
||||
measuredWidth = mContainer.getMeasuredHeight();
|
||||
}
|
||||
|
||||
Bitmap r = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);
|
||||
r.eraseColor(Color.TRANSPARENT);
|
||||
|
||||
Canvas canvas = new Canvas(r);
|
||||
|
||||
switch (mRotation) {
|
||||
case 0:
|
||||
// do nothing
|
||||
break;
|
||||
case 1:
|
||||
canvas.translate(measuredWidth, 0);
|
||||
canvas.rotate(90);
|
||||
break;
|
||||
case 2:
|
||||
canvas.rotate(180, measuredWidth / 2, measuredHeight / 2);
|
||||
break;
|
||||
case 3:
|
||||
canvas.translate(0, measuredHeight);
|
||||
canvas.rotate(270);
|
||||
break;
|
||||
}
|
||||
mContainer.draw(canvas);
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the child view for the icon.
|
||||
* <p/>
|
||||
* If the view contains a {@link TextView} with the id "text", operations such as {@link
|
||||
* #setTextAppearance} and {@link #makeIcon(CharSequence)} will operate upon that {@link TextView}.
|
||||
*/
|
||||
public void setContentView(View contentView) {
|
||||
mRotationLayout.removeAllViews();
|
||||
mRotationLayout.addView(contentView);
|
||||
mContentView = contentView;
|
||||
final View view = mRotationLayout.findViewById(R.id.amu_text);
|
||||
mTextView = view instanceof TextView ? (TextView) view : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the contents of the icon.
|
||||
*
|
||||
* @param degrees the amount the contents should be rotated, as a multiple of 90 degrees.
|
||||
*/
|
||||
public void setContentRotation(int degrees) {
|
||||
mRotationLayout.setViewRotation(degrees);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the icon.
|
||||
*
|
||||
* @param degrees the amount the icon should be rotated, as a multiple of 90 degrees.
|
||||
*/
|
||||
public void setRotation(int degrees) {
|
||||
mRotation = ((degrees + 360) % 360) / 90;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return u coordinate of the anchor, with rotation applied.
|
||||
*/
|
||||
public float getAnchorU() {
|
||||
return rotateAnchor(mAnchorU, mAnchorV);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return v coordinate of the anchor, with rotation applied.
|
||||
*/
|
||||
public float getAnchorV() {
|
||||
return rotateAnchor(mAnchorV, mAnchorU);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rotates the anchor around (u, v) = (0, 0).
|
||||
*/
|
||||
private float rotateAnchor(float u, float v) {
|
||||
switch (mRotation) {
|
||||
case 0:
|
||||
return u;
|
||||
case 1:
|
||||
return 1 - v;
|
||||
case 2:
|
||||
return 1 - u;
|
||||
case 3:
|
||||
return v;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text color, size, style, hint color, and highlight color from the specified
|
||||
* <code>TextAppearance</code> resource.
|
||||
*
|
||||
* @param resid the identifier of the resource.
|
||||
*/
|
||||
public void setTextAppearance(Context context, int resid) {
|
||||
if (mTextView != null) {
|
||||
mTextView.setTextAppearance(context, resid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text color, size, style, hint color, and highlight color from the specified
|
||||
* <code>TextAppearance</code> resource.
|
||||
*
|
||||
* @param resid the identifier of the resource.
|
||||
*/
|
||||
public void setTextAppearance(int resid) {
|
||||
setTextAppearance(mContext, resid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the background to a given Drawable, or remove the background.
|
||||
*
|
||||
* @param background the Drawable to use as the background, or null to remove the background.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
// View#setBackgroundDrawable is compatible with pre-API level 16 (Jelly Bean).
|
||||
public void setBackground(Drawable background) {
|
||||
mContainer.setBackgroundDrawable(background);
|
||||
|
||||
// Force setting of padding.
|
||||
// setBackgroundDrawable does not call setPadding if the background has 0 padding.
|
||||
if (background != null) {
|
||||
Rect rect = new Rect();
|
||||
background.getPadding(rect);
|
||||
mContainer.setPadding(rect.left, rect.top, rect.right, rect.bottom);
|
||||
} else {
|
||||
mContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the padding of the content view. The default padding of the content view (i.e. text
|
||||
* view) is 5dp top/bottom and 10dp left/right.
|
||||
*
|
||||
* @param left the left padding in pixels.
|
||||
* @param top the top padding in pixels.
|
||||
* @param right the right padding in pixels.
|
||||
* @param bottom the bottom padding in pixels.
|
||||
*/
|
||||
public void setContentPadding(int left, int top, int right, int bottom) {
|
||||
mContentView.setPadding(left, top, right, bottom);
|
||||
}
|
||||
|
||||
public static final int STYLE_DEFAULT = 1;
|
||||
public static final int STYLE_WHITE = 2;
|
||||
public static final int STYLE_RED = 3;
|
||||
public static final int STYLE_BLUE = 4;
|
||||
public static final int STYLE_GREEN = 5;
|
||||
public static final int STYLE_PURPLE = 6;
|
||||
public static final int STYLE_ORANGE = 7;
|
||||
|
||||
private static int getStyleColor(int style) {
|
||||
switch (style) {
|
||||
default:
|
||||
case STYLE_DEFAULT:
|
||||
case STYLE_WHITE:
|
||||
return 0xffffffff;
|
||||
case STYLE_RED:
|
||||
return 0xffcc0000;
|
||||
case STYLE_BLUE:
|
||||
return 0xff0099cc;
|
||||
case STYLE_GREEN:
|
||||
return 0xff669900;
|
||||
case STYLE_PURPLE:
|
||||
return 0xff9933cc;
|
||||
case STYLE_ORANGE:
|
||||
return 0xffff8800;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
/**
|
||||
* RotationLayout rotates the contents of the layout by multiples of 90 degrees.
|
||||
* <p/>
|
||||
* May not work with padding.
|
||||
*/
|
||||
public class RotationLayout extends FrameLayout {
|
||||
private int mRotation;
|
||||
|
||||
public RotationLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public RotationLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public RotationLayout(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
if (mRotation == 1 || mRotation == 3) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
setMeasuredDimension(getMeasuredHeight(), getMeasuredWidth());
|
||||
} else {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param degrees the rotation, in degrees.
|
||||
*/
|
||||
public void setViewRotation(int degrees) {
|
||||
mRotation = ((degrees + 360) % 360) / 90;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
if (mRotation == 0) {
|
||||
super.dispatchDraw(canvas);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRotation == 1) {
|
||||
canvas.translate(getWidth(), 0);
|
||||
canvas.rotate(90, getWidth() / 2, 0);
|
||||
canvas.translate(getHeight() / 2, getWidth() / 2);
|
||||
} else if (mRotation == 2) {
|
||||
canvas.rotate(180, getWidth() / 2, getHeight() / 2);
|
||||
} else {
|
||||
canvas.translate(0, getHeight());
|
||||
canvas.rotate(270, getWidth() / 2, 0);
|
||||
canvas.translate(getHeight() / 2, -getWidth() / 2);
|
||||
}
|
||||
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.google.maps.android.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.appcompat.widget.AppCompatTextView;
|
||||
|
||||
public class SquareTextView extends AppCompatTextView {
|
||||
private int mOffsetTop = 0;
|
||||
private int mOffsetLeft = 0;
|
||||
|
||||
public SquareTextView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SquareTextView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SquareTextView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
int width = getMeasuredWidth();
|
||||
int height = getMeasuredHeight();
|
||||
int dimension = Math.max(width, height);
|
||||
if (width > height) {
|
||||
mOffsetTop = width - height;
|
||||
mOffsetLeft = 0;
|
||||
} else {
|
||||
mOffsetTop = 0;
|
||||
mOffsetLeft = height - width;
|
||||
}
|
||||
setMeasuredDimension(dimension, dimension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
canvas.translate(mOffsetLeft / 2, mOffsetTop / 2);
|
||||
super.draw(canvas);
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,42 @@ package net.vonforst.evmap
|
||||
|
||||
import android.app.Application
|
||||
import com.facebook.stetho.Stetho
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
import org.acra.config.dialog
|
||||
import org.acra.config.limiter
|
||||
import org.acra.config.mailSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
|
||||
class EvMapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
updateNightMode(PreferenceDataSource(this))
|
||||
Stetho.initializeWithDefaults(this);
|
||||
Places.initialize(applicationContext, getString(R.string.google_maps_key));
|
||||
init(applicationContext)
|
||||
|
||||
if (!BuildConfig.DEBUG) {
|
||||
initAcra {
|
||||
buildConfigClass = BuildConfig::class.java
|
||||
reportFormat = StringFormat.KEY_VALUE_LIST
|
||||
|
||||
mailSender {
|
||||
mailTo = "evmap+crashreport@vonforst.net"
|
||||
}
|
||||
|
||||
dialog {
|
||||
text = getString(R.string.crash_report_text)
|
||||
title = getString(R.string.app_name)
|
||||
commentPrompt = getString(R.string.crash_report_comment_prompt)
|
||||
resIcon = R.drawable.ic_launcher_foreground
|
||||
resTheme = R.style.AppTheme
|
||||
}
|
||||
|
||||
limiter {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||