Compare commits
82 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -5,6 +5,7 @@ env:
|
||||
- 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=
|
||||
- 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
|
||||
@@ -34,7 +35,7 @@ cache:
|
||||
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=
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
17
README.md
@@ -1,7 +1,7 @@
|
||||
EVMap [](https://travis-ci.org/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.
|
||||
|
||||
@@ -18,6 +18,8 @@ Features
|
||||
- Realtime availability information (beta)
|
||||
- Search places
|
||||
- Favorites list, also with availability information
|
||||
- Charging price comparison, powered by [Chargeprice.app](https://chargeprice.app)
|
||||
- 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.
|
||||
@@ -32,10 +34,11 @@ Development setup
|
||||
|
||||
The App is developed using Android Studio.
|
||||
|
||||
For testing the app, you need to obtain API Keys for the
|
||||
[GoingElectric API](https://www.goingelectric.de/stromtankstellen/api/)
|
||||
For testing the app, you need to obtain free API Keys for the
|
||||
[GoingElectric API](https://www.goingelectric.de/stromtankstellen/api/),
|
||||
the [Chargeprice API](https://github.com/chargeprice/chargeprice-api-docs)
|
||||
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
|
||||
("Maps SDK for Android" and "Places API" need to be activated) and/or [Mapbox](https://www.mapbox.com/). 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:
|
||||
|
||||
@@ -44,8 +47,14 @@ following content:
|
||||
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
|
||||
insert your Google Maps key here
|
||||
</string>
|
||||
<string name="mapbox_key" translatable="false">
|
||||
insert your Mapbox key here
|
||||
</string>
|
||||
<string name="goingelectric_key" translatable="false">
|
||||
insert your GoingElectric key here
|
||||
</string>
|
||||
<string name="chargeprice_key" translatable="false">
|
||||
insert your Chargeprice key here
|
||||
</string>
|
||||
</resources>
|
||||
```
|
||||
|
||||
@@ -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 |
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/phone/11_android_auto_detail.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
_img/screenshots/phone/11_android_auto_map.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
@@ -13,8 +13,8 @@ android {
|
||||
applicationId "net.vonforst.evmap"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 37
|
||||
versionName "0.4.3"
|
||||
versionCode 46
|
||||
versionName "0.7.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -85,6 +85,13 @@ android {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +99,10 @@ dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.core:core-ktx:1.5.0-rc01'
|
||||
implementation "androidx.activity:activity-ktx:1.1.0"
|
||||
implementation "androidx.fragment:fragment-ktx:1.2.5"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.core:core:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.2.1'
|
||||
@@ -109,6 +115,8 @@ dependencies {
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.0'
|
||||
implementation 'com.squareup.moshi:moshi-kotlin:1.9.2'
|
||||
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.MikeOrtiz:TouchImageView:3.0.3'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libs_version"
|
||||
@@ -119,8 +127,11 @@ dependencies {
|
||||
implementation 'com.google.guava:guava:29.0-android'
|
||||
implementation 'com.github.pengrad:mapscaleview:1.6.0'
|
||||
|
||||
// Android Auto
|
||||
googleImplementation 'androidx.car.app:app:1.0.0-rc01'
|
||||
|
||||
// AnyMaps
|
||||
def anyMapsVersion = '7753eeb7b0'
|
||||
def anyMapsVersion = '1f050d860f'
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-base:$anyMapsVersion"
|
||||
googleImplementation "com.github.johan12345.AnyMaps:anymaps-google:$anyMapsVersion"
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-mapbox:$anyMapsVersion"
|
||||
@@ -180,3 +191,15 @@ dependencies {
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -1,10 +1,43 @@
|
||||
<?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-sdk tools:overrideLibrary="androidx.car.app" />
|
||||
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="com.google.android.geo.API_KEY"
|
||||
android:value="@string/google_maps_key" />
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
|
||||
<activity android:name=".auto.PermissionActivity" />
|
||||
</application>
|
||||
</manifest>
|
||||
664
app/src/google/java/net/vonforst/evmap/auto/CarAppService.kt
Normal file
@@ -0,0 +1,664 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.Manifest
|
||||
import android.content.*
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.location.Location
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.IBinder
|
||||
import android.os.ResultReceiver
|
||||
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.Session
|
||||
import androidx.car.app.model.*
|
||||
import androidx.car.app.model.Distance.UNIT_KILOMETERS
|
||||
import androidx.car.app.validation.HostValidator
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.*
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.api.nameForPlugType
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.ui.ChargerIconGenerator
|
||||
import net.vonforst.evmap.ui.availabilityText
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import java.io.IOException
|
||||
import java.time.Duration
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
return if (locationPermissionGranted()) {
|
||||
WelcomeScreen(carContext, this)
|
||||
} else {
|
||||
PermissionScreen(carContext, this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun locationPermissionGranted() =
|
||||
ContextCompat.checkSelfPermission(
|
||||
carContext,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
|
||||
private val locationReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
val location = intent.getParcelableExtra(CarLocationService.EXTRA_LOCATION) as Location?
|
||||
val mapScreen = this@EVMapSession.mapScreen
|
||||
if (location != null && mapScreen != null) {
|
||||
mapScreen.updateLocation(location)
|
||||
}
|
||||
this@EVMapSession.location = location
|
||||
}
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_START)
|
||||
fun bindLocationService() {
|
||||
if (!locationPermissionGranted()) return
|
||||
cas.bindService(
|
||||
Intent(cas, CarLocationService::class.java),
|
||||
serviceConnection,
|
||||
Context.BIND_AUTO_CREATE
|
||||
)
|
||||
}
|
||||
|
||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||
private fun unbindLocationService() {
|
||||
locationService?.let { service ->
|
||||
service.removeLocationUpdates()
|
||||
cas.unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
session.mapScreen = this
|
||||
return PlaceListMapTemplate.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.app_name))
|
||||
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())
|
||||
}.build())
|
||||
setCurrentLocationEnabled(true)
|
||||
setHeaderAction(Action.APP_ICON)
|
||||
build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
override fun updateLocation(location: Location) {
|
||||
this.location = location
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Screen to grant location permission
|
||||
*/
|
||||
class PermissionScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
override fun onGetTemplate(): Template {
|
||||
return MessageTemplate.Builder(carContext.getString(R.string.auto_location_permission_needed))
|
||||
.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 {
|
||||
val intent = Intent(carContext, PermissionActivity::class.java)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(
|
||||
PermissionActivity.EXTRA_RESULT_RECEIVER,
|
||||
object : ResultReceiver(null) {
|
||||
override fun onReceiveResult(
|
||||
resultCode: Int,
|
||||
resultData: Bundle?
|
||||
) {
|
||||
if (resultData!!.getBoolean(PermissionActivity.RESULT_GRANTED)) {
|
||||
session.bindLocationService()
|
||||
screenManager.push(
|
||||
WelcomeScreen(
|
||||
carContext,
|
||||
session
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
carContext.startActivity(intent)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.opened_on_phone,
|
||||
CarToast.LENGTH_LONG
|
||||
).show()
|
||||
})
|
||||
.build()
|
||||
)
|
||||
.addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.cancel))
|
||||
.setOnClickListener {
|
||||
carContext.finishCarApp()
|
||||
}
|
||||
.build(),
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
private val maxNumUpdates = 3
|
||||
|
||||
private var location: Location? = null
|
||||
private var lastUpdateLocation: Location? = null
|
||||
private var chargers: List<ChargeLocation>? = null
|
||||
private val api by lazy {
|
||||
GoingElectricApi.create(ctx.getString(R.string.goingelectric_key), context = ctx)
|
||||
}
|
||||
private val searchRadius = 5 // kilometers
|
||||
private val updateThreshold = 2000 // meters
|
||||
private val availabilityUpdateThreshold = Duration.ofMinutes(1)
|
||||
private var availabilities: MutableMap<Long, Pair<ZonedDateTime, ChargeLocationStatus>> =
|
||||
HashMap()
|
||||
private val maxRows = 6
|
||||
|
||||
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()
|
||||
chargerList.forEach { charger ->
|
||||
builder.addItem(formatCharger(charger))
|
||||
}
|
||||
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)
|
||||
build()
|
||||
}.build()
|
||||
}
|
||||
|
||||
private fun formatCharger(charger: ChargeLocation): Row {
|
||||
val color = ContextCompat.getColor(carContext, getMarkerTint(charger))
|
||||
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 {
|
||||
setTitle(charger.name)
|
||||
val text = SpannableStringBuilder()
|
||||
|
||||
// distance
|
||||
location?.let {
|
||||
val distance = distanceBetween(
|
||||
it.latitude, it.longitude,
|
||||
charger.coordinates.lat, charger.coordinates.lng
|
||||
) / 1000
|
||||
text.append(
|
||||
"distance",
|
||||
DistanceSpan.create(Distance.create(distance, UNIT_KILOMETERS)),
|
||||
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) {
|
||||
this.location = location
|
||||
if (updateCoroutine != null) {
|
||||
// don't update while still loading last update
|
||||
return
|
||||
}
|
||||
|
||||
invalidate()
|
||||
|
||||
if (lastUpdateLocation == null ||
|
||||
location.distanceTo(lastUpdateLocation) > updateThreshold
|
||||
) {
|
||||
lastUpdateLocation = location
|
||||
// update displayed chargers
|
||||
loadChargers(location)
|
||||
}
|
||||
}
|
||||
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
|
||||
private fun loadChargers(location: Location) {
|
||||
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(
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
searchRadius,
|
||||
zoom = 16f
|
||||
)
|
||||
chargers =
|
||||
response.body()?.chargelocations?.filterIsInstance(ChargeLocation::class.java)
|
||||
chargers?.let {
|
||||
if (it.size < 6) {
|
||||
// try again with larger radius
|
||||
val response = api.getChargepointsRadius(
|
||||
location.latitude,
|
||||
location.longitude,
|
||||
searchRadius * 5,
|
||||
zoom = 16f
|
||||
)
|
||||
chargers =
|
||||
response.body()?.chargelocations?.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
|
||||
invalidate()
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(carContext, R.string.connection_error, CarToast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) {
|
||||
var charger: ChargeLocation? = null
|
||||
var photo: Bitmap? = null
|
||||
private var availability: ChargeLocationStatus? = null
|
||||
|
||||
val apikey = ctx.getString(R.string.goingelectric_key)
|
||||
private val api by lazy {
|
||||
GoingElectricApi.create(apikey, context = ctx)
|
||||
}
|
||||
|
||||
private val iconGen = ChargerIconGenerator(carContext, null, oversize = 1.4f, height = 64)
|
||||
|
||||
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,
|
||||
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)
|
||||
}
|
||||
}
|
||||
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())
|
||||
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, charger.id)
|
||||
.putExtra(EXTRA_LAT, charger.coordinates.lat)
|
||||
.putExtra(EXTRA_LON, charger.coordinates.lng)
|
||||
carContext.startActivity(intent)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.opened_on_phone,
|
||||
CarToast.LENGTH_LONG
|
||||
).show()
|
||||
})
|
||||
.build()
|
||||
)
|
||||
} ?: setLoading(true)
|
||||
}.build()
|
||||
).apply {
|
||||
setTitle(chargerSparse.name)
|
||||
setHeaderAction(Action.BACK)
|
||||
}.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() {
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val response = api.getChargepointDetail(chargerSparse.id)
|
||||
charger = response.body()?.chargelocations?.get(0) as ChargeLocation
|
||||
|
||||
val photo = charger?.photos?.firstOrNull()
|
||||
photo?.let {
|
||||
val size = (carContext.resources.displayMetrics.density * 64).roundToInt()
|
||||
val url = "https://api.goingelectric.de/chargepoints/photo/?key=${apikey}" +
|
||||
"&id=${photo.id}&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()
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(carContext, R.string.connection_error, CarToast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.ResultReceiver
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
|
||||
class PermissionActivity : Activity() {
|
||||
companion object {
|
||||
const val EXTRA_RESULT_RECEIVER = "result_receiver";
|
||||
const val RESULT_GRANTED = "granted"
|
||||
}
|
||||
|
||||
private lateinit var resultReceiver: ResultReceiver
|
||||
private val permissions = arrayOf(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
private val requestCode = 1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (intent != null) {
|
||||
resultReceiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER)!!
|
||||
if (!hasPermissions(permissions)) {
|
||||
ActivityCompat.requestPermissions(this, permissions, requestCode)
|
||||
} else {
|
||||
onComplete(
|
||||
requestCode,
|
||||
permissions,
|
||||
intArrayOf(PackageManager.PERMISSION_GRANTED)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun onComplete(requestCode: Int, permissions: Array<String>?, grantResults: IntArray) {
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean(
|
||||
RESULT_GRANTED,
|
||||
grantResults.all { it == PackageManager.PERMISSION_GRANTED })
|
||||
resultReceiver.send(requestCode, bundle)
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun hasPermissions(permissions: Array<String>): Boolean {
|
||||
var result = true
|
||||
for (permission in permissions) {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
this,
|
||||
permission
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
result = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
onComplete(requestCode, permissions, grantResults)
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,15 @@
|
||||
<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 30% 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="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>
|
||||
</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>
|
||||
@@ -10,4 +10,15 @@
|
||||
</string-array>
|
||||
<string name="pref_map_provider_default" translatable="false">google</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 30% 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="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>
|
||||
</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>
|
||||
@@ -38,7 +38,224 @@
|
||||
|
||||
<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>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -9,6 +9,8 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
@@ -17,11 +19,15 @@ import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.fragment.MapFragment
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.utils.LocaleContextWrapper
|
||||
|
||||
|
||||
const val REQUEST_LOCATION_PERMISSION = 1
|
||||
const val EXTRA_CHARGER_ID = "chargerId"
|
||||
const val EXTRA_LAT = "lat"
|
||||
const val EXTRA_LON = "lon"
|
||||
|
||||
class MapsActivity : AppCompatActivity() {
|
||||
interface FragmentCallback {
|
||||
@@ -59,11 +65,66 @@ class MapsActivity : AppCompatActivity() {
|
||||
),
|
||||
findViewById<DrawerLayout>(R.id.drawer_layout)
|
||||
)
|
||||
findViewById<NavigationView>(R.id.nav_view).setupWithNavController(navController)
|
||||
val navView = findViewById<NavigationView>(R.id.nav_view)
|
||||
navView.setupWithNavController(navController)
|
||||
|
||||
val header = navView.getHeaderView(0)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(header) { v, insets ->
|
||||
v.setPadding(0, insets.getInsets(WindowInsetsCompat.Type.statusBars()).top, 0, 0)
|
||||
insets
|
||||
}
|
||||
|
||||
prefs = PreferenceDataSource(this)
|
||||
|
||||
checkPlayServices(this)
|
||||
|
||||
if (intent?.scheme == "geo") {
|
||||
val pos = intent.data?.schemeSpecificPart?.split("?")?.get(0)
|
||||
val query = intent.data?.query?.split("=")?.get(1)
|
||||
val coords = pos?.split(",")?.map { it.toDoubleOrNull() }
|
||||
|
||||
if (coords != null && coords.size == 2) {
|
||||
val lat = coords[0]
|
||||
val lon = coords[1]
|
||||
if (lat != null && lon != null && lat != 0.0 && lon != 0.0) {
|
||||
val deepLink = navController.createDeepLink()
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.map)
|
||||
.setArguments(MapFragment.showLocation(lat, lon))
|
||||
.createPendingIntent()
|
||||
deepLink.send()
|
||||
} else if (query != null && query.isNotEmpty()) {
|
||||
val deepLink = navController.createDeepLink()
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.map)
|
||||
.setArguments(MapFragment.showLocationByName(query))
|
||||
.createPendingIntent()
|
||||
deepLink.send()
|
||||
}
|
||||
}
|
||||
} else if (intent?.scheme == "https" && intent?.data?.host == "www.goingelectric.de") {
|
||||
val id = intent.data?.pathSegments?.last()?.toLongOrNull()
|
||||
if (id != null) {
|
||||
val deepLink = navController.createDeepLink()
|
||||
.setGraph(R.navigation.nav_graph)
|
||||
.setDestination(R.id.map)
|
||||
.setArguments(MapFragment.showChargerById(id))
|
||||
.createPendingIntent()
|
||||
deepLink.send()
|
||||
}
|
||||
} else if (intent.hasExtra(EXTRA_CHARGER_ID)) {
|
||||
navController.createDeepLink()
|
||||
.setDestination(R.id.map)
|
||||
.setArguments(
|
||||
MapFragment.showCharger(
|
||||
intent.getLongExtra(EXTRA_CHARGER_ID, 0),
|
||||
intent.getDoubleExtra(EXTRA_LAT, 0.0),
|
||||
intent.getDoubleExtra(EXTRA_LON, 0.0)
|
||||
)
|
||||
)
|
||||
.createPendingIntent()
|
||||
.send()
|
||||
}
|
||||
}
|
||||
|
||||
fun navigateTo(charger: ChargeLocation) {
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
package net.vonforst.evmap.adapter
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import net.vonforst.evmap.BR
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.chargeprice.ChargePrice
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceChargepointMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceTag
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.databinding.ItemChargepriceBinding
|
||||
import net.vonforst.evmap.databinding.ItemConnectorButtonBinding
|
||||
import net.vonforst.evmap.ui.CheckableConstraintLayout
|
||||
import net.vonforst.evmap.viewmodel.FavoritesViewModel
|
||||
|
||||
interface Equatable {
|
||||
@@ -88,4 +96,105 @@ class FavoritesAdapter(val vm: FavoritesViewModel) :
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_favorite
|
||||
|
||||
override fun getItemId(position: Int): Long = getItem(position).charger.id
|
||||
}
|
||||
|
||||
class ChargepriceAdapter() :
|
||||
DataBindingAdapter<ChargePrice>() {
|
||||
|
||||
val viewPool = RecyclerView.RecycledViewPool();
|
||||
var meta: ChargepriceChargepointMeta? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
var myTariffs: Set<String>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
var myTariffsAll: Boolean? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_chargeprice
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder<ChargePrice> {
|
||||
val holder = super.onCreateViewHolder(parent, viewType)
|
||||
val binding = holder.binding as ItemChargepriceBinding
|
||||
binding.rvTags.apply {
|
||||
adapter = ChargepriceTagsAdapter()
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false).apply {
|
||||
recycleChildrenOnDetach = true
|
||||
}
|
||||
itemAnimator = null
|
||||
setRecycledViewPool(viewPool)
|
||||
}
|
||||
return holder
|
||||
}
|
||||
|
||||
override fun bind(holder: ViewHolder<ChargePrice>, item: ChargePrice) {
|
||||
super.bind(holder, item)
|
||||
(holder.binding as ItemChargepriceBinding).apply {
|
||||
this.meta = this@ChargepriceAdapter.meta
|
||||
this.myTariffs = this@ChargepriceAdapter.myTariffs
|
||||
this.myTariffsAll = this@ChargepriceAdapter.myTariffsAll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CheckableConnectorAdapter : DataBindingAdapter<Chargepoint>() {
|
||||
private var checkedItem: Int? = 0
|
||||
|
||||
var enabledConnectors: List<String>? = null
|
||||
get() = field
|
||||
set(value) {
|
||||
field = value
|
||||
checkedItem?.let {
|
||||
if (value != null && getItem(it).type !in value) {
|
||||
val index = currentList.indexOfFirst {
|
||||
it.type in value
|
||||
}
|
||||
checkedItem = if (index == -1) null else index
|
||||
onCheckedItemChangedListener?.invoke(getCheckedItem())
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_connector_button
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder<Chargepoint>, position: Int) {
|
||||
val item = getItem(position)
|
||||
super.bind(holder, item)
|
||||
val binding = holder.binding as ItemConnectorButtonBinding
|
||||
binding.enabled = enabledConnectors?.let { item.type in it } ?: true
|
||||
val root = binding.root as CheckableConstraintLayout
|
||||
root.isChecked = checkedItem == position
|
||||
root.setOnClickListener {
|
||||
root.isChecked = true
|
||||
}
|
||||
root.setOnCheckedChangeListener { v: View, checked: Boolean ->
|
||||
if (checked) {
|
||||
checkedItem = position
|
||||
notifyDataSetChanged()
|
||||
onCheckedItemChangedListener?.invoke(getCheckedItem()!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getCheckedItem(): Chargepoint? = checkedItem?.let { getItem(it) }
|
||||
|
||||
fun setCheckedItem(item: Chargepoint?) {
|
||||
checkedItem = item?.let { currentList.indexOf(item) } ?: null
|
||||
}
|
||||
|
||||
var onCheckedItemChangedListener: ((Chargepoint?) -> Unit)? = null
|
||||
}
|
||||
|
||||
class ChargepriceTagsAdapter() :
|
||||
DataBindingAdapter<ChargepriceTag>() {
|
||||
override fun getItemViewType(position: Int): Int = R.layout.item_chargeprice_tag
|
||||
}
|
||||
@@ -94,16 +94,24 @@ fun buildDetails(
|
||||
loc.cost.descriptionLong ?: loc.cost.descriptionShort
|
||||
)
|
||||
else null,
|
||||
if (loc.chargecards != null && loc.chargecards.isNotEmpty()) DetailsAdapter.Detail(
|
||||
R.drawable.ic_payment,
|
||||
R.string.charge_cards,
|
||||
ctx.resources.getQuantityString(
|
||||
R.plurals.charge_cards_compatible_num,
|
||||
loc.chargecards.size, loc.chargecards.size
|
||||
),
|
||||
formatChargeCards(loc.chargecards, chargeCards, filteredChargeCards, ctx),
|
||||
clickable = true
|
||||
) else null,
|
||||
if (loc.chargecards != null && loc.chargecards.isNotEmpty() || loc.barrierFree == true)
|
||||
DetailsAdapter.Detail(
|
||||
R.drawable.ic_payment,
|
||||
R.string.charge_cards,
|
||||
listOfNotNull(
|
||||
if (loc.barrierFree == true) ctx.resources.getString(R.string.charging_barrierfree) else null,
|
||||
if (loc.chargecards != null && loc.chargecards.isNotEmpty()) {
|
||||
ctx.resources.getQuantityString(
|
||||
R.plurals.charge_cards_compatible_num,
|
||||
loc.chargecards.size, loc.chargecards.size
|
||||
)
|
||||
} else null
|
||||
).joinToString(", "),
|
||||
if (loc.chargecards != null && loc.chargecards.isNotEmpty()) {
|
||||
formatChargeCards(loc.chargecards, chargeCards, filteredChargeCards, ctx)
|
||||
} else null,
|
||||
clickable = true
|
||||
) else null,
|
||||
DetailsAdapter.Detail(
|
||||
R.drawable.ic_location,
|
||||
R.string.coordinates,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package net.vonforst.evmap.api
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.DrawableRes
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
import okhttp3.Response
|
||||
@@ -39,26 +43,35 @@ suspend fun Call.await(): Response {
|
||||
}
|
||||
}
|
||||
|
||||
const val earthRadiusKm: Double = 6372.8
|
||||
private val plugNames = mapOf(
|
||||
Chargepoint.TYPE_1 to R.string.plug_type_1,
|
||||
Chargepoint.TYPE_2 to R.string.plug_type_2,
|
||||
Chargepoint.TYPE_3 to R.string.plug_type_3,
|
||||
Chargepoint.CCS to R.string.plug_ccs,
|
||||
Chargepoint.SCHUKO to R.string.plug_schuko,
|
||||
Chargepoint.CHADEMO to R.string.plug_chademo,
|
||||
Chargepoint.SUPERCHARGER to R.string.plug_supercharger,
|
||||
Chargepoint.CEE_BLAU to R.string.plug_cee_blau,
|
||||
Chargepoint.CEE_ROT to R.string.plug_cee_rot,
|
||||
Chargepoint.TESLA_ROADSTER_HPC to R.string.plug_roadster_hpc
|
||||
)
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points on Earth in meters.
|
||||
* Latitude and longitude should be given in degrees.
|
||||
*/
|
||||
fun distanceBetween(
|
||||
startLatitude: Double, startLongitude: Double,
|
||||
endLatitude: Double, endLongitude: Double
|
||||
): Double {
|
||||
// see https://rosettacode.org/wiki/Haversine_formula#Java
|
||||
val dLat = Math.toRadians(endLatitude - startLatitude);
|
||||
val dLon = Math.toRadians(endLongitude - startLongitude);
|
||||
val originLat = Math.toRadians(startLatitude);
|
||||
val destinationLat = Math.toRadians(endLatitude);
|
||||
fun nameForPlugType(ctx: Context, type: String): String =
|
||||
plugNames[type]?.let {
|
||||
ctx.getString(it)
|
||||
} ?: type
|
||||
|
||||
val a = Math.pow(Math.sin(dLat / 2), 2.toDouble()) + Math.pow(
|
||||
Math.sin(dLon / 2),
|
||||
2.toDouble()
|
||||
) * Math.cos(originLat) * Math.cos(destinationLat);
|
||||
val c = 2 * Math.asin(Math.sqrt(a));
|
||||
return earthRadiusKm * c * 1000;
|
||||
}
|
||||
@DrawableRes
|
||||
fun iconForPlugType(type: String): Int =
|
||||
when (type) {
|
||||
Chargepoint.CCS -> R.drawable.ic_connector_ccs
|
||||
Chargepoint.CHADEMO -> R.drawable.ic_connector_chademo
|
||||
Chargepoint.SCHUKO -> R.drawable.ic_connector_schuko
|
||||
Chargepoint.SUPERCHARGER -> R.drawable.ic_connector_supercharger
|
||||
Chargepoint.TYPE_2 -> R.drawable.ic_connector_typ2
|
||||
Chargepoint.CEE_BLAU -> R.drawable.ic_connector_cee_blau
|
||||
Chargepoint.CEE_ROT -> R.drawable.ic_connector_cee_rot
|
||||
Chargepoint.TYPE_1 -> R.drawable.ic_connector_typ1
|
||||
// TODO: add other connectors
|
||||
else -> 0
|
||||
}
|
||||
@@ -8,7 +8,10 @@ import net.vonforst.evmap.api.RateLimitInterceptor
|
||||
import net.vonforst.evmap.api.await
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.viewmodel.FilterValues
|
||||
import net.vonforst.evmap.viewmodel.Resource
|
||||
import net.vonforst.evmap.viewmodel.getMultipleChoiceValue
|
||||
import net.vonforst.evmap.viewmodel.getSliderValue
|
||||
import okhttp3.JavaNetCookieJar
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
@@ -113,7 +116,21 @@ abstract class BaseAvailabilityDetector(private val client: OkHttpClient) : Avai
|
||||
data class ChargeLocationStatus(
|
||||
val status: Map<Chargepoint, List<ChargepointStatus>>,
|
||||
val source: String
|
||||
)
|
||||
) {
|
||||
fun applyFilters(filters: FilterValues?): ChargeLocationStatus {
|
||||
if (filters == null) return this
|
||||
|
||||
val connectorsVal = filters.getMultipleChoiceValue("connectors")
|
||||
val minPower = filters.getSliderValue("min_power")
|
||||
|
||||
val statusFiltered = status.filterKeys {
|
||||
(connectorsVal.all || it.type in connectorsVal.values) && it.power > minPower
|
||||
}
|
||||
return this.copy(status = statusFiltered)
|
||||
}
|
||||
|
||||
val totalChargepoints = status.map { it.key.count }.sum()
|
||||
}
|
||||
|
||||
enum class ChargepointStatus {
|
||||
AVAILABLE, UNKNOWN, CHARGING, OCCUPIED, FAULTED
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package net.vonforst.evmap.api.availability
|
||||
|
||||
import com.squareup.moshi.JsonClass
|
||||
import net.vonforst.evmap.api.distanceBetween
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
@@ -150,7 +150,7 @@ class NewMotionAvailabilityDetector(client: OkHttpClient, baseUrl: String? = nul
|
||||
"unspecified" -> "unknown"
|
||||
"unknown" -> "unknown"
|
||||
"saej1772" -> "unknown"
|
||||
else -> throw IllegalArgumentException("unrecognized type ${connector.connectorType}")
|
||||
else -> "unknown"
|
||||
}
|
||||
val status = when (statusStr) {
|
||||
"Unavailable" -> ChargepointStatus.FAULTED
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package net.vonforst.evmap.api.chargeprice
|
||||
|
||||
import android.content.Context
|
||||
import com.facebook.stetho.okhttp3.StethoInterceptor
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||
import moe.banana.jsonapi2.ArrayDocument
|
||||
import moe.banana.jsonapi2.JsonApiConverterFactory
|
||||
import moe.banana.jsonapi2.ResourceAdapterFactory
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface ChargepriceApi {
|
||||
@POST("charge_prices")
|
||||
suspend fun getChargePrices(
|
||||
@Body request: ChargepriceRequest,
|
||||
@Header("Accept-Language") language: String
|
||||
): ArrayDocument<ChargePrice>
|
||||
|
||||
@GET("vehicles")
|
||||
suspend fun getVehicles(): ArrayDocument<ChargepriceCar>
|
||||
|
||||
@GET("tariffs")
|
||||
suspend fun getTariffs(): ArrayDocument<ChargepriceTariff>
|
||||
|
||||
companion object {
|
||||
private val cacheSize = 1L * 1024 * 1024 // 1MB
|
||||
val supportedLanguages = setOf("de", "en", "fr", "nl")
|
||||
|
||||
private val jsonApiAdapterFactory = ResourceAdapterFactory.builder()
|
||||
.add(ChargepriceRequest::class.java)
|
||||
.add(ChargepriceTariff::class.java)
|
||||
.add(ChargepriceBrand::class.java)
|
||||
.add(ChargePrice::class.java)
|
||||
.add(ChargepriceCar::class.java)
|
||||
.build()
|
||||
val moshi = Moshi.Builder()
|
||||
.add(jsonApiAdapterFactory)
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
fun create(
|
||||
apikey: String,
|
||||
baseurl: String = "https://api.chargeprice.app/v1/",
|
||||
context: Context? = null
|
||||
): ChargepriceApi {
|
||||
val client = OkHttpClient.Builder().apply {
|
||||
addInterceptor { chain ->
|
||||
// add API key to every request
|
||||
val original = chain.request()
|
||||
val new = original.newBuilder()
|
||||
.header("API-Key", apikey)
|
||||
.header("Content-Type", "application/json")
|
||||
.build()
|
||||
chain.proceed(new)
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
addNetworkInterceptor(StethoInterceptor())
|
||||
}
|
||||
if (context != null) {
|
||||
cache(Cache(context.getCacheDir(), cacheSize))
|
||||
}
|
||||
}.build()
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseurl)
|
||||
.addConverterFactory(JsonApiConverterFactory.create(moshi))
|
||||
.client(client)
|
||||
.build()
|
||||
return retrofit.create(ChargepriceApi::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package net.vonforst.evmap.api.chargeprice
|
||||
|
||||
import android.content.Context
|
||||
import com.squareup.moshi.Json
|
||||
import moe.banana.jsonapi2.HasMany
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
|
||||
import moe.banana.jsonapi2.JsonApi
|
||||
import moe.banana.jsonapi2.Resource
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.Equatable
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.ui.currency
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
|
||||
|
||||
@JsonApi(type = "charge_price_request")
|
||||
class ChargepriceRequest : Resource() {
|
||||
@field:Json(name = "data_adapter")
|
||||
lateinit var dataAdapter: String
|
||||
lateinit var station: ChargepriceStation
|
||||
lateinit var options: ChargepriceOptions
|
||||
var tariffs: HasMany<ChargepriceTariff>? = null
|
||||
var vehicle: HasOne<ChargepriceCar>? = null
|
||||
}
|
||||
|
||||
data class ChargepriceStation(
|
||||
val longitude: Double,
|
||||
val latitude: Double,
|
||||
val country: String?,
|
||||
val network: String?,
|
||||
@Json(name = "charge_points") val chargePoints: List<ChargepriceChargepoint>
|
||||
) {
|
||||
companion object {
|
||||
fun fromGoingelectric(
|
||||
geCharger: ChargeLocation,
|
||||
compatibleConnectors: List<String>
|
||||
): ChargepriceStation {
|
||||
return ChargepriceStation(
|
||||
geCharger.coordinates.lng,
|
||||
geCharger.coordinates.lat,
|
||||
geCharger.address.country,
|
||||
geCharger.network,
|
||||
geCharger.chargepoints.filter {
|
||||
it.type in compatibleConnectors
|
||||
}.map {
|
||||
ChargepriceChargepoint(it.power, it.type)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ChargepriceChargepoint(
|
||||
val power: Double,
|
||||
val plug: String
|
||||
)
|
||||
|
||||
data class ChargepriceOptions(
|
||||
@Json(name = "max_monthly_fees") val maxMonthlyFees: Double? = null,
|
||||
val energy: Double? = null,
|
||||
val duration: Int? = null,
|
||||
@Json(name = "battery_range") val batteryRange: List<Double>? = null,
|
||||
@Json(name = "car_ac_phases") val carAcPhases: Int? = null,
|
||||
val currency: String? = null,
|
||||
@Json(name = "start_time") val startTime: Int? = null,
|
||||
@Json(name = "allow_unbalanced_load") val allowUnbalancedLoad: Boolean? = null,
|
||||
@Json(name = "provider_customer_tariffs") val providerCustomerTariffs: Boolean? = null
|
||||
)
|
||||
|
||||
@JsonApi(type = "tariff")
|
||||
class ChargepriceTariff() : Resource() {
|
||||
lateinit var provider: String
|
||||
lateinit var name: String
|
||||
@field:Json(name = "direct_payment")
|
||||
var directPayment: Boolean = false
|
||||
@field:Json(name = "provider_customer_tariff")
|
||||
var providerCustomerTariff: Boolean = false
|
||||
@field:Json(name = "supported_cuntries")
|
||||
lateinit var supportedCountries: Set<String>
|
||||
@field:Json(name = "charge_card_id")
|
||||
lateinit var chargeCardId: String // GE charge card ID
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as ChargepriceTariff
|
||||
|
||||
if (provider != other.provider) return false
|
||||
if (name != other.name) return false
|
||||
if (directPayment != other.directPayment) return false
|
||||
if (providerCustomerTariff != other.providerCustomerTariff) return false
|
||||
if (supportedCountries != other.supportedCountries) return false
|
||||
if (chargeCardId != other.chargeCardId) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + provider.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + directPayment.hashCode()
|
||||
result = 31 * result + providerCustomerTariff.hashCode()
|
||||
result = 31 * result + supportedCountries.hashCode()
|
||||
result = 31 * result + chargeCardId.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@JsonApi(type = "car")
|
||||
class ChargepriceCar : Resource() {
|
||||
lateinit var name: String
|
||||
lateinit var brand: String
|
||||
|
||||
@field:Json(name = "dc_charge_ports")
|
||||
lateinit var dcChargePorts: List<String>
|
||||
lateinit var manufacturer: HasOne<ChargepriceBrand>
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as ChargepriceCar
|
||||
|
||||
if (name != other.name) return false
|
||||
if (brand != other.brand) return false
|
||||
if (dcChargePorts != other.dcChargePorts) return false
|
||||
if (manufacturer != other.manufacturer) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + brand.hashCode()
|
||||
result = 31 * result + dcChargePorts.hashCode()
|
||||
result = 31 * result + manufacturer.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@JsonApi(type = "brand")
|
||||
class ChargepriceBrand : Resource()
|
||||
|
||||
@JsonApi(type = "charge_price")
|
||||
class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
lateinit var provider: String
|
||||
|
||||
@field:Json(name = "tariff_name")
|
||||
lateinit var tariffName: String
|
||||
lateinit var url: String
|
||||
|
||||
@field:Json(name = "monthly_min_sales")
|
||||
var monthlyMinSales: Double = 0.0
|
||||
|
||||
@field:Json(name = "total_monthly_fee")
|
||||
var totalMonthlyFee: Double = 0.0
|
||||
|
||||
@field:Json(name = "flat_rate")
|
||||
var flatRate: Boolean = false
|
||||
|
||||
@field:Json(name = "direct_payment")
|
||||
var directPayment: Boolean = false
|
||||
|
||||
@field:Json(name = "provider_customer_tariff")
|
||||
var providerCustomerTariff: Boolean = false
|
||||
lateinit var currency: String
|
||||
|
||||
@field:Json(name = "start_time")
|
||||
var startTime: Int = 0
|
||||
lateinit var tags: List<ChargepriceTag>
|
||||
|
||||
@field:Json(name = "charge_point_prices")
|
||||
lateinit var chargepointPrices: List<ChargepointPrice>
|
||||
|
||||
var tariff: HasOne<ChargepriceTariff>? = null
|
||||
|
||||
|
||||
fun formatMonthlyFees(ctx: Context): String {
|
||||
return listOfNotNull(
|
||||
if (totalMonthlyFee > 0) {
|
||||
ctx.getString(R.string.chargeprice_base_fee, totalMonthlyFee, currency(currency))
|
||||
} else null,
|
||||
if (monthlyMinSales > 0) {
|
||||
ctx.getString(R.string.chargeprice_min_spend, monthlyMinSales, currency(currency))
|
||||
} else null
|
||||
).joinToString(", ")
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as ChargePrice
|
||||
|
||||
if (provider != other.provider) return false
|
||||
if (tariffName != other.tariffName) return false
|
||||
if (url != other.url) return false
|
||||
if (monthlyMinSales != other.monthlyMinSales) return false
|
||||
if (totalMonthlyFee != other.totalMonthlyFee) return false
|
||||
if (flatRate != other.flatRate) return false
|
||||
if (directPayment != other.directPayment) return false
|
||||
if (providerCustomerTariff != other.providerCustomerTariff) return false
|
||||
if (currency != other.currency) return false
|
||||
if (startTime != other.startTime) return false
|
||||
if (tags != other.tags) return false
|
||||
if (chargepointPrices != other.chargepointPrices) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + provider.hashCode()
|
||||
result = 31 * result + tariffName.hashCode()
|
||||
result = 31 * result + url.hashCode()
|
||||
result = 31 * result + monthlyMinSales.hashCode()
|
||||
result = 31 * result + totalMonthlyFee.hashCode()
|
||||
result = 31 * result + flatRate.hashCode()
|
||||
result = 31 * result + directPayment.hashCode()
|
||||
result = 31 * result + providerCustomerTariff.hashCode()
|
||||
result = 31 * result + currency.hashCode()
|
||||
result = 31 * result + startTime
|
||||
result = 31 * result + tags.hashCode()
|
||||
result = 31 * result + chargepointPrices.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
public override fun clone(): ChargePrice {
|
||||
return ChargePrice().apply {
|
||||
chargepointPrices = this@ChargePrice.chargepointPrices
|
||||
currency = this@ChargePrice.currency
|
||||
directPayment = this@ChargePrice.directPayment
|
||||
flatRate = this@ChargePrice.flatRate
|
||||
monthlyMinSales = this@ChargePrice.monthlyMinSales
|
||||
provider = this@ChargePrice.provider
|
||||
providerCustomerTariff = this@ChargePrice.providerCustomerTariff
|
||||
startTime = this@ChargePrice.startTime
|
||||
tags = this@ChargePrice.tags
|
||||
tariffName = this@ChargePrice.tariffName
|
||||
totalMonthlyFee = this@ChargePrice.totalMonthlyFee
|
||||
url = this@ChargePrice.url
|
||||
tariff = this@ChargePrice.tariff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ChargepointPrice(
|
||||
val power: Double,
|
||||
val plug: String,
|
||||
val price: Double,
|
||||
@Json(name = "price_distribution") val priceDistribution: PriceDistribution,
|
||||
@Json(name = "blocking_fee_start") val blockingFeeStart: Int?,
|
||||
@Json(name = "no_price_reason") var noPriceReason: String?
|
||||
) {
|
||||
fun formatDistribution(ctx: Context): String {
|
||||
fun percent(value: Double): String {
|
||||
return ctx.getString(R.string.percent_format, value * 100) + "\u00a0"
|
||||
}
|
||||
|
||||
fun time(value: Int): String {
|
||||
val h = floor(value.toDouble() / 60).toInt();
|
||||
val min = ceil(value.toDouble() % 60).toInt();
|
||||
if (h == 0 && min > 0) return "${min}min";
|
||||
// be slightly sloppy (3:01 is shown as 3h) to save space
|
||||
else if (h > 0 && (min == 0 || min == 1)) return "${h}h";
|
||||
else return "%d:%02dh".format(h, min);
|
||||
}
|
||||
|
||||
// based on https://github.com/chargeprice/chargeprice-client/blob/d420bb2f216d9ad91a210a36dd0859a368a8229a/src/views/priceList.js
|
||||
with(priceDistribution) {
|
||||
return listOfNotNull(
|
||||
if (session != null && session > 0.0) {
|
||||
(if (session < 1) percent(session) else "") + ctx.getString(R.string.chargeprice_session_fee)
|
||||
} else null,
|
||||
if (kwh != null && kwh > 0.0 && !isOnlyKwh) {
|
||||
(if (kwh < 1) percent(kwh) else "") + ctx.getString(R.string.chargeprice_per_kwh)
|
||||
} else null,
|
||||
if (minute != null && minute > 0.0) {
|
||||
(if (minute < 1) percent(minute) else "") + ctx.getString(R.string.chargeprice_per_minute) +
|
||||
if (blockingFeeStart != null) {
|
||||
" (${
|
||||
ctx.getString(
|
||||
R.string.chargeprice_blocking_fee,
|
||||
time(blockingFeeStart)
|
||||
)
|
||||
})"
|
||||
} else ""
|
||||
} else null,
|
||||
if ((minute == null || minute == 0.0) && blockingFeeStart != null) {
|
||||
ctx.getString(R.string.chargeprice_blocking_fee, time(blockingFeeStart))
|
||||
} else null
|
||||
).joinToString(" +\u00a0")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class PriceDistribution(val kwh: Double?, val session: Double?, val minute: Double?) {
|
||||
val isOnlyKwh =
|
||||
kwh != null && kwh > 0 && (session == null || session == 0.0) && (minute == null || minute == 0.0)
|
||||
}
|
||||
|
||||
data class ChargepriceTag(val kind: String, val text: String, val url: String?) : Equatable
|
||||
|
||||
data class ChargepriceMeta(
|
||||
@Json(name = "charge_points") val chargePoints: List<ChargepriceChargepointMeta>
|
||||
)
|
||||
|
||||
data class ChargepriceChargepointMeta(
|
||||
val power: Double,
|
||||
val plug: String,
|
||||
val energy: Double,
|
||||
val duration: Double
|
||||
)
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.vonforst.evmap.api.goingelectric
|
||||
|
||||
import android.util.Log
|
||||
import com.squareup.moshi.*
|
||||
import java.lang.reflect.Type
|
||||
import java.time.Instant
|
||||
@@ -130,11 +131,18 @@ internal class HoursAdapter {
|
||||
return Hours(null, null)
|
||||
} else {
|
||||
val match = regex.find(str)
|
||||
?: throw IllegalArgumentException("$str does not match hours format")
|
||||
return Hours(
|
||||
LocalTime.parse(match.groupValues[1]),
|
||||
LocalTime.parse(match.groupValues[2])
|
||||
)
|
||||
if (match != null) {
|
||||
return Hours(
|
||||
LocalTime.parse(match.groupValues[1]),
|
||||
LocalTime.parse(match.groupValues[2])
|
||||
)
|
||||
} else {
|
||||
// I cannot reproduce this case, but it seems to occur once in a while
|
||||
Log.e("GoingElectricApi", "invalid hours value: " + str)
|
||||
return Hours(
|
||||
LocalTime.MIN, LocalTime.MIN
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.squareup.moshi.Moshi
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Call
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
@@ -16,7 +15,7 @@ import retrofit2.http.Query
|
||||
interface GoingElectricApi {
|
||||
@GET("chargepoints/")
|
||||
suspend fun getChargepoints(
|
||||
@Query("sw_lat") swlat: Double, @Query("sw_lng") sw_lng: Double,
|
||||
@Query("sw_lat") sw_lat: Double, @Query("sw_lng") sw_lng: Double,
|
||||
@Query("ne_lat") ne_lat: Double, @Query("ne_lng") ne_lng: Double,
|
||||
@Query("zoom") zoom: Float,
|
||||
@Query("clustering") clustering: Boolean = false,
|
||||
@@ -35,7 +34,28 @@ interface GoingElectricApi {
|
||||
): Response<ChargepointList>
|
||||
|
||||
@GET("chargepoints/")
|
||||
fun getChargepointDetail(@Query("ge_id") id: Long): Call<ChargepointList>
|
||||
suspend fun getChargepointsRadius(
|
||||
@Query("lat") lat: Double, @Query("lng") lng: Double,
|
||||
@Query("radius") radius: Int,
|
||||
@Query("zoom") zoom: Float,
|
||||
@Query("orderby") orderby: String = "distance",
|
||||
@Query("clustering") clustering: Boolean = false,
|
||||
@Query("cluster_distance") clusterDistance: Int? = null,
|
||||
@Query("freecharging") freecharging: Boolean = false,
|
||||
@Query("freeparking") freeparking: Boolean = false,
|
||||
@Query("min_power") minPower: Int = 0,
|
||||
@Query("plugs") plugs: String? = null,
|
||||
@Query("chargecards") chargecards: String? = null,
|
||||
@Query("networks") networks: String? = null,
|
||||
@Query("categories") categories: String? = null,
|
||||
@Query("startkey") startkey: Int? = null,
|
||||
@Query("open_twentyfourseven") open247: Boolean = false,
|
||||
@Query("barrierfree") barrierfree: Boolean = false,
|
||||
@Query("exclude_faults") excludeFaults: Boolean = false
|
||||
): Response<ChargepointList>
|
||||
|
||||
@GET("chargepoints/")
|
||||
suspend fun getChargepointDetail(@Query("ge_id") id: Long): Response<ChargepointList>
|
||||
|
||||
@GET("chargepoints/pluglist/")
|
||||
suspend fun getPlugs(): Response<StringList>
|
||||
@@ -49,6 +69,13 @@ interface GoingElectricApi {
|
||||
companion object {
|
||||
private val cacheSize = 10L * 1024 * 1024 // 10MB
|
||||
|
||||
val moshi = Moshi.Builder()
|
||||
.add(ChargepointListItemJsonAdapterFactory())
|
||||
.add(JsonObjectOrFalseAdapter.Factory())
|
||||
.add(HoursAdapter())
|
||||
.add(InstantAdapter())
|
||||
.build()
|
||||
|
||||
fun create(
|
||||
apikey: String,
|
||||
baseurl: String = "https://api.goingelectric.de",
|
||||
@@ -70,13 +97,6 @@ interface GoingElectricApi {
|
||||
}
|
||||
}.build()
|
||||
|
||||
val moshi = Moshi.Builder()
|
||||
.add(ChargepointListItemJsonAdapterFactory())
|
||||
.add(JsonObjectOrFalseAdapter.Factory())
|
||||
.add(HoursAdapter())
|
||||
.add(InstantAdapter())
|
||||
.build()
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseurl)
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
|
||||
@@ -54,6 +54,7 @@ data class ChargeLocation(
|
||||
val url: String,
|
||||
@Embedded(prefix = "fault_report_") @JsonObjectOrFalse @Json(name = "fault_report") val faultReport: FaultReport?,
|
||||
val verified: Boolean,
|
||||
@Json(name = "barrierfree") val barrierFree: Boolean?,
|
||||
// only shown in details:
|
||||
@JsonObjectOrFalse val operator: String?,
|
||||
@JsonObjectOrFalse @Json(name = "general_information") val generalInformation: String?,
|
||||
@@ -80,6 +81,21 @@ data class ChargeLocation(
|
||||
.map { it.power }.maxOrNull() ?: 0.0
|
||||
}
|
||||
|
||||
fun isMulti(filteredConnectors: Set<String>? = null): Boolean {
|
||||
var chargepoints = chargepointsMerged
|
||||
.filter { filteredConnectors?.contains(it.type) ?: true }
|
||||
if (maxPower(filteredConnectors) >= 43) {
|
||||
// fast charger -> only count fast chargers
|
||||
chargepoints = chargepoints.filter { it.power >= 43 }
|
||||
}
|
||||
val connectors = chargepoints.map { it.type }.distinct().toSet()
|
||||
|
||||
// check if there is more than one plug for any connector type
|
||||
val chargepointsPerConnector =
|
||||
connectors.map { conn -> chargepoints.filter { it.type == conn }.sumBy { it.count } }
|
||||
return chargepointsPerConnector.any { it > 1 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges chargepoints if they have the same plug and power
|
||||
*
|
||||
@@ -114,14 +130,16 @@ data class Cost(
|
||||
@JsonObjectOrFalse @Json(name = "description_short") val descriptionShort: String?,
|
||||
@JsonObjectOrFalse @Json(name = "description_long") val descriptionLong: String?
|
||||
) {
|
||||
fun getStatusText(ctx: Context): CharSequence {
|
||||
return HtmlCompat.fromHtml(
|
||||
ctx.getString(
|
||||
R.string.cost_detail,
|
||||
if (freecharging) ctx.getString(R.string.free) else ctx.getString(R.string.paid),
|
||||
if (freeparking) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
), 0
|
||||
)
|
||||
fun getStatusText(ctx: Context, emoji: Boolean = false): CharSequence {
|
||||
val charging =
|
||||
if (freecharging) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
val parking =
|
||||
if (freeparking) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
return if (emoji) {
|
||||
"⚡ $charging · \uD83C\uDD7F️ $parking"
|
||||
} else {
|
||||
HtmlCompat.fromHtml(ctx.getString(R.string.cost_detail, charging, parking), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.ChargepriceAdapter
|
||||
import net.vonforst.evmap.adapter.CheckableConnectorAdapter
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.databinding.FragmentChargepriceBinding
|
||||
import net.vonforst.evmap.viewmodel.ChargepriceViewModel
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
import java.text.NumberFormat
|
||||
|
||||
class ChargepriceFragment : DialogFragment() {
|
||||
private lateinit var binding: FragmentChargepriceBinding
|
||||
private var connectionErrorSnackbar: Snackbar? = null
|
||||
|
||||
private val vm: ChargepriceViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
ChargepriceViewModel(
|
||||
requireActivity().application,
|
||||
getString(R.string.chargeprice_key)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
dialog?.window?.attributes?.windowAnimations = R.style.ChargepriceDialogAnimation
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
binding = DataBindingUtil.inflate(
|
||||
inflater,
|
||||
R.layout.fragment_chargeprice, container, false
|
||||
)
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
|
||||
binding.toolbar.inflateMenu(R.menu.chargeprice)
|
||||
binding.toolbar.setTitle(R.string.chargeprice_title)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
// dialog with 95% screen height
|
||||
dialog?.window?.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
(resources.displayMetrics.heightPixels * 0.95).toInt()
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val toolbar = view.findViewById(R.id.toolbar) as Toolbar
|
||||
|
||||
val navController = findNavController()
|
||||
toolbar.setupWithNavController(
|
||||
navController,
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
|
||||
val jsonAdapter = GoingElectricApi.moshi.adapter(ChargeLocation::class.java)
|
||||
val charger = jsonAdapter.fromJson(requireArguments().getString(ARG_CHARGER)!!)!!
|
||||
vm.charger.value = charger
|
||||
if (vm.chargepoint.value == null) {
|
||||
vm.chargepoint.value = charger.chargepointsMerged.get(0)
|
||||
}
|
||||
|
||||
val chargepriceAdapter = ChargepriceAdapter().apply {
|
||||
onClickListener = {
|
||||
(requireActivity() as MapsActivity).openUrl(it.url)
|
||||
}
|
||||
}
|
||||
binding.chargePricesList.apply {
|
||||
adapter = chargepriceAdapter
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
addItemDecoration(
|
||||
DividerItemDecoration(
|
||||
context, LinearLayoutManager.VERTICAL
|
||||
)
|
||||
)
|
||||
}
|
||||
vm.chargepriceMetaForChargepoint.observe(viewLifecycleOwner) {
|
||||
chargepriceAdapter.meta = it?.data
|
||||
}
|
||||
vm.myTariffs.observe(viewLifecycleOwner) {
|
||||
chargepriceAdapter.myTariffs = it
|
||||
}
|
||||
vm.myTariffsAll.observe(viewLifecycleOwner) {
|
||||
chargepriceAdapter.myTariffsAll = it
|
||||
}
|
||||
|
||||
val connectorsAdapter = CheckableConnectorAdapter()
|
||||
|
||||
val observer: Observer<Chargepoint> = Observer {
|
||||
connectorsAdapter.setCheckedItem(it)
|
||||
}
|
||||
vm.chargepoint.observe(viewLifecycleOwner, observer)
|
||||
connectorsAdapter.onCheckedItemChangedListener = {
|
||||
vm.chargepoint.removeObserver(observer)
|
||||
vm.chargepoint.value = it
|
||||
vm.chargepoint.observe(viewLifecycleOwner, observer)
|
||||
}
|
||||
|
||||
vm.vehicleCompatibleConnectors.observe(viewLifecycleOwner) {
|
||||
connectorsAdapter.enabledConnectors = it
|
||||
}
|
||||
|
||||
binding.connectorsList.apply {
|
||||
adapter = connectorsAdapter
|
||||
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
||||
}
|
||||
|
||||
binding.imgChargepriceLogo.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl("https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=going_electric")
|
||||
}
|
||||
|
||||
binding.btnSettings.setOnClickListener {
|
||||
navController.navigate(R.id.action_chargeprice_to_settingsFragment)
|
||||
}
|
||||
|
||||
binding.batteryRange.setLabelFormatter { value: Float ->
|
||||
val fmt = NumberFormat.getNumberInstance()
|
||||
fmt.maximumFractionDigits = 0
|
||||
fmt.format(value.toDouble()) + "%"
|
||||
}
|
||||
binding.batteryRange.setOnTouchListener { _: View, motionEvent: MotionEvent ->
|
||||
when (motionEvent.actionMasked) {
|
||||
MotionEvent.ACTION_DOWN -> vm.batteryRangeSliderDragging.value = true
|
||||
MotionEvent.ACTION_UP -> vm.batteryRangeSliderDragging.value = false
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
binding.toolbar.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_close -> {
|
||||
dismiss()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
vm.chargePricesForChargepoint.observe(viewLifecycleOwner) { res ->
|
||||
when (res?.status) {
|
||||
Status.ERROR -> {
|
||||
if (vm.vehicle.value == null) return@observe
|
||||
connectionErrorSnackbar?.dismiss()
|
||||
connectionErrorSnackbar = Snackbar
|
||||
.make(
|
||||
view,
|
||||
R.string.chargeprice_connection_error,
|
||||
Snackbar.LENGTH_INDEFINITE
|
||||
)
|
||||
.setAction(R.string.retry) {
|
||||
connectionErrorSnackbar?.dismiss()
|
||||
vm.loadPrices()
|
||||
}
|
||||
connectionErrorSnackbar!!.show()
|
||||
}
|
||||
Status.SUCCESS, null -> {
|
||||
connectionErrorSnackbar?.dismiss()
|
||||
}
|
||||
Status.LOADING -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ARG_CHARGER = "charger"
|
||||
|
||||
fun showCharger(charger: ChargeLocation): Bundle {
|
||||
return Bundle().apply {
|
||||
putString(
|
||||
ARG_CHARGER,
|
||||
GoingElectricApi.moshi.adapter(ChargeLocation::class.java).toJson(charger)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
|
||||
|
||||
class FilterProfilesFragment : Fragment() {
|
||||
private lateinit var touchHelper: ItemTouchHelper
|
||||
private lateinit var adapter: FilterProfilesAdapter
|
||||
private lateinit var binding: FragmentFilterProfilesBinding
|
||||
private val vm: FilterProfilesViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
@@ -40,6 +42,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
}
|
||||
})
|
||||
private var deleteSnackbar: Snackbar? = null
|
||||
private var toDelete: FilterProfile? = null
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
@@ -64,7 +67,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
)
|
||||
|
||||
|
||||
val touchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
|
||||
touchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(
|
||||
ItemTouchHelper.UP or ItemTouchHelper.DOWN,
|
||||
ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT
|
||||
) {
|
||||
@@ -173,7 +176,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
}
|
||||
})
|
||||
|
||||
val adapter = FilterProfilesAdapter(touchHelper, onDelete = { fp ->
|
||||
adapter = FilterProfilesAdapter(touchHelper, onDelete = { fp ->
|
||||
delete(fp)
|
||||
}, onRename = { fp ->
|
||||
showEditTextDialog(requireContext()) { dialog, input ->
|
||||
@@ -183,7 +186,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
.setPositiveButton(R.string.ok) { di, button ->
|
||||
lifecycleScope.launch {
|
||||
vm.insert(fp.copy(name = input.text.toString()))
|
||||
vm.update(fp.copy(name = input.text.toString()))
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { di, button ->
|
||||
@@ -192,7 +195,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
}
|
||||
})
|
||||
binding.filterProfilesList.apply {
|
||||
this.adapter = adapter
|
||||
this.adapter = this@FilterProfilesFragment.adapter
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
addItemDecoration(
|
||||
@@ -210,19 +213,43 @@ class FilterProfilesFragment : Fragment() {
|
||||
}
|
||||
|
||||
fun delete(fp: FilterProfile) {
|
||||
vm.delete(fp.id)
|
||||
|
||||
val position = vm.filterProfiles.value?.indexOf(fp) ?: return
|
||||
// if there is already a profile to delete, delete it now
|
||||
actuallyDelete()
|
||||
deleteSnackbar?.dismiss()
|
||||
|
||||
toDelete = fp
|
||||
|
||||
view?.let {
|
||||
val snackbar = Snackbar.make(
|
||||
it,
|
||||
getString(R.string.deleted_filterprofile, fp.name),
|
||||
Snackbar.LENGTH_LONG
|
||||
).setAction(R.string.undo) {
|
||||
vm.insert(fp.copy(id = 0))
|
||||
}
|
||||
toDelete = null
|
||||
adapter.notifyItemChanged(position)
|
||||
}.addCallback(object : Snackbar.Callback() {
|
||||
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
|
||||
// if undo was not clicked, actually delete
|
||||
if (event == DISMISS_EVENT_TIMEOUT || event == DISMISS_EVENT_SWIPE) {
|
||||
actuallyDelete()
|
||||
}
|
||||
}
|
||||
})
|
||||
deleteSnackbar = snackbar
|
||||
snackbar.show()
|
||||
} ?: run {
|
||||
actuallyDelete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun actuallyDelete() {
|
||||
toDelete?.let { vm.delete(it.id) }
|
||||
toDelete = null
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
actuallyDelete()
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,13 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.location.Geocoder
|
||||
import android.location.Location
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.view.*
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -50,6 +53,8 @@ import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
import com.mahc.custombottomsheetbehavior.MergedAppBarLayoutBehavior
|
||||
import com.mapzen.android.lost.api.LocationListener
|
||||
import com.mapzen.android.lost.api.LocationRequest
|
||||
import com.mapzen.android.lost.api.LocationServices
|
||||
import com.mapzen.android.lost.api.LostApiClient
|
||||
import io.michaelrocks.bimap.HashBiMap
|
||||
@@ -72,15 +77,18 @@ import net.vonforst.evmap.ui.ChargerIconGenerator
|
||||
import net.vonforst.evmap.ui.ClusterIconGenerator
|
||||
import net.vonforst.evmap.ui.MarkerAnimator
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import net.vonforst.evmap.viewmodel.*
|
||||
|
||||
|
||||
const val REQUEST_AUTOCOMPLETE = 2
|
||||
const val ARG_CHARGER_ID = "chargerId"
|
||||
const val ARG_LAT = "lat"
|
||||
const val ARG_LON = "lon"
|
||||
const val ARG_LOCATION_NAME = "locationName"
|
||||
|
||||
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback,
|
||||
LostApiClient.ConnectionCallbacks {
|
||||
LostApiClient.ConnectionCallbacks, LocationListener {
|
||||
private lateinit var binding: FragmentMapBinding
|
||||
private val vm: MapViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
@@ -94,6 +102,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private var mapFragment: MapFragment? = null
|
||||
private var map: AnyMap? = null
|
||||
private lateinit var locationClient: LostApiClient
|
||||
private var requestingLocationUpdates = false
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehaviorGoogleMapsLike<View>
|
||||
private lateinit var detailAppBarBehavior: MergedAppBarLayoutBehavior
|
||||
private var markers: MutableBiMap<Marker, ChargeLocation> = HashBiMap()
|
||||
@@ -223,12 +232,19 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
|
||||
if (!PreferenceDataSource(requireContext()).welcomeDialogShown) {
|
||||
val prefs = PreferenceDataSource(requireContext())
|
||||
if (!prefs.welcomeDialogShown) {
|
||||
try {
|
||||
navController.navigate(R.id.action_map_to_welcome)
|
||||
} catch (ignored: IllegalArgumentException) {
|
||||
// when there is already another navigation going on
|
||||
}
|
||||
} else if (!prefs.update060AndroidAutoDialogShown) {
|
||||
try {
|
||||
navController.navigate(R.id.action_map_to_update_060_androidauto)
|
||||
} catch (ignored: IllegalArgumentException) {
|
||||
// when there is already another navigation going on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +253,13 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val hostActivity = activity as? MapsActivity ?: return
|
||||
hostActivity.fragmentCallback = this
|
||||
vm.reloadPrefs()
|
||||
if (requestingLocationUpdates && ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED && locationClient.isConnected
|
||||
) {
|
||||
requestLocationUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupClickListeners() {
|
||||
@@ -273,8 +296,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
binding.detailView.btnChargeprice.setOnClickListener {
|
||||
val charger = vm.charger.value?.data ?: return@setOnClickListener
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
"https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=going_electric")
|
||||
findNavController().navigate(
|
||||
R.id.action_map_to_chargepriceFragment,
|
||||
ChargepriceFragment.showCharger(charger)
|
||||
)
|
||||
}
|
||||
binding.detailView.topPart.setOnClickListener {
|
||||
bottomSheetBehavior.state = BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT
|
||||
@@ -302,6 +327,11 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val charger = vm.charger.value?.data
|
||||
if (charger != null) {
|
||||
(activity as? MapsActivity)?.openUrl("https:${charger.url}edit/")
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
R.string.edit_on_goingelectric_info,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -357,7 +387,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
vm.bottomSheetState.value = newState
|
||||
updateBackPressedCallback()
|
||||
|
||||
if (vm.layersMenuOpen.value!! && newState !in listOf(BottomSheetBehaviorGoogleMapsLike.STATE_SETTLING, BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN, BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED)) {
|
||||
if (vm.layersMenuOpen.value!! && newState !in listOf(
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_SETTLING,
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN,
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
)
|
||||
) {
|
||||
closeLayersMenu()
|
||||
}
|
||||
}
|
||||
@@ -411,6 +446,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
searchResultMarker = null
|
||||
|
||||
if (place != null) {
|
||||
// disable location following when search result is shown
|
||||
vm.myLocationEnabled.value = false
|
||||
if (place.viewport != null) {
|
||||
map.animateCamera(map.cameraUpdateFactory.newLatLngBounds(place.viewport, 0))
|
||||
} else {
|
||||
@@ -460,7 +497,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
getMarkerTint(c, vm.filteredConnectors.value),
|
||||
highlight = false,
|
||||
fault = c.faultReport != null,
|
||||
multi = getMarkerMulti(c, vm.filteredConnectors.value)
|
||||
multi = c.isMulti(vm.filteredConnectors.value)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -474,7 +511,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
getMarkerTint(charger, vm.filteredConnectors.value),
|
||||
highlight = true,
|
||||
fault = charger.faultReport != null,
|
||||
multi = getMarkerMulti(charger, vm.filteredConnectors.value)
|
||||
multi = charger.isMulti(vm.filteredConnectors.value)
|
||||
)
|
||||
)
|
||||
animator.animateMarkerBounce(marker)
|
||||
@@ -487,28 +524,13 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
getMarkerTint(c, vm.filteredConnectors.value),
|
||||
highlight = false,
|
||||
fault = c.faultReport != null,
|
||||
multi = getMarkerMulti(c, vm.filteredConnectors.value)
|
||||
multi = c.isMulti(vm.filteredConnectors.value)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMarkerMulti(charger: ChargeLocation, filteredConnectors: Set<String>?): Boolean {
|
||||
var chargepoints = charger.chargepointsMerged
|
||||
.filter { filteredConnectors?.contains(it.type) ?: true }
|
||||
if (charger.maxPower(filteredConnectors) >= 43) {
|
||||
// fast charger -> only count fast chargers
|
||||
chargepoints = chargepoints.filter { it.power >= 43 }
|
||||
}
|
||||
val connectors = chargepoints.map { it.type }.distinct().toSet()
|
||||
|
||||
// check if there is more than one plug for any connector type
|
||||
val chargepointsPerConnector =
|
||||
connectors.map { conn -> chargepoints.filter { it.type == conn }.sumBy { it.count } }
|
||||
return chargepointsPerConnector.any { it > 1 }
|
||||
}
|
||||
|
||||
private fun updateFavoriteToggle() {
|
||||
val favs = vm.favorites.value ?: return
|
||||
val charger = vm.chargerSparse.value ?: return
|
||||
@@ -677,6 +699,19 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
map.setOnCameraMoveListener {
|
||||
binding.scaleView.update(map.cameraPosition.zoom, map.cameraPosition.target.latitude)
|
||||
}
|
||||
map.setOnCameraMoveStartedListener { reason ->
|
||||
if (reason == AnyMap.OnCameraMoveStartedListener.REASON_GESTURE) {
|
||||
if (vm.myLocationEnabled.value == true) {
|
||||
// disable location following when manually scrolling the map
|
||||
vm.myLocationEnabled.value = false
|
||||
removeLocationUpdates()
|
||||
}
|
||||
if (vm.layersMenuOpen.value == true) {
|
||||
// close layers menu if open
|
||||
closeLayersMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
map.setOnMarkerClickListener { marker ->
|
||||
when (marker) {
|
||||
in markers -> {
|
||||
@@ -702,6 +737,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
backPressedCallback.handleOnBackPressed()
|
||||
}
|
||||
}
|
||||
map.setMapType(vm.mapType.value)
|
||||
map.setTrafficEnabled(vm.mapTrafficEnabled.value ?: false)
|
||||
|
||||
// set padding so that compass is not obstructed by toolbar
|
||||
map.setPadding(0, binding.toolbarContainer.height, 0, 0)
|
||||
@@ -715,6 +752,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val position = vm.mapPosition.value
|
||||
val lat = arguments?.optDouble(ARG_LAT)
|
||||
val lon = arguments?.optDouble(ARG_LON)
|
||||
val chargerId = arguments?.optLong(ARG_CHARGER_ID)
|
||||
val locationName = arguments?.getString(ARG_LOCATION_NAME)
|
||||
|
||||
var positionSet = false
|
||||
|
||||
if (position != null) {
|
||||
@@ -722,28 +762,62 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
map.cameraUpdateFactory.newLatLngZoom(position.bounds.center, position.zoom)
|
||||
map.moveCamera(cameraUpdate)
|
||||
positionSet = true
|
||||
} else if (lat != null && lon != null) {
|
||||
// show given position
|
||||
val cameraUpdate = map.cameraUpdateFactory.newLatLngZoom(LatLng(lat, lon), 16f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
|
||||
// show charger detail after chargers were loaded
|
||||
val chargerId = arguments?.optLong(ARG_CHARGER_ID)
|
||||
vm.chargepoints.observe(
|
||||
} else if (chargerId != null && (lat == null || lon == null)) {
|
||||
// show given charger ID
|
||||
vm.loadChargerById(chargerId)
|
||||
vm.chargerSparse.observe(
|
||||
viewLifecycleOwner,
|
||||
object : Observer<Resource<List<ChargepointListItem>>> {
|
||||
override fun onChanged(res: Resource<List<ChargepointListItem>>) {
|
||||
if (res.data == null) return
|
||||
for (item in res.data) {
|
||||
if (item is ChargeLocation && item.id == chargerId) {
|
||||
vm.chargerSparse.value = item
|
||||
vm.chargepoints.removeObserver(this)
|
||||
}
|
||||
object : Observer<ChargeLocation> {
|
||||
override fun onChanged(item: ChargeLocation?) {
|
||||
if (item?.id == chargerId) {
|
||||
val cameraUpdate = map.cameraUpdateFactory.newLatLngZoom(
|
||||
LatLng(item.coordinates.lat, item.coordinates.lng), 16f
|
||||
)
|
||||
map.moveCamera(cameraUpdate)
|
||||
vm.chargerSparse.removeObserver(this)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
positionSet = true
|
||||
} else if (lat != null && lon != null) {
|
||||
// show given position
|
||||
val cameraUpdate = map.cameraUpdateFactory.newLatLngZoom(LatLng(lat, lon), 16f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
|
||||
if (chargerId != null) {
|
||||
// show charger detail after chargers were loaded
|
||||
vm.chargepoints.observe(
|
||||
viewLifecycleOwner,
|
||||
object : Observer<Resource<List<ChargepointListItem>>> {
|
||||
override fun onChanged(res: Resource<List<ChargepointListItem>>) {
|
||||
if (res.data == null) return
|
||||
for (item in res.data) {
|
||||
if (item is ChargeLocation && item.id == chargerId) {
|
||||
vm.chargerSparse.value = item
|
||||
vm.chargepoints.removeObserver(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// mark location as search result
|
||||
vm.searchResult.value = PlaceWithBounds(LatLng(lat, lon), null)
|
||||
}
|
||||
|
||||
positionSet = true
|
||||
} else if (locationName != null) {
|
||||
lifecycleScope.launch {
|
||||
val address = withContext(Dispatchers.IO) {
|
||||
Geocoder(requireContext()).getFromLocationName(locationName, 1).getOrNull(0)
|
||||
}
|
||||
address?.let {
|
||||
val latLng = LatLng(it.latitude, it.longitude)
|
||||
val cameraUpdate = map.cameraUpdateFactory.newLatLngZoom(latLng, 16f)
|
||||
map.moveCamera(cameraUpdate)
|
||||
vm.searchResult.value = PlaceWithBounds(latLng, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
@@ -769,15 +843,18 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private fun enableLocation(moveTo: Boolean, animate: Boolean) {
|
||||
val map = this.map ?: return
|
||||
map.setMyLocationEnabled(true)
|
||||
vm.myLocationEnabled.value = true
|
||||
map.uiSettings.setMyLocationButtonEnabled(false)
|
||||
if (moveTo && locationClient.isConnected) {
|
||||
moveToCurrentLocation(map, animate)
|
||||
if (moveTo) {
|
||||
vm.myLocationEnabled.value = true
|
||||
if (locationClient.isConnected) {
|
||||
moveToLastLocation(map, animate)
|
||||
requestLocationUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(ACCESS_FINE_LOCATION)
|
||||
private fun moveToCurrentLocation(map: AnyMap, animate: Boolean) {
|
||||
private fun moveToLastLocation(map: AnyMap, animate: Boolean) {
|
||||
val location = LocationServices.FusedLocationApi.getLastLocation(locationClient)
|
||||
if (location != null) {
|
||||
val latLng = LatLng(location.latitude, location.longitude)
|
||||
@@ -808,7 +885,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
getMarkerTint(charger, vm.filteredConnectors.value),
|
||||
highlight = charger == vm.chargerSparse.value,
|
||||
fault = charger.faultReport != null,
|
||||
multi = getMarkerMulti(charger, vm.filteredConnectors.value)
|
||||
multi = charger.isMulti(vm.filteredConnectors.value)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -825,7 +902,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val tint = getMarkerTint(charger, vm.filteredConnectors.value)
|
||||
val highlight = charger == vm.chargerSparse.value
|
||||
val fault = charger.faultReport != null
|
||||
val multi = getMarkerMulti(charger, vm.filteredConnectors.value)
|
||||
val multi = charger.isMulti(vm.filteredConnectors.value)
|
||||
animator.animateMarkerDisappear(marker, tint, highlight, fault, multi)
|
||||
} else {
|
||||
animator.deleteMarker(marker)
|
||||
@@ -840,7 +917,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val tint = getMarkerTint(charger, vm.filteredConnectors.value)
|
||||
val highlight = charger == vm.chargerSparse.value
|
||||
val fault = charger.faultReport != null
|
||||
val multi = getMarkerMulti(charger, vm.filteredConnectors.value)
|
||||
val multi = charger.isMulti(vm.filteredConnectors.value)
|
||||
val marker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
|
||||
@@ -1054,6 +1131,33 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
putDouble(ARG_LON, charger.coordinates.lng)
|
||||
}
|
||||
}
|
||||
|
||||
fun showLocation(lat: Double, lon: Double): Bundle {
|
||||
return Bundle().apply {
|
||||
putDouble(ARG_LAT, lat)
|
||||
putDouble(ARG_LON, lon)
|
||||
}
|
||||
}
|
||||
|
||||
fun showChargerById(id: Long): Bundle {
|
||||
return Bundle().apply {
|
||||
putLong(ARG_CHARGER_ID, id)
|
||||
}
|
||||
}
|
||||
|
||||
fun showCharger(id: Long, lat: Double, lon: Double): Bundle {
|
||||
return Bundle().apply {
|
||||
putLong(ARG_CHARGER_ID, id)
|
||||
putDouble(ARG_LAT, lat)
|
||||
putDouble(ARG_LON, lon)
|
||||
}
|
||||
}
|
||||
|
||||
fun showLocationByName(query: String): Bundle {
|
||||
return Bundle().apply {
|
||||
putString(ARG_LOCATION_NAME, query)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnected() {
|
||||
@@ -1065,11 +1169,52 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
ACCESS_FINE_LOCATION
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
moveToCurrentLocation(map, false)
|
||||
moveToLastLocation(map, false)
|
||||
requestLocationUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(ACCESS_FINE_LOCATION)
|
||||
private fun requestLocationUpdates() {
|
||||
val request: LocationRequest = LocationRequest.create()
|
||||
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
|
||||
.setInterval(5000)
|
||||
LocationServices.FusedLocationApi.requestLocationUpdates(locationClient, request, this)
|
||||
requestingLocationUpdates = true
|
||||
}
|
||||
|
||||
private fun removeLocationUpdates() {
|
||||
if (locationClient.isConnected) {
|
||||
LocationServices.FusedLocationApi.removeLocationUpdates(locationClient, this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onConnectionSuspended() {
|
||||
}
|
||||
|
||||
override fun onLocationChanged(location: Location?) {
|
||||
val map = this.map ?: return
|
||||
if (location == null || vm.myLocationEnabled.value == false) return
|
||||
|
||||
val latLng = LatLng(location.latitude, location.longitude)
|
||||
val oldLoc = vm.location.value
|
||||
if (latLng != oldLoc && (oldLoc == null || distanceBetween(
|
||||
latLng.latitude,
|
||||
latLng.longitude,
|
||||
oldLoc.latitude,
|
||||
oldLoc.longitude
|
||||
) > 1)
|
||||
) {
|
||||
// only update map if location changed by more than 1 meter
|
||||
vm.location.value = latLng
|
||||
val camUpdate = map.cameraUpdateFactory.newLatLng(latLng)
|
||||
map.animateCamera(camUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
removeLocationUpdates()
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,37 @@ import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
import net.vonforst.evmap.viewmodel.SettingsViewModel
|
||||
import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat(),
|
||||
SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private lateinit var prefs: PreferenceDataSource
|
||||
|
||||
private val vm: SettingsViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
SettingsViewModel(
|
||||
requireActivity().application,
|
||||
getString(R.string.chargeprice_key)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
private lateinit var myVehiclePreference: ListPreference
|
||||
private lateinit var myTariffsPreference: MultiSelectListPreference
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
val toolbar = view.findViewById(R.id.toolbar) as Toolbar
|
||||
@@ -28,6 +45,46 @@ class SettingsFragment : PreferenceFragmentCompat(),
|
||||
navController,
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
|
||||
myVehiclePreference = findPreference("chargeprice_my_vehicle")!!
|
||||
myVehiclePreference.isEnabled = false
|
||||
vm.vehicles.observe(viewLifecycleOwner) { res ->
|
||||
res.data?.let { cars ->
|
||||
val sortedCars = cars.sortedBy { it.brand }
|
||||
myVehiclePreference.entryValues = sortedCars.map { it.id }.toTypedArray()
|
||||
myVehiclePreference.entries =
|
||||
sortedCars.map { "${it.brand} ${it.name}" }.toTypedArray()
|
||||
myVehiclePreference.isEnabled = true
|
||||
myVehiclePreference.summary = cars.find { it.id == prefs.chargepriceMyVehicle }
|
||||
?.let { "${it.brand} ${it.name}" }
|
||||
}
|
||||
}
|
||||
|
||||
myTariffsPreference = findPreference("chargeprice_my_tariffs")!!
|
||||
vm.tariffs.observe(viewLifecycleOwner) { res ->
|
||||
res.data?.let { tariffs ->
|
||||
myTariffsPreference.entryValues = tariffs.map { it.id }.toTypedArray()
|
||||
myTariffsPreference.entries = tariffs.map {
|
||||
if (!it.name.startsWith(it.provider)) {
|
||||
"${it.provider} ${it.name}"
|
||||
} else {
|
||||
it.name
|
||||
}
|
||||
}.toTypedArray()
|
||||
myTariffsPreference.isEnabled = true
|
||||
updateMyTariffsSummary()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMyTariffsSummary() {
|
||||
myTariffsPreference.summary = if (prefs.chargepriceMyTariffsAll) {
|
||||
getString(R.string.chargeprice_all_tariffs_selected)
|
||||
} else {
|
||||
val n = prefs.chargepriceMyTariffs?.size ?: 0
|
||||
requireContext().resources
|
||||
.getQuantityString(R.plurals.chargeprice_some_tariffs_selected, n, n)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
@@ -51,6 +108,18 @@ class SettingsFragment : PreferenceFragmentCompat(),
|
||||
"darkmode" -> {
|
||||
updateNightMode(prefs)
|
||||
}
|
||||
"chargeprice_my_vehicle" -> {
|
||||
vm.vehicles.value?.data?.let { cars ->
|
||||
val vehicle = cars.find { it.id == prefs.chargepriceMyVehicle }
|
||||
vehicle?.let {
|
||||
myVehiclePreference.summary = "${it.brand} ${it.name}"
|
||||
prefs.chargepriceMyVehicleDcChargeports = it.dcChargePorts
|
||||
}
|
||||
}
|
||||
}
|
||||
"chargeprice_my_tariffs" -> {
|
||||
updateMyTariffsSummary()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@ class WelcomeDialogFragment : AppCompatDialogFragment() {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.btnOk.setOnClickListener {
|
||||
PreferenceDataSource(requireContext()).welcomeDialogShown = true
|
||||
val prefs = PreferenceDataSource(requireContext())
|
||||
prefs.welcomeDialogShown = true
|
||||
prefs.update060AndroidAutoDialogShown = true
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package net.vonforst.evmap.fragment.updatedialogs
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import net.vonforst.evmap.databinding.DialogUpdate060AndroidautoBinding
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
class Update060AndroidAutoDialogFramgent : AppCompatDialogFragment() {
|
||||
private lateinit var binding: DialogUpdate060AndroidautoBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = DialogUpdate060AndroidautoBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.btnOk.setOnClickListener {
|
||||
PreferenceDataSource(requireContext()).update060AndroidAutoDialogShown = true
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
dialog?.window?.setLayout(
|
||||
WindowManager.LayoutParams.MATCH_PARENT,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -14,4 +14,7 @@ interface ChargeLocationsDao {
|
||||
|
||||
@Query("SELECT * FROM chargelocation")
|
||||
fun getAllChargeLocations(): LiveData<List<ChargeLocation>>
|
||||
|
||||
@Query("SELECT * FROM chargelocation")
|
||||
suspend fun getAllChargeLocationsAsync(): List<ChargeLocation>
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import net.vonforst.evmap.viewmodel.SliderFilterValue
|
||||
Plug::class,
|
||||
Network::class,
|
||||
ChargeCard::class
|
||||
], version = 10
|
||||
], version = 11
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
@@ -41,7 +41,7 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
Room.databaseBuilder(context, AppDatabase::class.java, "evmap.db")
|
||||
.addMigrations(
|
||||
MIGRATION_2, MIGRATION_3, MIGRATION_4, MIGRATION_5, MIGRATION_6,
|
||||
MIGRATION_7, MIGRATION_8, MIGRATION_9, MIGRATION_10
|
||||
MIGRATION_7, MIGRATION_8, MIGRATION_9, MIGRATION_10, MIGRATION_11
|
||||
)
|
||||
.addCallback(object : Callback() {
|
||||
override fun onCreate(db: SupportSQLiteDatabase) {
|
||||
@@ -170,5 +170,11 @@ abstract class AppDatabase : RoomDatabase() {
|
||||
db.execSQL("ALTER TABLE `FilterProfile` ADD `order` INTEGER NOT NULL DEFAULT 0")
|
||||
}
|
||||
}
|
||||
|
||||
private val MIGRATION_11 = object : Migration(10, 11) {
|
||||
override fun migrate(db: SupportSQLiteDatabase) {
|
||||
db.execSQL("ALTER TABLE `ChargeLocation` ADD `barrierFree` INTEGER")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package net.vonforst.evmap.storage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.car2go.maps.AnyMap
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.viewmodel.FILTERS_CUSTOM
|
||||
import net.vonforst.evmap.viewmodel.FILTERS_DISABLED
|
||||
@@ -73,9 +74,81 @@ class PreferenceDataSource(val context: Context) {
|
||||
context.getString(R.string.pref_map_provider_default)
|
||||
)!!
|
||||
|
||||
var mapType: AnyMap.Type
|
||||
get() = AnyMap.Type.valueOf(sp.getString("map_type", null) ?: AnyMap.Type.NORMAL.toString())
|
||||
set(type) {
|
||||
sp.edit().putString("map_type", type.toString()).apply()
|
||||
}
|
||||
|
||||
var mapTrafficEnabled: Boolean
|
||||
get() = sp.getBoolean("map_traffic_enabled", false)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("map_traffic_enabled", value).apply()
|
||||
}
|
||||
|
||||
var welcomeDialogShown: Boolean
|
||||
get() = sp.getBoolean("welcome_dialog_shown", false)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("welcome_dialog_shown", value).apply()
|
||||
}
|
||||
|
||||
var update060AndroidAutoDialogShown: Boolean
|
||||
get() = sp.getBoolean("update_0.6.0_androidauto_dialog_shown", false)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("update_0.6.0_androidauto_dialog_shown", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceMyVehicle: String?
|
||||
get() = sp.getString("chargeprice_my_vehicle", null)
|
||||
set(value) {
|
||||
sp.edit().putString("chargeprice_my_vehicle", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceMyVehicleDcChargeports: List<String>?
|
||||
get() = sp.getString("chargeprice_my_vehicle_dc_chargeports", null)?.split(",")
|
||||
set(value) {
|
||||
sp.edit().putString("chargeprice_my_vehicle_dc_chargeports", value?.joinToString(","))
|
||||
.apply()
|
||||
}
|
||||
|
||||
var chargepriceMyTariffs: Set<String>?
|
||||
get() = sp.getStringSet("chargeprice_my_tariffs", null)
|
||||
set(value) {
|
||||
sp.edit().putStringSet("chargeprice_my_tariffs", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceMyTariffsAll: Boolean
|
||||
get() = sp.getBoolean("chargeprice_my_tariffs_all", true)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("chargeprice_my_tariffs_all", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceNoBaseFee: Boolean
|
||||
get() = sp.getBoolean("chargeprice_no_base_fee", false)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("chargeprice_no_base_fee", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceShowProviderCustomerTariffs: Boolean
|
||||
get() = sp.getBoolean("chargeprice_show_provider_customer_tariffs", false)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("chargeprice_show_provider_customer_tariffs", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceCurrency: String
|
||||
get() = sp.getString("chargeprice_currency", null) ?: "EUR"
|
||||
set(value) {
|
||||
sp.edit().putString("chargeprice_currency", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceBatteryRange: List<Float>
|
||||
get() = listOf(
|
||||
sp.getFloat("chargeprice_battery_range_min", 20f),
|
||||
sp.getFloat("chargeprice_battery_range_max", 80f),
|
||||
)
|
||||
set(value) {
|
||||
sp.edit().putFloat("chargeprice_battery_range_min", value[0])
|
||||
.putFloat("chargeprice_battery_range_max", value[1])
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.text.Layout
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import kotlin.math.ceil
|
||||
|
||||
class BalancedBreakingTextView(context: Context, attrs: AttributeSet) :
|
||||
AppCompatTextView(context, attrs) {
|
||||
|
||||
@Override
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
|
||||
if (layout != null) {
|
||||
val width =
|
||||
ceil(getMaxLineWidth(layout)).toInt() + compoundPaddingLeft + compoundPaddingRight
|
||||
val height = measuredHeight
|
||||
setMeasuredDimension(width, height)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMaxLineWidth(layout: Layout): Float {
|
||||
var maxWidth = 0.0f
|
||||
for (i in 0 until layout.lineCount) {
|
||||
if (layout.getLineWidth(i) > maxWidth) {
|
||||
maxWidth = layout.getLineWidth(i)
|
||||
}
|
||||
}
|
||||
return maxWidth
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,21 @@ import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.use
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.databinding.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.api.iconForPlugType
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
@@ -50,6 +54,25 @@ fun invisibleUnless(view: View, visible: Boolean) {
|
||||
view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
|
||||
}
|
||||
|
||||
@BindingAdapter("invisibleUnlessAnimated")
|
||||
fun invisibleUnlessAnimated(view: View, oldValue: Boolean, newValue: Boolean) {
|
||||
if (oldValue == newValue) return
|
||||
|
||||
view.animate().cancel()
|
||||
if (newValue) {
|
||||
view.visibility = View.VISIBLE
|
||||
view.alpha = 0f
|
||||
view.animate().alpha(1f).withEndAction {
|
||||
view.alpha = 1f
|
||||
}
|
||||
} else {
|
||||
view.animate().alpha(0f).withEndAction {
|
||||
view.alpha = 1f
|
||||
view.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("isFabActive")
|
||||
fun isFabActive(view: FloatingActionButton, isColored: Boolean) {
|
||||
val color = view.context.theme.obtainStyledAttributes(
|
||||
@@ -80,20 +103,7 @@ fun <T> setRecyclerViewData(recyclerView: ViewPager2, items: List<T>?) {
|
||||
|
||||
@BindingAdapter("connectorIcon")
|
||||
fun getConnectorItem(view: ImageView, type: String) {
|
||||
view.setImageResource(
|
||||
when (type) {
|
||||
Chargepoint.CCS -> R.drawable.ic_connector_ccs
|
||||
Chargepoint.CHADEMO -> R.drawable.ic_connector_chademo
|
||||
Chargepoint.SCHUKO -> R.drawable.ic_connector_schuko
|
||||
Chargepoint.SUPERCHARGER -> R.drawable.ic_connector_supercharger
|
||||
Chargepoint.TYPE_2 -> R.drawable.ic_connector_typ2
|
||||
Chargepoint.CEE_BLAU -> R.drawable.ic_connector_cee_blau
|
||||
Chargepoint.CEE_ROT -> R.drawable.ic_connector_cee_rot
|
||||
Chargepoint.TYPE_1 -> R.drawable.ic_connector_typ1
|
||||
// TODO: add other connectors
|
||||
else -> 0
|
||||
}
|
||||
)
|
||||
view.setImageResource(iconForPlugType(type))
|
||||
}
|
||||
|
||||
@BindingAdapter("srcCompat")
|
||||
@@ -171,19 +181,51 @@ fun setLinkify(textView: TextView, oldValue: Int, newValue: Int) {
|
||||
}
|
||||
}
|
||||
|
||||
@BindingAdapter("chargepriceTagColor")
|
||||
fun setChargepriceTagColor(view: TextView, kind: String) {
|
||||
view.backgroundTintList = ColorStateList.valueOf(
|
||||
ContextCompat.getColor(
|
||||
view.context,
|
||||
when (kind) {
|
||||
"star" -> R.color.chargeprice_star
|
||||
"alert" -> R.color.chargeprice_alert
|
||||
"info" -> R.color.chargeprice_info
|
||||
"lock" -> R.color.chargeprice_lock
|
||||
else -> R.color.chip_background
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@BindingAdapter("chargepriceTagIcon")
|
||||
fun setChargepriceTagIcon(view: TextView, kind: String) {
|
||||
view.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
when (kind) {
|
||||
"star" -> R.drawable.ic_chargeprice_star
|
||||
"alert" -> R.drawable.ic_chargeprice_alert
|
||||
"info" -> R.drawable.ic_chargeprice_info
|
||||
"lock" -> R.drawable.ic_chargeprice_lock
|
||||
else -> 0
|
||||
}, 0, 0, 0
|
||||
)
|
||||
}
|
||||
|
||||
private fun availabilityColor(
|
||||
status: List<ChargepointStatus>?,
|
||||
context: Context
|
||||
): Int = if (status != null) {
|
||||
val unknown = status.any { it == ChargepointStatus.UNKNOWN }
|
||||
val available = status.count { it == ChargepointStatus.AVAILABLE }
|
||||
val allFaulted = status.all { it == ChargepointStatus.FAULTED }
|
||||
|
||||
if (unknown) {
|
||||
ContextCompat.getColor(context, R.color.unknown)
|
||||
} else if (available > 0) {
|
||||
ContextCompat.getColor(context, R.color.available)
|
||||
} else {
|
||||
} else if (allFaulted) {
|
||||
ContextCompat.getColor(context, R.color.unavailable)
|
||||
} else {
|
||||
ContextCompat.getColor(context, R.color.charging)
|
||||
}
|
||||
} else {
|
||||
val ta = context.theme.obtainStyledAttributes(intArrayOf(R.attr.colorControlNormal))
|
||||
@@ -204,4 +246,60 @@ fun availabilityText(status: List<ChargepointStatus>?): String? {
|
||||
|
||||
fun flatten(it: Iterable<Iterable<ChargepointStatus>>?): List<ChargepointStatus>? {
|
||||
return it?.flatten()
|
||||
}
|
||||
|
||||
fun currency(currency: String): String {
|
||||
// shorthands for currencies
|
||||
return when (currency) {
|
||||
"EUR" -> "€"
|
||||
"USD" -> "$"
|
||||
"DKK", "SEK", "NOK" -> "kr."
|
||||
"PLN" -> "zł"
|
||||
"CHF" -> "Fr. "
|
||||
"CZK" -> "Kč"
|
||||
"GBP" -> "£"
|
||||
"HRK" -> "kn"
|
||||
"HUF" -> "Ft"
|
||||
"ISK" -> "Kr"
|
||||
else -> currency
|
||||
}
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "app:values")
|
||||
fun getRangeSliderValue(slider: RangeSlider) = slider.values
|
||||
|
||||
@BindingAdapter("app:valuesAttrChanged")
|
||||
fun setRangeSliderListeners(slider: RangeSlider, attrChange: InverseBindingListener) {
|
||||
slider.addOnChangeListener { _, _, _ ->
|
||||
attrChange.onChange()
|
||||
}
|
||||
}
|
||||
|
||||
@ColorInt
|
||||
fun colorEnabled(ctx: Context, enabled: Boolean): Int {
|
||||
val attr = if (enabled) {
|
||||
android.R.attr.textColorSecondary
|
||||
} else {
|
||||
android.R.attr.textColorHint
|
||||
}
|
||||
val typedValue = ctx.obtainStyledAttributes(intArrayOf(attr))
|
||||
val color = typedValue.getColor(0, 0)
|
||||
typedValue.recycle()
|
||||
return color
|
||||
}
|
||||
|
||||
@BindingAdapter("app:tint")
|
||||
fun setImageTintList(view: ImageView, @ColorInt color: Int) {
|
||||
view.imageTintList = ColorStateList.valueOf(color)
|
||||
}
|
||||
|
||||
@BindingAdapter("myTariffsBackground")
|
||||
fun myTariffsBackground(view: View, myTariff: Boolean) {
|
||||
if (myTariff) {
|
||||
view.background = ContextCompat.getDrawable(view.context, R.drawable.my_tariff_background)
|
||||
} else {
|
||||
view.context.obtainStyledAttributes(intArrayOf(R.attr.selectableItemBackground)).use {
|
||||
view.background = it.getDrawable(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.Checkable
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
||||
|
||||
class CheckableConstraintLayout(ctx: Context, attrs: AttributeSet) : ConstraintLayout(ctx, attrs),
|
||||
Checkable {
|
||||
private var onCheckedChangeListener: ((View, Boolean) -> Unit)? = null
|
||||
private var checked = false
|
||||
private val CHECKED_STATE_SET = intArrayOf(android.R.attr.state_checked)
|
||||
|
||||
override fun setChecked(b: Boolean) {
|
||||
if (b != checked) {
|
||||
checked = b;
|
||||
refreshDrawableState();
|
||||
onCheckedChangeListener?.invoke(this, checked);
|
||||
}
|
||||
}
|
||||
|
||||
override fun isChecked(): Boolean {
|
||||
return checked
|
||||
}
|
||||
|
||||
override fun toggle() {
|
||||
checked = !checked
|
||||
}
|
||||
|
||||
override fun onCreateDrawableState(extraSpace: Int): IntArray? {
|
||||
val drawableState = super.onCreateDrawableState(extraSpace + 1)
|
||||
if (isChecked) {
|
||||
mergeDrawableStates(drawableState, CHECKED_STATE_SET)
|
||||
}
|
||||
return drawableState
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when the checked state of this view changes.
|
||||
*
|
||||
* @param listener the callback to call on checked state change
|
||||
*/
|
||||
fun setOnCheckedChangeListener(listener: (View, Boolean) -> Unit) {
|
||||
onCheckedChangeListener = listener
|
||||
}
|
||||
}
|
||||
@@ -43,8 +43,11 @@ class ClusterIconGenerator(context: Context) : IconGenerator(context) {
|
||||
|
||||
|
||||
class ChargerIconGenerator(
|
||||
val context: Context, val factory: BitmapDescriptorFactory,
|
||||
val scaleResolution: Int = 20
|
||||
val context: Context,
|
||||
val factory: BitmapDescriptorFactory?,
|
||||
val scaleResolution: Int = 20,
|
||||
val oversize: Float = 1.4f, // increase to add padding for fault icon or scale > 1
|
||||
val height: Int = 44
|
||||
) {
|
||||
private data class BitmapData(
|
||||
val tint: Int,
|
||||
@@ -58,7 +61,6 @@ class ChargerIconGenerator(
|
||||
// 230 items: (21 sizes, 5 colors, multi on/off) + highlight + fault (only with scale = 1)
|
||||
private val cacheSize = (scaleResolution + 3) * 5 * 2;
|
||||
private val cache = LruCache<BitmapData, BitmapDescriptor>(cacheSize)
|
||||
private val oversize = 1.4f // increase to add padding for fault icon or scale > 1
|
||||
private val icon = R.drawable.ic_map_marker_charging
|
||||
private val multiIcon = R.drawable.ic_map_marker_charging_multiple
|
||||
private val highlightIcon = R.drawable.ic_map_marker_highlight
|
||||
@@ -110,12 +112,30 @@ class ChargerIconGenerator(
|
||||
cachedImg
|
||||
} else {
|
||||
val bitmap = generateBitmap(data)
|
||||
val bmd = factory.fromBitmap(bitmap)
|
||||
val bmd = factory!!.fromBitmap(bitmap)
|
||||
cache.put(data, bmd)
|
||||
bmd
|
||||
}
|
||||
}
|
||||
|
||||
fun getBitmap(
|
||||
@ColorRes tint: Int,
|
||||
scale: Float = 1f,
|
||||
alpha: Int = 255,
|
||||
highlight: Boolean = false,
|
||||
fault: Boolean = false,
|
||||
multi: Boolean = false
|
||||
): Bitmap {
|
||||
val data = BitmapData(
|
||||
tint, (scale * scaleResolution).roundToInt(),
|
||||
alpha,
|
||||
if (scale == 1f) highlight else false,
|
||||
if (scale == 1f) fault else false,
|
||||
multi
|
||||
)
|
||||
return generateBitmap(data)
|
||||
}
|
||||
|
||||
private fun generateBitmap(data: BitmapData): Bitmap {
|
||||
val icon = if (data.multi) multiIcon else icon
|
||||
val vd: Drawable = ContextCompat.getDrawable(context, icon)!!
|
||||
@@ -123,17 +143,22 @@ class ChargerIconGenerator(
|
||||
DrawableCompat.setTint(vd, ContextCompat.getColor(context, data.tint));
|
||||
DrawableCompat.setTintMode(vd, PorterDuff.Mode.MULTIPLY);
|
||||
|
||||
val leftPadding = vd.intrinsicWidth * (oversize - 1) / 2
|
||||
val topPadding = vd.intrinsicHeight * (oversize - 1)
|
||||
val density = context.resources.displayMetrics.density
|
||||
val width =
|
||||
(height.toFloat() * density / vd.intrinsicHeight * vd.intrinsicWidth).roundToInt()
|
||||
val height = (height * density).roundToInt()
|
||||
|
||||
val leftPadding = width * (oversize - 1) / 2
|
||||
val topPadding = height * (oversize - 1)
|
||||
vd.setBounds(
|
||||
leftPadding.toInt(), topPadding.toInt(),
|
||||
leftPadding.toInt() + vd.intrinsicWidth,
|
||||
topPadding.toInt() + vd.intrinsicHeight
|
||||
leftPadding.toInt() + width,
|
||||
topPadding.toInt() + height
|
||||
)
|
||||
vd.alpha = data.alpha
|
||||
|
||||
val bm = Bitmap.createBitmap(
|
||||
(vd.intrinsicWidth * oversize).toInt(), (vd.intrinsicHeight * oversize).toInt(),
|
||||
(width * oversize).toInt(), (height * oversize).toInt(),
|
||||
Bitmap.Config.ARGB_8888
|
||||
)
|
||||
val canvas = Canvas(bm)
|
||||
@@ -142,8 +167,8 @@ class ChargerIconGenerator(
|
||||
canvas.scale(
|
||||
scale,
|
||||
scale,
|
||||
leftPadding + vd.intrinsicWidth / 2f,
|
||||
topPadding + vd.intrinsicHeight.toFloat()
|
||||
leftPadding + width / 2f,
|
||||
topPadding + height.toFloat()
|
||||
)
|
||||
|
||||
vd.draw(canvas)
|
||||
@@ -153,8 +178,8 @@ class ChargerIconGenerator(
|
||||
val highlightDrawable = ContextCompat.getDrawable(context, hIcon)!!
|
||||
highlightDrawable.setBounds(
|
||||
leftPadding.toInt(), topPadding.toInt(),
|
||||
leftPadding.toInt() + vd.intrinsicWidth,
|
||||
topPadding.toInt() + vd.intrinsicHeight
|
||||
leftPadding.toInt() + width,
|
||||
topPadding.toInt() + height
|
||||
)
|
||||
highlightDrawable.alpha = data.alpha
|
||||
highlightDrawable.draw(canvas)
|
||||
@@ -164,7 +189,7 @@ class ChargerIconGenerator(
|
||||
val faultDrawable = ContextCompat.getDrawable(context, faultIcon)!!
|
||||
val faultSize = 0.75
|
||||
val faultShift = 0.25
|
||||
val base = vd.intrinsicWidth
|
||||
val base = width
|
||||
faultDrawable.setBounds(
|
||||
(leftPadding.toInt() + base * (1 - faultSize + faultShift)).toInt(),
|
||||
(topPadding.toInt() - base * faultShift).toInt(),
|
||||
|
||||
@@ -12,7 +12,7 @@ import kotlin.math.max
|
||||
|
||||
fun getMarkerTint(
|
||||
charger: ChargeLocation,
|
||||
connectors: Set<String>?
|
||||
connectors: Set<String>? = null
|
||||
): Int = when {
|
||||
charger.maxPower(connectors) >= 100 -> R.color.charger_100kw
|
||||
charger.maxPower(connectors) >= 43 -> R.color.charger_43kw
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import net.vonforst.evmap.fragment.MultiSelectDialog
|
||||
|
||||
class MultiSelectDialogPreference(ctx: Context, attrs: AttributeSet) :
|
||||
MultiSelectListPreference(ctx, attrs) {
|
||||
override fun onClick() {
|
||||
val dialog =
|
||||
MultiSelectDialog.getInstance(
|
||||
title.toString(),
|
||||
entryValues.map { it.toString() }.zip(entries.map { it.toString() }).toMap(),
|
||||
if (all) entryValues.map { it.toString() }.toSet() else values,
|
||||
emptySet()
|
||||
)
|
||||
dialog.okListener = { selected ->
|
||||
all = selected == entryValues.toSet()
|
||||
values = selected
|
||||
}
|
||||
dialog.show((context as AppCompatActivity).supportFragmentManager, null)
|
||||
}
|
||||
|
||||
var all: Boolean
|
||||
get() = sharedPreferences.getBoolean(key + "_all", true)
|
||||
set(value) {
|
||||
sharedPreferences.edit().putBoolean(key + "_all", value).apply()
|
||||
}
|
||||
}
|
||||
34
app/src/main/java/net/vonforst/evmap/utils/LocationUtils.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package net.vonforst.evmap.utils
|
||||
|
||||
import android.location.Location
|
||||
import kotlin.math.*
|
||||
|
||||
/**
|
||||
* Adds a certain distance in meters to a location. Approximate calculation.
|
||||
*/
|
||||
fun Location.plusMeters(dx: Double, dy: Double): Pair<Double, Double> {
|
||||
val lat = this.latitude + (180 / Math.PI) * (dx / 6378137.0)
|
||||
val lon = this.longitude + (180 / Math.PI) * (dy / 6378137.0) / cos(Math.toRadians(lat))
|
||||
return Pair(lat, lon)
|
||||
}
|
||||
|
||||
const val earthRadiusM = 6378137.0
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points on Earth in meters.
|
||||
* Latitude and longitude should be given in degrees.
|
||||
*/
|
||||
fun distanceBetween(
|
||||
startLatitude: Double, startLongitude: Double,
|
||||
endLatitude: Double, endLongitude: Double
|
||||
): Double {
|
||||
// see https://rosettacode.org/wiki/Haversine_formula#Java
|
||||
val dLat = Math.toRadians(endLatitude - startLatitude)
|
||||
val dLon = Math.toRadians(endLongitude - startLongitude)
|
||||
val originLat = Math.toRadians(startLatitude)
|
||||
val destinationLat = Math.toRadians(endLatitude)
|
||||
|
||||
val a = sin(dLat / 2).pow(2.0) + sin(dLon / 2).pow(2.0) * cos(originLat) * cos(destinationLat)
|
||||
val c = 2 * asin(sqrt(a))
|
||||
return earthRadiusM * c
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
|
||||
class ChargepriceViewModel(application: Application, chargepriceApiKey: String) :
|
||||
AndroidViewModel(application) {
|
||||
private var api = ChargepriceApi.create(chargepriceApiKey)
|
||||
private var prefs = PreferenceDataSource(application)
|
||||
|
||||
val charger: MutableLiveData<ChargeLocation> by lazy {
|
||||
MutableLiveData<ChargeLocation>()
|
||||
}
|
||||
|
||||
val chargepoint: MutableLiveData<Chargepoint> by lazy {
|
||||
MutableLiveData<Chargepoint>()
|
||||
}
|
||||
|
||||
val vehicle: LiveData<ChargepriceCar> by lazy {
|
||||
MutableLiveData<ChargepriceCar>().apply {
|
||||
value = prefs.chargepriceMyVehicle?.let { ChargepriceCar().apply { id = it } }
|
||||
}
|
||||
}
|
||||
|
||||
private val acConnectors = listOf(
|
||||
Chargepoint.CEE_BLAU,
|
||||
Chargepoint.CEE_ROT,
|
||||
Chargepoint.SCHUKO,
|
||||
Chargepoint.TYPE_1,
|
||||
Chargepoint.TYPE_2
|
||||
)
|
||||
private val plugMapping = mapOf(
|
||||
"ccs" to Chargepoint.CCS,
|
||||
"tesla_suc" to Chargepoint.SUPERCHARGER,
|
||||
"tesla_ccs" to Chargepoint.CCS,
|
||||
"chademo" to Chargepoint.CHADEMO
|
||||
)
|
||||
val vehicleCompatibleConnectors: LiveData<List<String>> by lazy {
|
||||
MutableLiveData<List<String>>().apply {
|
||||
value = prefs.chargepriceMyVehicleDcChargeports?.map {
|
||||
plugMapping.get(it)
|
||||
}?.filterNotNull()?.plus(acConnectors)
|
||||
}
|
||||
}
|
||||
|
||||
val noCompatibleConnectors: LiveData<Boolean> by lazy {
|
||||
MediatorLiveData<Boolean>().apply {
|
||||
value = false
|
||||
listOf(charger, vehicleCompatibleConnectors).forEach {
|
||||
addSource(it) {
|
||||
val charger = charger.value ?: return@addSource
|
||||
val connectors = vehicleCompatibleConnectors.value ?: return@addSource
|
||||
value = !charger.chargepoints.map { it.type }.any { it in connectors }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val batteryRange: MutableLiveData<List<Float>> by lazy {
|
||||
MutableLiveData<List<Float>>().apply {
|
||||
value = prefs.chargepriceBatteryRange
|
||||
observeForever {
|
||||
prefs.chargepriceBatteryRange = it
|
||||
}
|
||||
}
|
||||
}
|
||||
val batteryRangeSliderDragging: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>().apply {
|
||||
value = false
|
||||
}
|
||||
}
|
||||
|
||||
val chargePrices: MediatorLiveData<Resource<List<ChargePrice>>> by lazy {
|
||||
MediatorLiveData<Resource<List<ChargePrice>>>().apply {
|
||||
value = Resource.loading(null)
|
||||
listOf(
|
||||
charger,
|
||||
vehicle,
|
||||
batteryRange,
|
||||
batteryRangeSliderDragging,
|
||||
vehicleCompatibleConnectors
|
||||
).forEach {
|
||||
addSource(it) {
|
||||
if (!batteryRangeSliderDragging.value!!) loadPrices()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val chargePriceMeta: MutableLiveData<Resource<ChargepriceMeta>> by lazy {
|
||||
MutableLiveData<Resource<ChargepriceMeta>>().apply {
|
||||
value = Resource.loading(null)
|
||||
}
|
||||
}
|
||||
|
||||
val chargePricesForChargepoint: MediatorLiveData<Resource<List<ChargePrice>>> by lazy {
|
||||
MediatorLiveData<Resource<List<ChargePrice>>>().apply {
|
||||
listOf(chargePrices, chargepoint).forEach {
|
||||
addSource(it) {
|
||||
val cps = chargePrices.value
|
||||
val chargepoint = chargepoint.value
|
||||
if (cps == null || chargepoint == null) {
|
||||
value = null
|
||||
} else if (cps.status == Status.ERROR) {
|
||||
value = Resource.error(cps.message, null)
|
||||
} else if (cps.status == Status.LOADING) {
|
||||
value = Resource.loading(null)
|
||||
} else {
|
||||
val myTariffs = prefs.chargepriceMyTariffs
|
||||
value = Resource.success(cps.data!!.map { cp ->
|
||||
val filteredPrices =
|
||||
cp.chargepointPrices.filter { it.plug == chargepoint.type && 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
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val myTariffs: LiveData<Set<String>> by lazy {
|
||||
MutableLiveData<Set<String>>().apply {
|
||||
value = prefs.chargepriceMyTariffs
|
||||
}
|
||||
}
|
||||
val myTariffsAll: LiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>().apply {
|
||||
value = prefs.chargepriceMyTariffsAll
|
||||
}
|
||||
}
|
||||
|
||||
val chargepriceMetaForChargepoint: MediatorLiveData<Resource<ChargepriceChargepointMeta>> by lazy {
|
||||
MediatorLiveData<Resource<ChargepriceChargepointMeta>>().apply {
|
||||
listOf(chargePriceMeta, chargepoint).forEach {
|
||||
addSource(it) {
|
||||
val cpMeta = chargePriceMeta.value
|
||||
val chargepoint = chargepoint.value
|
||||
if (cpMeta == null || chargepoint == null) {
|
||||
value = null
|
||||
} else if (cpMeta.status == Status.ERROR) {
|
||||
value = Resource.error(cpMeta.message, null)
|
||||
} else if (cpMeta.status == Status.LOADING) {
|
||||
value = Resource.loading(null)
|
||||
} else {
|
||||
value =
|
||||
Resource.success(cpMeta.data!!.chargePoints.filter { it.plug == chargepoint.type && it.power == chargepoint.power }[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var loadPricesJob: Job? = null
|
||||
fun loadPrices() {
|
||||
chargePrices.value = Resource.loading(null)
|
||||
val geCharger = charger.value
|
||||
val car = vehicle.value
|
||||
val compatibleConnectors = vehicleCompatibleConnectors.value
|
||||
if (geCharger == null || car == null || compatibleConnectors == null) {
|
||||
chargePrices.value = Resource.error(null, null)
|
||||
return
|
||||
}
|
||||
|
||||
val cpStation = ChargepriceStation.fromGoingelectric(geCharger, compatibleConnectors)
|
||||
|
||||
loadPricesJob?.cancel()
|
||||
loadPricesJob = viewModelScope.launch {
|
||||
try {
|
||||
val result = api.getChargePrices(ChargepriceRequest().apply {
|
||||
dataAdapter = "going_electric"
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange.value!!.map { it.toDouble() },
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency
|
||||
)
|
||||
}, getChargepriceLanguage())
|
||||
val meta =
|
||||
result.meta.get<ChargepriceMeta>(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta
|
||||
chargePrices.value = Resource.success(result)
|
||||
chargePriceMeta.value = Resource.success(meta)
|
||||
} catch (e: IOException) {
|
||||
chargePrices.value = Resource.error(e.message, null)
|
||||
chargePriceMeta.value = Resource.error(e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getChargepriceLanguage(): String {
|
||||
val locale = Locale.getDefault().language
|
||||
return if (ChargepriceApi.supportedLanguages.contains(locale)) {
|
||||
locale
|
||||
} else {
|
||||
"en"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,10 @@ import net.vonforst.evmap.adapter.Equatable
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
import net.vonforst.evmap.api.distanceBetween
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
|
||||
class FavoritesViewModel(application: Application, geApiKey: String) :
|
||||
AndroidViewModel(application) {
|
||||
|
||||
@@ -33,6 +33,12 @@ class FilterProfilesViewModel(application: Application) : AndroidViewModel(appli
|
||||
}
|
||||
}
|
||||
|
||||
fun update(item: FilterProfile) {
|
||||
viewModelScope.launch {
|
||||
db.filterProfileDao().update(item)
|
||||
}
|
||||
}
|
||||
|
||||
fun reorderProfiles(list: List<FilterProfile>) {
|
||||
viewModelScope.launch {
|
||||
db.filterProfileDao().update(*list.toTypedArray())
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.vonforst.evmap.adapter.Equatable
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeCard
|
||||
import net.vonforst.evmap.api.goingelectric.Chargepoint
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.api.nameForPlugType
|
||||
import net.vonforst.evmap.storage.*
|
||||
import kotlin.math.abs
|
||||
import kotlin.reflect.KClass
|
||||
@@ -30,21 +31,9 @@ internal fun getFilters(
|
||||
chargeCards: LiveData<List<ChargeCard>>
|
||||
): LiveData<List<Filter<FilterValue>>> {
|
||||
return MediatorLiveData<List<Filter<FilterValue>>>().apply {
|
||||
val plugNames = mapOf(
|
||||
Chargepoint.TYPE_1 to application.getString(R.string.plug_type_1),
|
||||
Chargepoint.TYPE_2 to application.getString(R.string.plug_type_2),
|
||||
Chargepoint.TYPE_3 to application.getString(R.string.plug_type_3),
|
||||
Chargepoint.CCS to application.getString(R.string.plug_ccs),
|
||||
Chargepoint.SCHUKO to application.getString(R.string.plug_schuko),
|
||||
Chargepoint.CHADEMO to application.getString(R.string.plug_chademo),
|
||||
Chargepoint.SUPERCHARGER to application.getString(R.string.plug_supercharger),
|
||||
Chargepoint.CEE_BLAU to application.getString(R.string.plug_cee_blau),
|
||||
Chargepoint.CEE_ROT to application.getString(R.string.plug_cee_rot),
|
||||
Chargepoint.TESLA_ROADSTER_HPC to application.getString(R.string.plug_roadster_hpc)
|
||||
)
|
||||
listOf(plugs, networks, chargeCards).forEach { source ->
|
||||
addSource(source) { _ ->
|
||||
buildFilters(plugs, plugNames, networks, chargeCards, application)
|
||||
buildFilters(plugs, networks, chargeCards, application)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,13 +41,12 @@ internal fun getFilters(
|
||||
|
||||
private fun MediatorLiveData<List<Filter<FilterValue>>>.buildFilters(
|
||||
plugs: LiveData<List<Plug>>,
|
||||
plugNames: Map<String, String>,
|
||||
networks: LiveData<List<Network>>,
|
||||
chargeCards: LiveData<List<ChargeCard>>,
|
||||
application: Application
|
||||
) {
|
||||
val plugMap = plugs.value?.map { plug ->
|
||||
plug.name to (plugNames[plug.name] ?: plug.name)
|
||||
plug.name to nameForPlugType(application, plug.name)
|
||||
}?.toMap() ?: return
|
||||
val networkMap = networks.value?.map { it.name to it.name }?.toMap() ?: return
|
||||
val chargecardMap = chargeCards.value?.map { it.id.toString() to it.name }?.toMap() ?: return
|
||||
@@ -135,8 +123,8 @@ private fun MediatorLiveData<List<Filter<FilterValue>>>.buildFilters(
|
||||
internal fun filtersWithValue(
|
||||
filters: LiveData<List<Filter<FilterValue>>>,
|
||||
filterValues: LiveData<List<FilterValue>>
|
||||
): MediatorLiveData<List<FilterWithValue<out FilterValue>>> =
|
||||
MediatorLiveData<List<FilterWithValue<out FilterValue>>>().apply {
|
||||
): MediatorLiveData<FilterValues> =
|
||||
MediatorLiveData<FilterValues>().apply {
|
||||
listOf(filters, filterValues).forEach {
|
||||
addSource(it) {
|
||||
val f = filters.value ?: return@addSource
|
||||
@@ -173,7 +161,7 @@ class FilterViewModel(application: Application, geApiKey: String) :
|
||||
db.filterValueDao().getFilterValues(FILTERS_CUSTOM)
|
||||
}
|
||||
|
||||
val filtersWithValue: LiveData<List<FilterWithValue<out FilterValue>>> by lazy {
|
||||
val filtersWithValue: LiveData<FilterValues> by lazy {
|
||||
filtersWithValue(filters, filterValues)
|
||||
}
|
||||
|
||||
@@ -331,5 +319,19 @@ data class SliderFilterValue(
|
||||
|
||||
data class FilterWithValue<T : FilterValue>(val filter: Filter<T>, val value: T) : Equatable
|
||||
|
||||
typealias FilterValues = List<FilterWithValue<out FilterValue>>
|
||||
|
||||
fun FilterValues.getBooleanValue(key: String) =
|
||||
(this.find { it.value.key == key }!!.value as BooleanFilterValue).value
|
||||
|
||||
fun FilterValues.getSliderValue(key: String) =
|
||||
(this.find { it.value.key == key }!!.value as SliderFilterValue).value
|
||||
|
||||
fun FilterValues.getMultipleChoiceFilter(key: String) =
|
||||
this.find { it.value.key == key }!!.filter as MultipleChoiceFilter
|
||||
|
||||
fun FilterValues.getMultipleChoiceValue(key: String) =
|
||||
this.find { it.value.key == key }!!.value as MultipleChoiceFilterValue
|
||||
|
||||
const val FILTERS_DISABLED = -2L
|
||||
const val FILTERS_CUSTOM = -1L
|
||||
@@ -6,17 +6,16 @@ import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
import net.vonforst.evmap.api.distanceBetween
|
||||
import net.vonforst.evmap.api.goingelectric.*
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeCard
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.ChargepointListItem
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.storage.*
|
||||
import net.vonforst.evmap.ui.cluster
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import java.io.IOException
|
||||
|
||||
data class MapPosition(val bounds: LatLngBounds, val zoom: Float)
|
||||
@@ -37,7 +36,6 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
private var api = GoingElectricApi.create(geApiKey, context = application)
|
||||
private var db = AppDatabase.getInstance(application)
|
||||
private var prefs = PreferenceDataSource(application)
|
||||
private var chargepointLoader: Job? = null
|
||||
|
||||
val bottomSheetState: MutableLiveData<Int> by lazy {
|
||||
MutableLiveData<Int>()
|
||||
@@ -69,7 +67,7 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
}
|
||||
private val filters = getFilters(application, plugs, networks, chargeCards)
|
||||
|
||||
private val filtersWithValue: LiveData<List<FilterWithValue<out FilterValue>>> by lazy {
|
||||
private val filtersWithValue: LiveData<FilterValues> by lazy {
|
||||
filtersWithValue(filters, filterValues)
|
||||
}
|
||||
|
||||
@@ -147,7 +145,7 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
val callback = { _: Any? ->
|
||||
val loc = location.value
|
||||
val charger = chargerSparse.value
|
||||
value = if (loc != null && charger != null && myLocationEnabled.value == true) {
|
||||
value = if (loc != null && charger != null) {
|
||||
distanceBetween(
|
||||
loc.latitude,
|
||||
loc.longitude,
|
||||
@@ -158,7 +156,6 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
}
|
||||
addSource(chargerSparse, callback)
|
||||
addSource(location, callback)
|
||||
addSource(myLocationEnabled, callback)
|
||||
}
|
||||
}
|
||||
val location: MutableLiveData<LatLng> by lazy {
|
||||
@@ -177,6 +174,21 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
}
|
||||
}
|
||||
}
|
||||
val filteredAvailability: MediatorLiveData<Resource<ChargeLocationStatus>> by lazy {
|
||||
MediatorLiveData<Resource<ChargeLocationStatus>>().apply {
|
||||
val callback = { _: Any? ->
|
||||
val av = availability.value
|
||||
val filters = filtersWithValue.value
|
||||
if (av?.status == Status.SUCCESS && filters != null) {
|
||||
value = Resource.success(av.data!!.applyFilters(filters))
|
||||
} else {
|
||||
value = av
|
||||
}
|
||||
}
|
||||
addSource(availability, callback)
|
||||
addSource(filtersWithValue, callback)
|
||||
}
|
||||
}
|
||||
val myLocationEnabled: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>()
|
||||
}
|
||||
@@ -196,13 +208,19 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
|
||||
val mapType: MutableLiveData<AnyMap.Type> by lazy {
|
||||
MutableLiveData<AnyMap.Type>().apply {
|
||||
value = AnyMap.Type.NORMAL
|
||||
value = prefs.mapType
|
||||
observeForever {
|
||||
prefs.mapType = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val mapTrafficEnabled: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>().apply {
|
||||
value = false
|
||||
value = prefs.mapTrafficEnabled
|
||||
observeForever {
|
||||
prefs.mapTrafficEnabled = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,39 +278,41 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
loadChargepoints(pos, filters)
|
||||
}
|
||||
|
||||
private fun loadChargepoints(
|
||||
mapPosition: MapPosition,
|
||||
filters: List<FilterWithValue<out FilterValue>>
|
||||
) {
|
||||
chargepointLoader?.cancel()
|
||||
private var chargepointLoader =
|
||||
throttleLatest(500L, viewModelScope) { data: Pair<MapPosition, FilterValues> ->
|
||||
chargepoints.value = Resource.loading(chargepoints.value?.data)
|
||||
filteredConnectors.value = null
|
||||
filteredChargeCards.value = null
|
||||
|
||||
chargepoints.value = Resource.loading(chargepoints.value?.data)
|
||||
filteredConnectors.value = null
|
||||
filteredChargeCards.value = null
|
||||
val bounds = mapPosition.bounds
|
||||
val zoom = mapPosition.zoom
|
||||
chargepointLoader = viewModelScope.launch {
|
||||
val result = getChargepointsWithFilters(bounds, zoom, filters)
|
||||
val mapPosition = data.first
|
||||
val filters = data.second
|
||||
val result = getChargepointsWithFilters(mapPosition.bounds, mapPosition.zoom, filters)
|
||||
filteredConnectors.value = result.second
|
||||
filteredChargeCards.value = result.third
|
||||
chargepoints.value = result.first
|
||||
}
|
||||
|
||||
private fun loadChargepoints(
|
||||
mapPosition: MapPosition,
|
||||
filters: FilterValues
|
||||
) {
|
||||
chargepointLoader(Pair(mapPosition, filters))
|
||||
}
|
||||
|
||||
private suspend fun getChargepointsWithFilters(
|
||||
bounds: LatLngBounds,
|
||||
zoom: Float,
|
||||
filters: List<FilterWithValue<out FilterValue>>
|
||||
filters: FilterValues
|
||||
): Triple<Resource<List<ChargepointListItem>>, Set<String>?, Set<Long>?> {
|
||||
val freecharging = getBooleanValue(filters, "freecharging")
|
||||
val freeparking = getBooleanValue(filters, "freeparking")
|
||||
val open247 = getBooleanValue(filters, "open_247")
|
||||
val barrierfree = getBooleanValue(filters, "barrierfree")
|
||||
val excludeFaults = getBooleanValue(filters, "exclude_faults")
|
||||
val minPower = getSliderValue(filters, "min_power")
|
||||
val minConnectors = getSliderValue(filters, "min_connectors")
|
||||
val freecharging = filters.getBooleanValue("freecharging")
|
||||
val freeparking = filters.getBooleanValue("freeparking")
|
||||
val open247 = filters.getBooleanValue("open_247")
|
||||
val barrierfree = filters.getBooleanValue("barrierfree")
|
||||
val excludeFaults = filters.getBooleanValue("exclude_faults")
|
||||
val minPower = filters.getSliderValue("min_power")
|
||||
val minConnectors = filters.getSliderValue("min_connectors")
|
||||
|
||||
val connectorsVal = getMultipleChoiceValue(filters, "connectors")
|
||||
val connectorsVal = filters.getMultipleChoiceValue("connectors")
|
||||
if (connectorsVal.values.isEmpty() && !connectorsVal.all) {
|
||||
// no connectors chosen
|
||||
return Triple(Resource.success(emptyList()), null, null)
|
||||
@@ -300,7 +320,7 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
val connectors = formatMultipleChoice(connectorsVal)
|
||||
val filteredConnectors = if (connectorsVal.all) null else connectorsVal.values
|
||||
|
||||
val chargeCardsVal = getMultipleChoiceValue(filters, "chargecards")
|
||||
val chargeCardsVal = filters.getMultipleChoiceValue("chargecards")
|
||||
if (chargeCardsVal.values.isEmpty() && !chargeCardsVal.all) {
|
||||
// no chargeCards chosen
|
||||
return Triple(Resource.success(emptyList()), filteredConnectors, null)
|
||||
@@ -309,14 +329,14 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
val filteredChargeCards =
|
||||
if (chargeCardsVal.all) null else chargeCardsVal.values.map { it.toLong() }.toSet()
|
||||
|
||||
val networksVal = getMultipleChoiceValue(filters, "networks")
|
||||
val networksVal = filters.getMultipleChoiceValue("networks")
|
||||
if (networksVal.values.isEmpty() && !networksVal.all) {
|
||||
// no networks chosen
|
||||
return Triple(Resource.success(emptyList()), filteredConnectors, filteredChargeCards)
|
||||
}
|
||||
val networks = formatMultipleChoice(networksVal)
|
||||
|
||||
val categoriesVal = getMultipleChoiceValue(filters, "categories")
|
||||
val categoriesVal = filters.getMultipleChoiceValue("categories")
|
||||
if (categoriesVal.values.isEmpty() && !categoriesVal.all) {
|
||||
// no categories chosen
|
||||
return Triple(Resource.success(emptyList()), filteredConnectors, filteredChargeCards)
|
||||
@@ -398,26 +418,6 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
private fun formatMultipleChoice(connectorsVal: MultipleChoiceFilterValue) =
|
||||
if (connectorsVal.all) null else connectorsVal.values.joinToString(",")
|
||||
|
||||
private fun getBooleanValue(
|
||||
filters: List<FilterWithValue<out FilterValue>>,
|
||||
key: String
|
||||
) = (filters.find { it.value.key == key }!!.value as BooleanFilterValue).value
|
||||
|
||||
private fun getSliderValue(
|
||||
filters: List<FilterWithValue<out FilterValue>>,
|
||||
key: String
|
||||
) = (filters.find { it.value.key == key }!!.value as SliderFilterValue).value
|
||||
|
||||
private fun getMultipleChoiceFilter(
|
||||
filters: List<FilterWithValue<out FilterValue>>,
|
||||
key: String
|
||||
) = filters.find { it.value.key == key }!!.filter as MultipleChoiceFilter
|
||||
|
||||
private fun getMultipleChoiceValue(
|
||||
filters: List<FilterWithValue<out FilterValue>>,
|
||||
key: String
|
||||
) = filters.find { it.value.key == key }!!.value as MultipleChoiceFilterValue
|
||||
|
||||
private suspend fun loadAvailability(charger: ChargeLocation) {
|
||||
availability.value = Resource.loading(null)
|
||||
availability.value = getAvailability(charger)
|
||||
@@ -425,24 +425,41 @@ class MapViewModel(application: Application, geApiKey: String) : AndroidViewMode
|
||||
|
||||
private fun loadChargerDetails(charger: ChargeLocation) {
|
||||
chargerDetails.value = Resource.loading(null)
|
||||
api.getChargepointDetail(charger.id).enqueue(object :
|
||||
Callback<ChargepointList> {
|
||||
override fun onFailure(call: Call<ChargepointList>, t: Throwable) {
|
||||
chargerDetails.value = Resource.error(t.message, null)
|
||||
t.printStackTrace()
|
||||
}
|
||||
|
||||
override fun onResponse(
|
||||
call: Call<ChargepointList>,
|
||||
response: Response<ChargepointList>
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val response = api.getChargepointDetail(charger.id)
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
chargerDetails.value = Resource.error(response.message(), null)
|
||||
} else {
|
||||
chargerDetails.value =
|
||||
Resource.success(response.body()!!.chargelocations[0] as ChargeLocation)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
chargerDetails.value = Resource.error(e.message, null)
|
||||
e.printStackTrace()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun loadChargerById(chargerId: Long) {
|
||||
chargerDetails.value = Resource.loading(null)
|
||||
chargerSparse.value = null
|
||||
viewModelScope.launch {
|
||||
val response = api.getChargepointDetail(chargerId)
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
chargerSparse.value = null
|
||||
chargerDetails.value = Resource.error(response.message(), null)
|
||||
} else {
|
||||
val chargers = response.body()!!.chargelocations
|
||||
if (chargers.isNotEmpty()) {
|
||||
val charger = chargers[0] as ChargeLocation
|
||||
chargerDetails.value =
|
||||
Resource.success(charger)
|
||||
chargerSparse.value = charger} else {
|
||||
chargerDetails.value = Resource.error("not found", null)
|
||||
chargerSparse.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceCar
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceTariff
|
||||
import java.io.IOException
|
||||
|
||||
class SettingsViewModel(application: Application, chargepriceApiKey: String) :
|
||||
AndroidViewModel(application) {
|
||||
private var api = ChargepriceApi.create(chargepriceApiKey)
|
||||
|
||||
val vehicles: MutableLiveData<Resource<List<ChargepriceCar>>> by lazy {
|
||||
MutableLiveData<Resource<List<ChargepriceCar>>>().apply {
|
||||
value = Resource.loading(null)
|
||||
loadVehicles()
|
||||
}
|
||||
}
|
||||
|
||||
val tariffs: MutableLiveData<Resource<List<ChargepriceTariff>>> by lazy {
|
||||
MutableLiveData<Resource<List<ChargepriceTariff>>>().apply {
|
||||
value = Resource.loading(null)
|
||||
loadTariffs()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVehicles() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = api.getVehicles()
|
||||
vehicles.value = Resource.success(result)
|
||||
} catch (e: IOException) {
|
||||
vehicles.value = Resource.error(e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadTariffs() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = api.getTariffs()
|
||||
tariffs.value = Resource.success(result)
|
||||
} catch (e: IOException) {
|
||||
tariffs.value = Resource.error(e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,10 @@ package net.vonforst.evmap.viewmodel
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.lifecycle.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
|
||||
@@ -63,4 +67,27 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> throttleLatest(
|
||||
skipMs: Long = 300L,
|
||||
coroutineScope: CoroutineScope,
|
||||
destinationFunction: suspend (T) -> Unit
|
||||
): (T) -> Unit {
|
||||
var throttleJob: Job? = null
|
||||
var waitingParam: T? = null
|
||||
return { param: T ->
|
||||
if (throttleJob?.isCompleted != false) {
|
||||
throttleJob = coroutineScope.launch {
|
||||
destinationFunction(param)
|
||||
delay(skipMs)
|
||||
waitingParam?.let { wParam ->
|
||||
waitingParam = null
|
||||
destinationFunction(wParam)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
waitingParam = param
|
||||
}
|
||||
}
|
||||
}
|
||||
17
app/src/main/res/anim/chargeprice_dialog_enter.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<scale
|
||||
android:duration="300"
|
||||
android:fromXScale="0.7"
|
||||
android:fromYScale="0.2"
|
||||
android:toXScale="1.0"
|
||||
android:toYScale="1.0"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
<alpha
|
||||
android:duration="250"
|
||||
android:fromAlpha="0.1"
|
||||
android:toAlpha="1.0"
|
||||
android:interpolator="@android:anim/decelerate_interpolator" />
|
||||
</set>
|
||||
17
app/src/main/res/anim/chargeprice_dialog_exit.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<scale
|
||||
android:duration="250"
|
||||
android:fromXScale="1.0"
|
||||
android:fromYScale="1.0"
|
||||
android:toXScale="0.7"
|
||||
android:toYScale="0.2"
|
||||
android:pivotX="50%"
|
||||
android:pivotY="50%"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
<alpha
|
||||
android:duration="250"
|
||||
android:fromAlpha="1.0"
|
||||
android:toAlpha="0.1"
|
||||
android:interpolator="@android:anim/accelerate_interpolator" />
|
||||
</set>
|
||||
BIN
app/src/main/res/drawable-nodpi/android_auto_screenshot.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
42
app/src/main/res/drawable/android_auto.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<vector android:height="45.9264dp"
|
||||
android:viewportHeight="480"
|
||||
android:viewportWidth="501.334"
|
||||
android:width="48dp"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#03a9f4"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m32,416c-17.68,0 -32,-14.32 -32,-32 0,-5.814 1.547,-11.28 4.267,-15.974l202.667,-352c5.52,-9.573 15.866,-16.026 27.733,-16.026s22.213,6.453 27.733,16.026l202.667,352c2.72,4.694 4.267,10.16 4.267,15.974 0,17.68 -14.32,32 -32,32z" />
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m234.667,149.333v266.667h266.667z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="459.19528"
|
||||
android:endY="410.17865"
|
||||
android:startX="177.41093"
|
||||
android:startY="250.14912"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#331A237E"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#051A237E"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#039be5"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m206.934,16.026 l-202.667,352c-2.72,4.694 -4.267,10.16 -4.267,15.974 0,17.68 14.32,32 32,32h202.667v-416c-11.867,0 -22.213,6.453 -27.733,16.026z" />
|
||||
<path
|
||||
android:fillColor="#f1f1f1"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m234.667,149.333 l181.333,320 -10.666,10.667 -170.667,-64 -170.667,64 -10.666,-10.667z" />
|
||||
<path
|
||||
android:fillColor="#e1e1e1"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="m234.667,149.333 l181.333,320 -10.666,10.667 -170.667,-64" />
|
||||
</vector>
|
||||
15
app/src/main/res/drawable/button_outline.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_checked="true">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke android:color="?attr/colorPrimary" android:width="1dp" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:state_checked="false">
|
||||
<shape android:shape="rectangle">
|
||||
<stroke android:color="#1F000000" android:width="1dp" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
12
app/src/main/res/drawable/circle_bg_logo.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item>
|
||||
<shape android:shape="oval">
|
||||
<solid android:color="#66FFFFFF" />
|
||||
<size
|
||||
android:height="24dp"
|
||||
android:width="24dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="?attr/controlBackground" />
|
||||
</layer-list>
|
||||
10
app/src/main/res/drawable/ic_chargeprice_alert.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector android:height="20dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="20dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_chargeprice_info.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector android:height="20dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="20dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_chargeprice_lock.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector android:height="16dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="16dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_chargeprice_star.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector android:height="16dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="16dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_close.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="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_navigation.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="M12,2L4.5,20.29l0.71,0.71L12,18l6.79,3 0.71,-0.71z" />
|
||||
</vector>
|
||||
149
app/src/main/res/drawable/ic_powered_by_chargeprice.xml
Normal file
@@ -0,0 +1,149 @@
|
||||
<vector android:height="26dp"
|
||||
android:viewportHeight="257.0819"
|
||||
android:viewportWidth="1289.0747"
|
||||
android:width="130.4dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m339.23,124.6q14.22,0 23.58,4.5 9.54,4.5 9.54,11.52 0,3.06 -1.98,5.58 -1.98,2.34 -5.04,2.34 -2.34,0 -3.78,-0.72 -1.26,-0.72 -3.6,-2.34 -1.08,-1.08 -3.42,-2.52 -2.16,-1.08 -6.12,-1.8 -3.96,-0.72 -7.2,-0.72 -9.36,0 -16.56,4.32 -7.2,4.32 -11.16,12.06 -3.96,7.56 -3.96,16.92 0,9.54 3.78,17.1 3.96,7.56 10.98,11.88 7.02,4.32 16.02,4.32 9.36,0 15.12,-2.88 1.26,-0.72 3.42,-2.34 1.8,-1.44 3.06,-2.16 1.44,-0.72 3.42,-0.72 3.6,0 5.58,2.34 2.16,2.16 2.16,5.76 0,3.78 -4.86,7.56 -4.68,3.6 -12.78,5.94 -7.92,2.34 -17.1,2.34 -13.68,0 -24.12,-6.3 -10.44,-6.48 -16.2,-17.64 -5.58,-11.34 -5.58,-25.2 0,-13.86 5.94,-25.02 5.94,-11.34 16.56,-17.64 10.62,-6.48 24.3,-6.48z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m437.63,125.14q31.68,0 31.68,39.24l0,48.06q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.6,0 -6.12,-2.52 -2.34,-2.52 -2.34,-6.12l0,-48.06q0,-23.4 -19.8,-23.4 -10.62,0 -17.64,6.84 -7.02,6.66 -7.02,16.56l0,48.06q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.78,0 -6.12,-2.34 -2.34,-2.52 -2.34,-6.3l0,-115.92q0,-3.6 2.34,-6.12 2.52,-2.52 6.12,-2.52 3.78,0 6.12,2.52 2.52,2.52 2.52,6.12l0,45.54q4.5,-7.02 12.6,-11.88 8.1,-5.04 17.28,-5.04z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m571.21,125.5q3.78,0 6.12,2.52 2.52,2.34 2.52,6.3l0,78.12q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.78,0 -6.12,-2.34 -2.34,-2.52 -2.34,-6.3l0,-4.68q-4.68,6.3 -12.78,10.8 -8.1,4.32 -17.46,4.32 -12.24,0 -22.32,-6.3 -9.9,-6.3 -15.66,-17.46 -5.58,-11.34 -5.58,-25.38 0,-14.04 5.58,-25.2 5.76,-11.34 15.66,-17.64 9.9,-6.3 21.78,-6.3 9.54,0 17.64,3.96 8.28,3.96 13.14,10.08l0,-4.32q0,-3.78 2.34,-6.3 2.34,-2.52 6.12,-2.52zM534.49,207.04q8.46,0 14.94,-4.32 6.66,-4.32 10.26,-11.88 3.78,-7.56 3.78,-17.1 0,-9.36 -3.78,-16.92 -3.6,-7.56 -10.26,-11.88 -6.48,-4.5 -14.94,-4.5 -8.46,0 -15.12,4.32 -6.48,4.32 -10.26,11.88 -3.6,7.56 -3.6,17.1 0,9.54 3.6,17.1 3.78,7.56 10.26,11.88 6.66,4.32 15.12,4.32z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m652.08,124.6q4.68,0 8.1,2.52 3.42,2.34 3.42,5.94 0,4.32 -2.34,6.66 -2.16,2.16 -5.4,2.16 -1.62,0 -4.86,-1.08 -3.78,-1.26 -5.94,-1.26 -5.58,0 -10.98,3.96 -5.22,3.78 -8.64,10.62 -3.24,6.66 -3.24,14.94l0,43.38q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.78,0 -6.12,-2.34 -2.34,-2.52 -2.34,-6.3l0,-77.04q0,-3.6 2.34,-6.12 2.52,-2.52 6.12,-2.52 3.78,0 6.12,2.52 2.52,2.52 2.52,6.12l0,9.18q3.96,-8.82 11.88,-14.22 7.92,-5.58 18,-5.76z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m755.31,125.5q3.78,0 6.12,2.52 2.52,2.34 2.52,6.3l0,79.2q0,14.58 -6.3,24.3 -6.12,9.9 -16.74,14.58 -10.62,4.68 -23.94,4.68 -7.2,0 -16.92,-2.52 -9.54,-2.52 -12.24,-5.22 -5.58,-2.88 -5.58,-7.2 0,-1.08 0.72,-2.88 1.98,-4.5 6.66,-4.5 2.34,0 5.04,1.08 14.4,5.58 22.5,5.58 14.4,0 21.96,-7.02 7.74,-6.84 7.74,-18.9l0,-9.72q-3.78,7.02 -12.78,12.06 -8.82,5.04 -18.72,5.04 -12.42,0 -22.68,-6.3 -10.26,-6.3 -16.2,-17.46 -5.76,-11.34 -5.76,-25.38 0,-14.04 5.76,-25.2 5.94,-11.34 16.02,-17.64 10.26,-6.3 22.5,-6.3 9.9,0 18.36,4.5 8.64,4.5 13.5,10.98l0,-5.76q0,-3.78 2.34,-6.3 2.34,-2.52 6.12,-2.52zM717.33,207.04q8.82,0 15.66,-4.14 6.84,-4.32 10.62,-11.88 3.96,-7.74 3.96,-17.28 0,-9.54 -3.96,-17.1 -3.78,-7.56 -10.62,-11.88 -6.84,-4.32 -15.66,-4.32 -8.64,0 -15.48,4.32 -6.84,4.32 -10.8,12.06 -3.78,7.56 -3.78,16.92 0,9.36 3.78,17.1 3.96,7.56 10.8,11.88 6.84,4.32 15.48,4.32z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m868.61,170.32q-0.18,3.24 -2.7,5.58 -2.52,2.16 -5.94,2.16l-63.36,0q1.26,13.14 9.9,21.06 8.82,7.92 21.42,7.92 8.64,0 14.04,-2.52 5.4,-2.52 9.54,-6.48 2.7,-1.62 5.22,-1.62 3.06,0 5.04,2.16 2.16,2.16 2.16,5.04 0,3.78 -3.6,6.84 -5.22,5.22 -13.86,8.82 -8.64,3.6 -17.64,3.6 -14.58,0 -25.74,-6.12 -10.98,-6.12 -17.1,-17.1 -5.94,-10.98 -5.94,-24.84 0,-15.12 6.12,-26.46 6.3,-11.52 16.38,-17.64 10.26,-6.12 21.96,-6.12 11.52,0 21.6,5.94 10.08,5.94 16.2,16.38 6.12,10.44 6.3,23.4zM824.51,140.44q-10.08,0 -17.46,5.76 -7.38,5.58 -9.72,17.46l53.1,0l0,-1.44q-0.9,-9.54 -8.64,-15.66 -7.56,-6.12 -17.28,-6.12z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m935.8,125.14q12.24,0 22.14,6.3 9.9,6.12 15.48,17.28 5.76,11.16 5.76,25.2 0,14.04 -5.76,25.2 -5.58,10.98 -15.48,17.28 -9.9,6.3 -21.78,6.3 -9.36,0 -17.46,-4.14 -8.1,-4.14 -13.14,-10.08l0,39.96q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.6,0 -6.12,-2.52 -2.34,-2.34 -2.34,-6.12l0,-113.22q0,-3.78 2.34,-6.3 2.34,-2.52 6.12,-2.52 3.78,0 6.12,2.52 2.52,2.52 2.52,6.3l0,5.22q4.32,-6.3 12.6,-10.8 8.28,-4.5 17.64,-4.5zM933.82,206.86q8.28,0 14.94,-4.32 6.66,-4.32 10.26,-11.7 3.78,-7.56 3.78,-16.92 0,-9.36 -3.78,-16.74 -3.6,-7.56 -10.26,-11.88 -6.66,-4.32 -14.94,-4.32 -8.46,0 -15.12,4.32 -6.66,4.14 -10.44,11.7 -3.6,7.56 -3.6,16.92 0,9.36 3.6,16.92 3.78,7.56 10.44,11.88 6.66,4.14 15.12,4.14z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m1045.83,124.6q4.68,0 8.1,2.52 3.42,2.34 3.42,5.94 0,4.32 -2.34,6.66 -2.16,2.16 -5.4,2.16 -1.62,0 -4.86,-1.08 -3.78,-1.26 -5.94,-1.26 -5.58,0 -10.98,3.96 -5.22,3.78 -8.64,10.62 -3.24,6.66 -3.24,14.94l0,43.38q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.78,0 -6.12,-2.34 -2.34,-2.52 -2.34,-6.3l0,-77.04q0,-3.6 2.34,-6.12 2.52,-2.52 6.12,-2.52 3.78,0 6.12,2.52 2.52,2.52 2.52,6.12l0,9.18q3.96,-8.82 11.88,-14.22 7.92,-5.58 18,-5.76z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m1089.23,212.44q0,3.6 -2.52,6.12 -2.34,2.52 -6.12,2.52 -3.6,0 -6.12,-2.52 -2.34,-2.52 -2.34,-6.12l0,-77.94q0,-3.6 2.34,-6.12 2.52,-2.52 6.12,-2.52 3.78,0 6.12,2.52 2.52,2.52 2.52,6.12zM1080.59,113.98q-5.22,0 -7.56,-1.8 -2.16,-1.98 -2.16,-6.12l0,-2.88q0,-4.32 2.34,-6.12 2.52,-1.8 7.56,-1.8 5.04,0 7.2,1.98 2.34,1.8 2.34,5.94l0,2.88q0,4.32 -2.34,6.12 -2.34,1.8 -7.38,1.8z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m1154.85,124.6q14.22,0 23.58,4.5 9.54,4.5 9.54,11.52 0,3.06 -1.98,5.58 -1.98,2.34 -5.04,2.34 -2.34,0 -3.78,-0.72 -1.26,-0.72 -3.6,-2.34 -1.08,-1.08 -3.42,-2.52 -2.16,-1.08 -6.12,-1.8 -3.96,-0.72 -7.2,-0.72 -9.36,0 -16.56,4.32 -7.2,4.32 -11.16,12.06 -3.96,7.56 -3.96,16.92 0,9.54 3.78,17.1 3.96,7.56 10.98,11.88 7.02,4.32 16.02,4.32 9.36,0 15.12,-2.88 1.26,-0.72 3.42,-2.34 1.8,-1.44 3.06,-2.16 1.44,-0.72 3.42,-0.72 3.6,0 5.58,2.34 2.16,2.16 2.16,5.76 0,3.78 -4.86,7.56 -4.68,3.6 -12.78,5.94 -7.92,2.34 -17.1,2.34 -13.68,0 -24.12,-6.3 -10.44,-6.48 -16.2,-17.64 -5.58,-11.34 -5.58,-25.2 0,-13.86 5.94,-25.02 5.94,-11.34 16.56,-17.64 10.62,-6.48 24.3,-6.48z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m1289.07,170.32q-0.18,3.24 -2.7,5.58 -2.52,2.16 -5.94,2.16l-63.36,0q1.26,13.14 9.9,21.06 8.82,7.92 21.42,7.92 8.64,0 14.04,-2.52 5.4,-2.52 9.54,-6.48 2.7,-1.62 5.22,-1.62 3.06,0 5.04,2.16 2.16,2.16 2.16,5.04 0,3.78 -3.6,6.84 -5.22,5.22 -13.86,8.82 -8.64,3.6 -17.64,3.6 -14.58,0 -25.74,-6.12 -10.98,-6.12 -17.1,-17.1 -5.94,-10.98 -5.94,-24.84 0,-15.12 6.12,-26.46 6.3,-11.52 16.38,-17.64 10.26,-6.12 21.96,-6.12 11.52,0 21.6,5.94 10.08,5.94 16.2,16.38 6.12,10.44 6.3,23.4zM1244.97,140.44q-10.08,0 -17.46,5.76 -7.38,5.58 -9.72,17.46l53.1,0l0,-1.44q-0.9,-9.54 -8.64,-15.66 -7.56,-6.12 -17.28,-6.12z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m321.33,1q5.1,0 9.7,3.1 4.6,3 7.4,8.2 2.8,5.1 2.8,11.2 0,6 -2.8,11.2 -2.8,5.2 -7.4,8.3 -4.6,3 -9.7,3l-17.4,0l0,18.9q0,2.7 -1.6,4.4 -1.6,1.7 -4.2,1.7 -2.5,0 -4.1,-1.7 -1.6,-1.8 -1.6,-4.4l0,-57.8q0,-2.6 1.7,-4.3 1.8,-1.8 4.4,-1.8zM321.33,34.6q1.9,0 3.7,-1.6 1.9,-1.6 3,-4.1 1.2,-2.6 1.2,-5.4 0,-2.8 -1.2,-5.3 -1.1,-2.6 -3,-4.1 -1.8,-1.6 -3.7,-1.6l-17.4,0l0,22.1z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m417.18,36q0,9.9 -4.4,18.2 -4.4,8.2 -12.2,13 -7.7,4.8 -17.4,4.8 -9.7,0 -17.5,-4.8 -7.7,-4.8 -12.1,-13 -4.3,-8.3 -4.3,-18.2 0,-9.9 4.3,-18.1 4.4,-8.3 12.1,-13.1 7.8,-4.8 17.5,-4.8 9.7,0 17.4,4.8 7.8,4.8 12.2,13.1 4.4,8.2 4.4,18.1zM404.18,36q0,-6.7 -2.7,-12.1 -2.7,-5.5 -7.5,-8.7 -4.8,-3.2 -10.8,-3.2 -6.1,0 -10.9,3.2 -4.7,3.1 -7.4,8.6 -2.6,5.5 -2.6,12.2 0,6.7 2.6,12.2 2.7,5.5 7.4,8.7 4.8,3.1 10.9,3.1 6,0 10.8,-3.2 4.8,-3.2 7.5,-8.6 2.7,-5.5 2.7,-12.2z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m506.87,0.7q2.4,0 4.4,1.9 2.1,1.8 2.1,4.6 0,0.9 -0.3,2l-19.7,58q-0.6,1.7 -2.1,2.7 -1.5,1 -3.3,1.1 -1.8,0 -3.4,-1 -1.6,-1 -2.5,-2.9l-14.2,-32.3 -14.3,32.3q-0.9,1.9 -2.5,2.9 -1.6,1 -3.4,1 -1.8,-0.1 -3.3,-1.1 -1.5,-1 -2.1,-2.7l-19.7,-58q-0.3,-1.1 -0.3,-2 0,-2.8 2,-4.6 2.1,-1.9 4.6,-1.9 2,0 3.6,1.1 1.6,1 2.2,2.8l14.9,45.2 13,-31.2q0.8,-1.8 2.3,-2.8 1.5,-1.1 3.4,-1 1.9,-0.1 3.3,1 1.5,1 2.3,2.8l12.3,30.9 14.8,-44.9q0.6,-1.8 2.2,-2.8 1.7,-1.1 3.7,-1.1z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m562.11,59.5q2.6,0 4.3,1.8 1.8,1.7 1.8,4 0,2.5 -1.8,4.1 -1.7,1.6 -4.3,1.6l-33.5,0q-2.6,0 -4.4,-1.7 -1.7,-1.8 -1.7,-4.4l0,-57.8q0,-2.6 1.7,-4.3 1.8,-1.8 4.4,-1.8l33.5,0q2.6,0 4.3,1.7 1.8,1.6 1.8,4.2 0,2.5 -1.7,4.1 -1.7,1.5 -4.4,1.5l-27.1,0l0,17l22.6,0q2.6,0 4.3,1.7 1.8,1.6 1.8,4.2 0,2.5 -1.7,4.1 -1.7,1.5 -4.4,1.5l-22.6,0l0,18.5z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m634.63,61.2q1.3,0.8 2,2.1 0.8,1.3 0.8,2.7 0,1.8 -1.2,3.3 -1.5,1.8 -4.6,1.8 -2.4,0 -4.4,-1.1 -7.2,-4.1 -7.2,-16.7 0,-3.6 -2.4,-5.7 -2.3,-2.1 -6.7,-2.1L592.23,45.5l0,19.4q0,2.7 -1.5,4.4 -1.4,1.7 -3.8,1.7 -2.9,0 -5.1,-1.7 -2.1,-1.8 -2.1,-4.4l0,-57.8q0,-2.6 1.7,-4.3 1.8,-1.8 4.4,-1.8l28.8,0q5.2,0 9.8,2.8 4.6,2.8 7.3,7.7 2.8,4.9 2.8,11 0,5 -2.7,9.8 -2.7,4.7 -7,7.5 6.3,4.4 6.9,11.8 0.3,1.6 0.3,3.1 0.4,3.1 0.8,4.5 0.4,1.3 1.8,2zM614.13,35.2q1.8,0 3.5,-1.7 1.7,-1.7 2.8,-4.5 1.1,-2.9 1.1,-6.2 0,-2.8 -1.1,-5.1 -1.1,-2.4 -2.8,-3.8 -1.7,-1.4 -3.5,-1.4l-21.9,0l0,22.7z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m688.08,59.5q2.6,0 4.3,1.8 1.8,1.7 1.8,4 0,2.5 -1.8,4.1 -1.7,1.6 -4.3,1.6l-33.5,0q-2.6,0 -4.4,-1.7 -1.7,-1.8 -1.7,-4.4l0,-57.8q0,-2.6 1.7,-4.3 1.8,-1.8 4.4,-1.8l33.5,0q2.6,0 4.3,1.7 1.8,1.6 1.8,4.2 0,2.5 -1.7,4.1 -1.7,1.5 -4.4,1.5l-27.1,0l0,17l22.6,0q2.6,0 4.3,1.7 1.8,1.6 1.8,4.2 0,2.5 -1.7,4.1 -1.7,1.5 -4.4,1.5l-22.6,0l0,18.5z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m735.71,1q9.4,0 16.1,4.7 6.8,4.6 10.3,12.6 3.6,7.9 3.6,17.7 0,9.8 -3.6,17.8 -3.5,7.9 -10.3,12.6 -6.7,4.6 -16.1,4.6l-23.9,0q-2.6,0 -4.4,-1.7 -1.7,-1.8 -1.7,-4.4l0,-57.8q0,-2.6 1.7,-4.3 1.8,-1.8 4.4,-1.8zM734.71,59.5q9,0 13.5,-6.6 4.5,-6.7 4.5,-16.9 0,-10.2 -4.6,-16.8 -4.5,-6.7 -13.4,-6.7l-16.5,0l0,47z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m847.12,32.2q5.3,2.1 8.6,6.4 3.4,4.3 3.4,11.1 0,11.9 -6.8,16.6 -6.8,4.7 -16.2,4.7l-24.9,0q-2.6,0 -4.4,-1.7 -1.7,-1.8 -1.7,-4.4l0,-57.8q0,-2.6 1.7,-4.3 1.8,-1.8 4.4,-1.8l25.2,0q19,0 19,17.8 0,4.5 -2.2,8 -2.1,3.4 -6.1,5.4zM842.42,21q0,-4.1 -2.1,-6.1 -2,-2.1 -5.7,-2.1l-16.5,0l0,15.6l16.8,0q3,0 5.2,-2 2.3,-2 2.3,-5.4zM836.12,59.5q4.7,0 7.3,-2.5 2.7,-2.5 2.7,-7.3 0,-5.9 -3.1,-7.7 -3.1,-1.8 -7.6,-1.8l-17.3,0l0,19.3z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m918.81,6.9q0,2 -1.1,3.7l-20.9,29.9l0,24.4q0,2.6 -1.7,4.4 -1.7,1.7 -4.1,1.7 -2.5,0 -4.3,-1.7 -1.7,-1.8 -1.7,-4.4l0,-25.8l-20.8,-27.6q-1.8,-2.4 -1.8,-4.7 0,-2.6 2,-4.3 2.1,-1.8 4.4,-1.8 2.8,0 4.9,2.8l17.6,24.3 16.5,-24.1q2.1,-3 5,-3 2.4,0 4.2,1.8 1.8,1.8 1.8,4.4z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="1" />
|
||||
<path
|
||||
android:fillColor="#000016"
|
||||
android:pathData="m246.94,173.65 l-11.5,-6.03 10.02,-5.24c3.41,-1.78 3.42,-4.71 0.01,-6.5l-10.76,-5.65 10.76,-5.62c3.41,-1.79 3.42,-4.71 0.01,-6.5L159.4,92.94c-3.41,-1.79 -9,-1.81 -12.42,-0.04L2.56,167.49c-3.42,1.77 -3.42,4.65 0.01,6.41l11.02,5.66 -11.02,5.69c-3.42,1.77 -3.42,4.65 0.01,6.41l11.76,6.04 -10.29,5.31c-3.42,1.77 -3.42,4.65 0.01,6.41l88.01,45.22c3.43,1.76 9.02,1.74 12.43,-0.04l142.46,-74.47c3.41,-1.78 3.41,-4.71 0,-6.5zM153.91,115.02 L132.31,139.92c-1.08,1.25 -0.76,2.88 0.7,3.64l17.18,8.83c1.47,0.75 1.47,1.99 0,2.75l-53.92,27.85c-1.47,0.76 -1.78,0.36 -0.7,-0.89l21.59,-24.9c1.08,-1.25 0.77,-2.88 -0.7,-3.64l-17.18,-8.83c-1.47,-0.75 -1.47,-1.99 0,-2.75l53.92,-27.85c1.47,-0.76 1.78,-0.36 0.7,0.89z" />
|
||||
</vector>
|
||||
10
app/src/main/res/drawable/ic_verified.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="M23,12l-2.44,-2.79l0.34,-3.69l-3.61,-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7l3.61,0.82L8.6,22.5l3.4,-1.47l3.4,1.46l1.89,-3.19l3.61,-0.82l-0.34,-3.69L23,12zM10.09,16.72l-3.8,-3.81l1.48,-1.48l2.32,2.33l5.85,-5.87l1.48,1.48L10.09,16.72z" />
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/my_tariff_background.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/chip_background" />
|
||||
<item android:drawable="?selectableItemBackground" />
|
||||
</layer-list>
|
||||
6
app/src/main/res/drawable/rounded_rect_16dp.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="?android:colorBackground" />
|
||||
<corners android:radius="16dp" />
|
||||
</shape>
|
||||
@@ -35,6 +35,10 @@
|
||||
name="availability"
|
||||
type="Resource<ChargeLocationStatus>" />
|
||||
|
||||
<variable
|
||||
name="filteredAvailability"
|
||||
type="Resource<ChargeLocationStatus>" />
|
||||
|
||||
<variable
|
||||
name="chargeCards"
|
||||
type="java.util.Map<Long, ChargeCard>" />
|
||||
@@ -66,14 +70,16 @@
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:maxLines="@{expanded ? 3 : 1}"
|
||||
android:text="@{charger.data.name}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintEnd_toStartOf="@+id/txtAvailability"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Parkhaus" />
|
||||
@@ -116,12 +122,12 @@
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:padding="2dp"
|
||||
android:text="@{String.format("%s/%d", BindingAdaptersKt.availabilityText(BindingAdaptersKt.flatten(availability.data.status.values())), charger.data.totalChargepoints)}"
|
||||
android:text="@{String.format("%s/%d", BindingAdaptersKt.availabilityText(BindingAdaptersKt.flatten(filteredAvailability.data.status.values())), filteredAvailability.data.totalChargepoints)}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="@android:color/white"
|
||||
app:backgroundTintAvailability="@{BindingAdaptersKt.flatten(availability.data.status.values())}"
|
||||
app:goneUnlessAnimated="@{availability.data != null && !expanded}"
|
||||
app:goneUnless="@{availability.data != null}"
|
||||
app:invisibleUnlessAnimated="@{availability.data != null && !expanded}"
|
||||
app:invisibleUnless="@{availability.data != null}"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="@+id/txtName"
|
||||
tools:backgroundTint="@color/available"
|
||||
@@ -308,6 +314,39 @@
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView13" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgVerified"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:contentDescription="@string/verified"
|
||||
android:tooltipText="@string/verified_desc"
|
||||
app:goneUnless="@{ charger.data.verified }"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/txtName"
|
||||
app:layout_constraintStart_toEndOf="@+id/imgFaultReport"
|
||||
app:layout_constraintTop_toTopOf="@+id/txtName"
|
||||
app:srcCompat="@drawable/ic_verified"
|
||||
app:tint="@color/available"
|
||||
tools:targetApi="o" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgFaultReport"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:contentDescription="@string/fault_report"
|
||||
android:tooltipText="@string/fault_report"
|
||||
app:goneUnless="@{ charger.data.faultReport != null }"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/txtName"
|
||||
app:layout_constraintStart_toEndOf="@+id/txtName"
|
||||
app:layout_constraintTop_toTopOf="@+id/txtName"
|
||||
app:srcCompat="@drawable/ic_map_marker_fault"
|
||||
tools:targetApi="o" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
93
app/src/main/res/layout/dialog_update_060_androidauto.xml
Normal file
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:maxWidth="200dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/linearLayout4"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/topPanel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="88dp"
|
||||
android:background="@color/android_auto_accent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView4"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_bg_logo"
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/android_auto" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcomeTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/update_060_androidauto_title"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/topPanel" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcomeText1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/update_060_androidauto_text"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/welcomeTitle" />
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/welcomeText1"
|
||||
app:srcCompat="@drawable/android_auto_screenshot" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnOk"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton.Dialog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/ok" />
|
||||
|
||||
</LinearLayout>
|
||||
187
app/src/main/res/layout/fragment_chargeprice.xml
Normal file
@@ -0,0 +1,187 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="net.vonforst.evmap.viewmodel.ChargepriceViewModel" />
|
||||
<import type="net.vonforst.evmap.viewmodel.Status" />
|
||||
|
||||
<variable
|
||||
name="vm"
|
||||
type="ChargepriceViewModel" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/linearLayout5"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<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="?android:actionBarSize">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgChargepriceLogo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/powered_by_chargeprice"
|
||||
android:focusable="true"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:layout_gravity="right"
|
||||
app:srcCompat="@drawable/ic_powered_by_chargeprice"
|
||||
app:tint="?android:textColorPrimary"
|
||||
tools:ignore="RtlSymmetry" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/chargeprice_select_connector"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
|
||||
android:textColor="?colorPrimary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_container" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/connectors_list"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:data="@{vm.charger.chargepointsMerged}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView"
|
||||
tools:itemCount="3"
|
||||
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
tools:listitem="@layout/item_connector_button"
|
||||
tools:orientation="horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@{String.format(@string/chargeprice_battery_range, vm.batteryRange[0], vm.batteryRange[1])}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
|
||||
android:textColor="?colorPrimary"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/connectors_list"
|
||||
tools:text="Charge from 20% to 80%" />
|
||||
|
||||
<com.google.android.material.slider.RangeSlider
|
||||
android:id="@+id/battery_range"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:valueFrom="0.0"
|
||||
android:valueTo="100.0"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView2"
|
||||
app:values="@={vm.batteryRange}" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/charge_prices_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:data="@{vm.chargePricesForChargepoint.data}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/battery_range"
|
||||
tools:itemCount="3"
|
||||
tools:listitem="@layout/item_chargeprice" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView8"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/chargeprice_no_tariffs_found"
|
||||
app:goneUnless="@{vm.chargePricesForChargepoint.status == Status.SUCCESS && vm.chargePricesForChargepoint.data.size() == 0}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/charge_prices_list"
|
||||
app:layout_constraintTop_toTopOf="@+id/charge_prices_list" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView9"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/chargeprice_no_compatible_connectors"
|
||||
app:goneUnless="@{vm.noCompatibleConnectors}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/charge_prices_list"
|
||||
app:layout_constraintTop_toTopOf="@+id/charge_prices_list" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/chargeprice_select_car_first"
|
||||
app:goneUnless="@{vm.vehicle == null}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnSettings"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/charge_prices_list"
|
||||
app:layout_constraintTop_toTopOf="@+id/charge_prices_list"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar5"
|
||||
style="?android:attr/progressBarStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:goneUnless="@{vm.chargePricesForChargepoint.status == Status.LOADING}"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/charge_prices_list"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/charge_prices_list"
|
||||
app:layout_constraintTop_toTopOf="@+id/charge_prices_list" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSettings"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/settings"
|
||||
app:goneUnless="@{vm.vehicle == null}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView3" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
@@ -63,7 +63,8 @@
|
||||
<TextView
|
||||
android:id="@+id/search"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/search"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
@@ -139,6 +140,7 @@
|
||||
layout="@layout/detail_view"
|
||||
app:charger="@{vm.charger}"
|
||||
app:availability="@{vm.availability}"
|
||||
app:filteredAvailability="@{vm.filteredAvailability}"
|
||||
app:chargeCards="@{vm.chargeCardMap}"
|
||||
app:filteredChargeCards="@{vm.filteredChargeCards}"
|
||||
app:distance="@{vm.chargerDistance}"
|
||||
|
||||
164
app/src/main/res/layout/item_chargeprice.xml
Normal file
@@ -0,0 +1,164 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="net.vonforst.evmap.api.chargeprice.ChargePrice" />
|
||||
|
||||
<import type="net.vonforst.evmap.api.chargeprice.ChargepriceChargepointMeta" />
|
||||
|
||||
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
|
||||
|
||||
<import type="java.util.Set" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="ChargePrice" />
|
||||
|
||||
<variable
|
||||
name="meta"
|
||||
type="ChargepriceChargepointMeta" />
|
||||
|
||||
<variable
|
||||
name="myTariffs"
|
||||
type="Set<String>" />
|
||||
|
||||
<variable
|
||||
name="myTariffsAll"
|
||||
type="Boolean" />
|
||||
</data>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:myTariffsBackground="@{!myTariffsAll && myTariffs.contains(item.tariff.get().id)}">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtTariff"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{item.tariffName}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:layout_constraintBottom_toTopOf="@+id/txtProvider"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="CheapCharge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtProvider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{item.provider}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:goneUnless="@{!item.tariffName.startsWith(item.provider)}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/rvTags"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
|
||||
tools:text="Cheap Charging Co." />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/rvTags"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
app:data="@{item.tags}"
|
||||
app:layout_constraintBottom_toTopOf="@+id/txtProviderCustomerTariff"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtProvider"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/item_chargeprice_tag" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtProviderCustomerTariff"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@string/chargeprice_provider_customer_tariff"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:goneUnless="@{item.providerCustomerTariff}"
|
||||
app:layout_constraintBottom_toTopOf="@id/txtMonthlyFee"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/rvTags" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtMonthlyFee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{item.formatMonthlyFees(context)}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:goneUnless="@{item.totalMonthlyFee > 0 || item.monthlyMinSales > 0}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtProviderCustomerTariff"
|
||||
tools:text="Base fee 1 €/month" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtPrice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:gravity="end"
|
||||
android:text="@{String.format(@string/charge_price_format, item.chargepointPrices.get(0).price, BindingAdaptersKt.currency(item.currency))}"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/txtAveragePrice"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="1,50 €" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtAveragePrice"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:gravity="end"
|
||||
android:text="@{String.format(item.chargepointPrices.get(0).priceDistribution.isOnlyKwh ? @string/charge_price_kwh_format : @string/charge_price_average_format, item.chargepointPrices.get(0).price / meta.energy, BindingAdaptersKt.currency(item.currency))}"
|
||||
app:goneUnless="@{item.chargepointPrices.get(0).price > 0}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:layout_constraintBottom_toTopOf="@id/txtPriceDetails"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtPrice"
|
||||
tools:text="⌀ 0,29 €/kWh" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtPriceDetails"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:gravity="end"
|
||||
android:text="@{item.chargepointPrices.get(0).formatDistribution(context)}"
|
||||
app:goneUnless="@{!item.chargepointPrices.get(0).formatDistribution(context).empty}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline5"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtAveragePrice"
|
||||
tools:text="pro kWh + ab 4h Blockiergeb." />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.65" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</layout>
|
||||
40
app/src/main/res/layout/item_chargeprice_tag.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="net.vonforst.evmap.api.chargeprice.ChargepriceTag" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="ChargepriceTag" />
|
||||
</data>
|
||||
|
||||
<net.vonforst.evmap.ui.BalancedBreakingTextView
|
||||
android:id="@+id/rvTags"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:background="@drawable/rounded_rect_16dp"
|
||||
android:maxLines="3"
|
||||
android:text="@{item.text}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="?android:textColorPrimary"
|
||||
android:theme="@style/ThemeOverlay.MaterialComponents.Dark"
|
||||
android:gravity="center_vertical"
|
||||
android:drawablePadding="4dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="3dp"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:drawableTint="?android:textColorPrimary"
|
||||
android:breakStrategy="balanced"
|
||||
app:chargepriceTagColor="@{item.kind}"
|
||||
app:chargepriceTagIcon="@{item.kind}"
|
||||
tools:backgroundTint="@color/chargeprice_alert"
|
||||
tools:drawableLeft="@drawable/ic_chargeprice_alert"
|
||||
tools:text="Only for drivers of blue cars" />
|
||||
</layout>
|
||||
78
app/src/main/res/layout/item_connector_button.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="net.vonforst.evmap.api.goingelectric.Chargepoint" />
|
||||
|
||||
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="Chargepoint" />
|
||||
|
||||
<variable
|
||||
name="enabled"
|
||||
type="boolean" />
|
||||
</data>
|
||||
|
||||
<net.vonforst.evmap.ui.CheckableConstraintLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/button_outline"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:clickable="@{enabled}"
|
||||
android:focusable="true"
|
||||
android:layout_margin="4dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:tintMode="src_in"
|
||||
android:contentDescription="@{item.type}"
|
||||
app:connectorIcon="@{item.type}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
|
||||
tools:tint="?colorControlNormal"
|
||||
tools:srcCompat="@drawable/ic_connector_typ2" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView5"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="38dp"
|
||||
android:layout_marginTop="38dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{String.format("× %d", item.count)}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/imageView"
|
||||
app:layout_constraintTop_toTopOf="@+id/imageView"
|
||||
tools:text="×99" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView6"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@{item.formatPower()}"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/imageView"
|
||||
tools:text="350 kW" />
|
||||
|
||||
</net.vonforst.evmap.ui.CheckableConstraintLayout>
|
||||
</layout>
|
||||
@@ -18,7 +18,7 @@
|
||||
app:selectableItemBackground="@{item.clickable}">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtTitle"
|
||||
android:id="@+id/txtTariff"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
@@ -48,7 +48,7 @@
|
||||
tools:srcCompat="@drawable/ic_address" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtContent"
|
||||
android:id="@+id/txtProvider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
@@ -60,8 +60,8 @@
|
||||
app:goneUnless="@{item.detailText != null}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
|
||||
tools:text="Lorem ipsum" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
app:selectableItemBackground="@{item.clickable}">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtTitle"
|
||||
android:id="@+id/txtTariff"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
@@ -54,7 +54,7 @@
|
||||
tools:srcCompat="@drawable/ic_address" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtContent"
|
||||
android:id="@+id/txtProvider"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
@@ -66,8 +66,8 @@
|
||||
app:goneUnless="@{item.detailText != null}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/expandToggle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtTariff"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
tools:text="Lorem ipsum" />
|
||||
|
||||
@@ -82,8 +82,8 @@
|
||||
app:goneUnless="@{expandToggle.checked}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtContent" />
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtProvider" />
|
||||
|
||||
<include
|
||||
android:id="@+id/hours_tue"
|
||||
@@ -96,7 +96,7 @@
|
||||
app:dayOfWeek="@{DayOfWeek.TUESDAY}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_mon" />
|
||||
|
||||
<include
|
||||
@@ -110,7 +110,7 @@
|
||||
app:dayOfWeek="@{DayOfWeek.WEDNESDAY}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_tue" />
|
||||
|
||||
<include
|
||||
@@ -124,7 +124,7 @@
|
||||
app:dayOfWeek="@{DayOfWeek.THURSDAY}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_wed" />
|
||||
|
||||
<include
|
||||
@@ -138,7 +138,7 @@
|
||||
app:dayOfWeek="@{DayOfWeek.FRIDAY}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_thu" />
|
||||
|
||||
<include
|
||||
@@ -152,7 +152,7 @@
|
||||
app:dayOfWeek="@{DayOfWeek.SATURDAY}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_fri" />
|
||||
|
||||
<include
|
||||
@@ -166,7 +166,7 @@
|
||||
app:dayOfWeek="@{DayOfWeek.SUNDAY}"
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_sat" />
|
||||
|
||||
<include
|
||||
@@ -182,7 +182,7 @@
|
||||
app:hours="@{item.hoursDays}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTitle"
|
||||
app:layout_constraintStart_toStartOf="@+id/txtTariff"
|
||||
app:layout_constraintTop_toBottomOf="@id/hours_sun" />
|
||||
|
||||
<ToggleButton
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="24dp">
|
||||
android:paddingTop="24dp"
|
||||
android:id="@+id/nav_header">
|
||||
|
||||
<include layout="@layout/app_logo" />
|
||||
</FrameLayout>
|
||||
10
app/src/main/res/menu/chargeprice.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_close"
|
||||
android:icon="@drawable/ic_close"
|
||||
android:title="@string/close"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
||||
@@ -31,9 +31,19 @@
|
||||
app:enterAnim="@anim/fragment_fade_enter"
|
||||
app:popEnterAnim="@anim/fragment_fade_enter"
|
||||
app:popExitAnim="@anim/fragment_fade_exit" />
|
||||
<action
|
||||
android:id="@+id/action_map_to_chargepriceFragment"
|
||||
app:destination="@id/chargeprice"
|
||||
app:exitAnim="@anim/fragment_fade_exit"
|
||||
app:enterAnim="@anim/fragment_fade_enter"
|
||||
app:popEnterAnim="@anim/fragment_fade_enter"
|
||||
app:popExitAnim="@anim/fragment_fade_exit" />
|
||||
<action
|
||||
android:id="@+id/action_map_to_welcome"
|
||||
app:destination="@id/welcome" />
|
||||
<action
|
||||
android:id="@+id/action_map_to_update_060_androidauto"
|
||||
app:destination="@id/update_060_androidauto" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/about"
|
||||
@@ -73,6 +83,19 @@
|
||||
android:name="net.vonforst.evmap.fragment.FilterProfilesFragment"
|
||||
android:label="@string/menu_manage_filter_profiles"
|
||||
tools:layout="@layout/fragment_filter_profiles" />
|
||||
<dialog
|
||||
android:id="@+id/chargeprice"
|
||||
android:name="net.vonforst.evmap.fragment.ChargepriceFragment"
|
||||
android:label="@string/chargeprice_title"
|
||||
tools:layout="@layout/fragment_chargeprice">
|
||||
<action
|
||||
android:id="@+id/action_chargeprice_to_settingsFragment"
|
||||
app:destination="@id/settings"
|
||||
app:exitAnim="@anim/fragment_fade_exit"
|
||||
app:enterAnim="@anim/fragment_fade_enter"
|
||||
app:popEnterAnim="@anim/fragment_fade_enter"
|
||||
app:popExitAnim="@anim/fragment_fade_exit" />
|
||||
</dialog>
|
||||
<fragment
|
||||
android:id="@+id/donate"
|
||||
android:name="net.vonforst.evmap.fragment.DonateFragment"
|
||||
@@ -83,6 +106,11 @@
|
||||
android:name="net.vonforst.evmap.fragment.WelcomeDialogFragment"
|
||||
android:label="@string/welcome_to_evmap"
|
||||
tools:layout="@layout/dialog_welcome" />
|
||||
<dialog
|
||||
android:id="@+id/update_060_androidauto"
|
||||
android:name="net.vonforst.evmap.fragment.updatedialogs.Update060AndroidAutoDialogFramgent"
|
||||
android:label="@string/welcome_to_evmap"
|
||||
tools:layout="@layout/dialog_update_060_androidauto" />
|
||||
<chrome
|
||||
android:id="@+id/report_new_charger"
|
||||
app:url="@string/report_new_charger_url" />
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
<string name="settings_ui">Oberfläche</string>
|
||||
<string name="settings_map">Karte</string>
|
||||
<string name="copyright">Copyright</string>
|
||||
<string name="copyright_summary">©2020 Johan von Forstner</string>
|
||||
<string name="copyright_summary">©2020–2021 Johan von Forstner</string>
|
||||
<string name="other">Sonstiges</string>
|
||||
<string name="privacy">Datenschutzerklärung</string>
|
||||
<string name="fav_add">Zu Favoriten hinzufügen</string>
|
||||
@@ -152,8 +152,61 @@
|
||||
<string name="deleted_filterprofile">„%s” gelöscht</string>
|
||||
<string name="undo">Rückgängig</string>
|
||||
<string name="rename">Umbenennen</string>
|
||||
<string name="charging_barrierfree">Ohne Vertrag / Registrierung nutzbar</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d kompatibler Ladetarif</item>
|
||||
<item quantity="other">%d kompatible Ladetarife</item>
|
||||
</plurals>
|
||||
<string name="navigate">Navigieren</string>
|
||||
<string name="verified">Verifiziert</string>
|
||||
<string name="verified_desc">Verifiziert von der GoingElectric.de Community – nicht zwangsläufig auch aktuell verfügbar.</string>
|
||||
<string name="update_060_androidauto_title">Neues Update: Android Auto</string>
|
||||
<string name="update_060_androidauto_text">Mit diesem neuen Update kannst du EVMap nutzen, um Ladestationen in der Nähe auf unterstützen Autos direkt aus Android Auto zu finden. Öffne einfach die EVMap-App aus dem Menü von Android Auto.</string>
|
||||
<string name="charge_price_format">%1$.2f %2$s</string>
|
||||
<string name="charge_price_average_format">⌀ %1$.2f %2$s/kWh</string>
|
||||
<string name="charge_price_kwh_format">%1$.2f %2$s/kWh</string>
|
||||
<string name="chargeprice_select_connector">Anschluss auswählen</string>
|
||||
<string name="chargeprice_provider_customer_tariff">Nur für Energiekunden</string>
|
||||
<string name="percent_format">%.0f%%</string>
|
||||
<string name="chargeprice_session_fee">Startgebühr</string>
|
||||
<string name="chargeprice_per_kwh">pro kWh</string>
|
||||
<string name="chargeprice_per_minute">pro min</string>
|
||||
<string name="chargeprice_blocking_fee">Blockiergeb. >%s</string>
|
||||
<string name="chargeprice_no_tariffs_found">Keine geeigneten Tarife für diese Ladestation bei Chargeprice.app gefunden.</string>
|
||||
<string name="powered_by_chargeprice">powered by Chargeprice</string>
|
||||
<string name="chargeprice_base_fee">Fixkosten: %1$.2f %2$s/Monat</string>
|
||||
<string name="chargeprice_min_spend">Mindestumsatz: %1$.2f %2$s/Monat</string>
|
||||
<string name="settings_chargeprice">Preisvergleich</string>
|
||||
<string name="pref_my_vehicle">Mein Fahrzeug</string>
|
||||
<string name="pref_chargeprice_no_base_fee">Nur Tarife ohne monatliche Gebühren</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs">Exklusive Energiekunden-Tarife anzeigen</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs_summary">Einige Anbieter bieten für ihre Kunden (z.B. Haushaltsstrom, Gas) günstigere Tarife an</string>
|
||||
<string name="chargeprice_select_car_first">Bitte wähle zuerst dein Auto in den Einstellungen aus.</string>
|
||||
<string name="chargeprice_battery_range">Laden von %1$.0f%% bis %2$.0f%%</string>
|
||||
<string name="edit_on_goingelectric_info">Falls hier nur eine leere Seite erscheint, logge dich bitte zuerst bei GoingElectric.de ein.</string>
|
||||
<string name="close">schließen</string>
|
||||
<string name="chargeprice_title">Preisvergleich</string>
|
||||
<string name="chargeprice_connection_error">Preise konnten nicht geladen werden</string>
|
||||
<string name="chargeprice_no_compatible_connectors">Keiner der Anschlüsse dieser Ladestation ist mit deinem Fahrzeug kompatibel.</string>
|
||||
<string name="pref_chargeprice_currency">Währung</string>
|
||||
<string name="pref_my_tariffs">Meine Tarife</string>
|
||||
<string name="chargeprice_all_tariffs_selected">alle Tarife ausgewählt</string>
|
||||
<string-array name="pref_chargeprice_currency_names">
|
||||
<item>Schweizer Franken (CHF)</item>
|
||||
<item>Tschechische Krone (CZK)</item>
|
||||
<item>Dänische Krone (DKK)</item>
|
||||
<item>Euro (EUR)</item>
|
||||
<item>Britisches Pfund (GBP)</item>
|
||||
<item>Kroatische Kuna (HRK)</item>
|
||||
<item>Ungarischer Forint (HUF)</item>
|
||||
<item>Isländische Krone (ISK)</item>
|
||||
<item>Norwegische Krone (NOK)</item>
|
||||
<item>Polnischer Złoty (PLN)</item>
|
||||
<item>Schwedische Krone (SEK)</item>
|
||||
<item>US-Dollar (USD)</item>
|
||||
</string-array>
|
||||
<plurals name="chargeprice_some_tariffs_selected">
|
||||
<item quantity="one">%d Tarif ausgewählt</item>
|
||||
<item quantity="other">%d Tarife ausgewählt</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -20,4 +20,32 @@
|
||||
<item>on</item>
|
||||
<item>off</item>
|
||||
</string-array>
|
||||
<string-array name="pref_chargeprice_currency_names">
|
||||
<item>Swiss franc (CHF)</item>
|
||||
<item>Czech koruna (CZK)</item>
|
||||
<item>Danish krone (DKK)</item>
|
||||
<item>Euro (EUR)</item>
|
||||
<item>Pound sterling (GBP)</item>
|
||||
<item>Croatian kuna (HRK)</item>
|
||||
<item>Hungarian forint (HUF)</item>
|
||||
<item>Icelandic króna (ISK)</item>
|
||||
<item>Norwegian krone (NOK)</item>
|
||||
<item>Polish złoty (PLN)</item>
|
||||
<item>Swedish krona (SEK)</item>
|
||||
<item>US dollar (USD)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_chargeprice_currency_values" donottranslate="true">
|
||||
<item>CHF</item>
|
||||
<item>CZK</item>
|
||||
<item>DKK</item>
|
||||
<item>EUR</item>
|
||||
<item>GBP</item>
|
||||
<item>HRK</item>
|
||||
<item>HUF</item>
|
||||
<item>ISK</item>
|
||||
<item>NOK</item>
|
||||
<item>PLN</item>
|
||||
<item>SEK</item>
|
||||
<item>USD</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -10,8 +10,15 @@
|
||||
<color name="charger_11kw">#9e9e9e</color>
|
||||
<color name="charger_low">#607d8b</color>
|
||||
<color name="available">#4caf50</color>
|
||||
<color name="charging">#00bcd4</color>
|
||||
<color name="unavailable">#f44336</color>
|
||||
<color name="unknown">#9e9e9e</color>
|
||||
<color name="status_bar_scrim">#C3000000</color>
|
||||
<color name="delete_red">#f44336</color>
|
||||
<color name="android_auto_accent">#039be5</color>
|
||||
<color name="chargeprice_alert">#DD2C00</color>
|
||||
<color name="chargeprice_info">#2979FF</color>
|
||||
<color name="chargeprice_lock">#546E7A</color>
|
||||
<color name="chargeprice_star">#00C853</color>
|
||||
<color name="chip_background">#1F000000</color>
|
||||
</resources>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<string name="settings_ui">User Interface</string>
|
||||
<string name="settings_map">Map</string>
|
||||
<string name="copyright">Copyright</string>
|
||||
<string name="copyright_summary">©2020 Johan von Forstner</string>
|
||||
<string name="copyright_summary">©2020–2021 Johan von Forstner</string>
|
||||
<string name="other">Other</string>
|
||||
<string name="privacy">Privacy Notice</string>
|
||||
<string name="fav_add">Add to favorites</string>
|
||||
@@ -151,8 +151,47 @@
|
||||
<string name="deleted_filterprofile">Deleted “%s”</string>
|
||||
<string name="undo">Undo</string>
|
||||
<string name="rename">Rename</string>
|
||||
<string name="charging_barrierfree">Usable without registration</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d compatible payment method</item>
|
||||
<item quantity="other">%d compatible payment methods</item>
|
||||
</plurals>
|
||||
<string name="navigate">Navigate</string>
|
||||
<string name="verified">verified</string>
|
||||
<string name="verified_desc">Charger verified by a member at the GoingElectric.de community — not necessarily working right now.</string>
|
||||
<string name="update_060_androidauto_title">New update: Android Auto</string>
|
||||
<string name="update_060_androidauto_text">With this new update, you can also use EVMap to find nearby chargers from within Android Auto on supported cars. Simply select the EVMap app in the Android Auto menu.</string>
|
||||
<string name="charge_price_format">%2$s%1$.2f</string>
|
||||
<string name="charge_price_average_format">⌀ %2$s%1$.2f/kWh</string>
|
||||
<string name="charge_price_kwh_format">%2$s%1$.2f/kWh</string>
|
||||
<string name="chargeprice_select_connector">Choose connector</string>
|
||||
<string name="chargeprice_provider_customer_tariff">Only for provider customers</string>
|
||||
<string name="edit_on_goingelectric_info">If only an empty page is showing here, please first log in to GoingElectric.de.</string>
|
||||
<string name="percent_format">%.0f%%</string>
|
||||
<string name="chargeprice_session_fee">session fee</string>
|
||||
<string name="chargeprice_per_kwh">per kWh</string>
|
||||
<string name="chargeprice_per_minute">per min</string>
|
||||
<string name="chargeprice_blocking_fee">Blocking fee >%s</string>
|
||||
<string name="chargeprice_no_tariffs_found">Chargeprice.app found no charging plans compatible with this charger.</string>
|
||||
<string name="powered_by_chargeprice">powered by Chargeprice</string>
|
||||
<string name="chargeprice_base_fee">Base fee: %2$s%1$.2f/month</string>
|
||||
<string name="chargeprice_min_spend">Minimum spend: %2$s%1$.2f/month</string>
|
||||
<string name="settings_chargeprice">Price comparison</string>
|
||||
<string name="pref_my_vehicle">My vehicle</string>
|
||||
<string name="pref_chargeprice_no_base_fee">Only show plans with no monthly fees</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs">Show customer-exclusive plans</string>
|
||||
<string name="chargeprice_select_car_first">Please first select your car model in the settings.</string>
|
||||
<string name="chargeprice_battery_range">Charge from %1$.0f%% to %2$.0f%%</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs_summary">Some providers offer cheaper plans exclusively to their customers (e.g., household electricity, gas)</string>
|
||||
<string name="close">close</string>
|
||||
<string name="chargeprice_title">Prices</string>
|
||||
<string name="chargeprice_connection_error">Could not load prices</string>
|
||||
<string name="chargeprice_no_compatible_connectors">None of the connectors on this charging station is compatible with your vehicle.</string>
|
||||
<string name="pref_chargeprice_currency">Currency</string>
|
||||
<string name="pref_my_tariffs">My charging plans</string>
|
||||
<string name="chargeprice_all_tariffs_selected">all plans selected</string>
|
||||
<plurals name="chargeprice_some_tariffs_selected">
|
||||
<item quantity="one">%d plan selected</item>
|
||||
<item quantity="other">%d plans selected</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
||||
@@ -26,4 +26,9 @@
|
||||
<item name="android:windowIsFloating">false</item>
|
||||
</style>
|
||||
|
||||
<style name="ChargepriceDialogAnimation">
|
||||
<item name="android:windowEnterAnimation">@anim/chargeprice_dialog_enter</item>
|
||||
<item name="android:windowExitAnimation">@anim/chargeprice_dialog_exit</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<PreferenceCategory android:title="@string/settings_ui">
|
||||
|
||||
<ListPreference
|
||||
android:key="language"
|
||||
android:title="@string/pref_language"
|
||||
@@ -38,4 +38,30 @@
|
||||
android:defaultValue="true" />
|
||||
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory android:title="@string/settings_chargeprice">
|
||||
<ListPreference
|
||||
android:key="chargeprice_my_vehicle"
|
||||
android:title="@string/pref_my_vehicle" />
|
||||
<net.vonforst.evmap.ui.MultiSelectDialogPreference
|
||||
android:key="chargeprice_my_tariffs"
|
||||
android:title="@string/pref_my_tariffs" />
|
||||
<ListPreference
|
||||
android:key="chargeprice_currency"
|
||||
android:title="@string/pref_chargeprice_currency"
|
||||
android:entries="@array/pref_chargeprice_currency_names"
|
||||
android:entryValues="@array/pref_chargeprice_currency_values"
|
||||
android:defaultValue="EUR"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
<CheckBoxPreference
|
||||
android:key="chargeprice_no_base_fee"
|
||||
android:title="@string/pref_chargeprice_no_base_fee"
|
||||
android:defaultValue="false"
|
||||
app:singleLineTitle="false" />
|
||||
<CheckBoxPreference
|
||||
android:key="chargeprice_show_provider_customer_tariffs"
|
||||
android:title="@string/pref_chargeprice_show_provider_customer_tariffs"
|
||||
android:summary="@string/pref_chargeprice_show_provider_customer_tariffs_summary"
|
||||
android:defaultValue="false"
|
||||
app:singleLineTitle="false" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
@@ -1,12 +0,0 @@
|
||||
package net.vonforst.evmap.api
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
class UtilsTest {
|
||||
@Test
|
||||
fun testDistanceBetween() {
|
||||
assertEquals(129412.71, distanceBetween(54.0, 9.0, 53.0, 8.0), 0.01)
|
||||
}
|
||||
}
|
||||
@@ -66,8 +66,7 @@ class NewMotionAvailabilityDetectorTest {
|
||||
@Test
|
||||
fun apiTest() {
|
||||
for (chargepoint in listOf(2105L, 18284L)) {
|
||||
val charger = api.getChargepointDetail(chargepoint)
|
||||
.execute().body()!!
|
||||
val charger = runBlocking { api.getChargepointDetail(chargepoint).body()!! }
|
||||
.chargelocations[0] as ChargeLocation
|
||||
println(charger)
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package net.vonforst.evmap.api.chargeprice
|
||||
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.vonforst.evmap.api.goingelectric.ChargeLocation
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApi
|
||||
import net.vonforst.evmap.okResponse
|
||||
import okhttp3.mockwebserver.Dispatcher
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import okhttp3.mockwebserver.RecordedRequest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.net.HttpURLConnection
|
||||
|
||||
class ChargepriceApiTest {
|
||||
val ge: GoingElectricApi
|
||||
val webServer = MockWebServer()
|
||||
val chargeprice: ChargepriceApi
|
||||
|
||||
init {
|
||||
webServer.start()
|
||||
|
||||
val apikey = ""
|
||||
val baseurl = webServer.url("/ge/").toString()
|
||||
ge = GoingElectricApi.create(apikey, baseurl)
|
||||
chargeprice = ChargepriceApi.create(
|
||||
apikey,
|
||||
webServer.url("/cp/").toString()
|
||||
)
|
||||
|
||||
webServer.dispatcher = object : Dispatcher() {
|
||||
val notFoundResponse = MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_FOUND)
|
||||
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
val segments = request.requestUrl!!.pathSegments
|
||||
val urlHead = segments.subList(0, 2).joinToString("/")
|
||||
when (urlHead) {
|
||||
"ge/chargepoints" -> {
|
||||
val id = request.requestUrl!!.queryParameter("ge_id")
|
||||
return okResponse("/chargers/$id.json")
|
||||
}
|
||||
"cp/charge_prices" -> {
|
||||
val body = request.body.readUtf8()
|
||||
return okResponse("/chargeprice/2105.json")
|
||||
}
|
||||
else -> return notFoundResponse
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readResource(s: String) =
|
||||
ChargepriceApiTest::class.java.getResource(s)?.readText()
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
@Test
|
||||
fun apiTest() {
|
||||
for (chargepoint in listOf(2105L, 18284L)) {
|
||||
val charger = runBlocking { ge.getChargepointDetail(chargepoint).body()!! }
|
||||
.chargelocations[0] as ChargeLocation
|
||||
println(charger)
|
||||
|
||||
runBlocking {
|
||||
val result = chargeprice.getChargePrices(
|
||||
ChargepriceRequest().apply {
|
||||
dataAdapter = "going_electric"
|
||||
station =
|
||||
ChargepriceStation.fromGoingelectric(charger, listOf("Typ2", "Schuko"))
|
||||
options = ChargepriceOptions(energy = 22.0, duration = 60)
|
||||
}, "en"
|
||||
)
|
||||
assertEquals(25, result.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ class GoingElectricApiTest {
|
||||
|
||||
@Test
|
||||
fun testLoadChargepointDetail() {
|
||||
val response = api.getChargepointDetail(2105).execute()
|
||||
val response = runBlocking { api.getChargepointDetail(2105) }
|
||||
assertTrue(response.isSuccessful)
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package net.vonforst.evmap.utils
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
|
||||
class LocationUtilsTest {
|
||||
@Test
|
||||
fun testDistanceBetween() {
|
||||
assertEquals(129521.08, distanceBetween(54.0, 9.0, 53.0, 8.0), 0.01)
|
||||
}
|
||||
}
|
||||
1287
app/src/test/resources/chargeprice/2105.json
Normal file
10
fastlane/metadata/android/de-DE/changelogs/39.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
Neue Features:
|
||||
- Kartenausschnitt folgt der aktuellen Position
|
||||
- Verfügbarkeitsanzeige in der Kartenansicht beinhaltet nur die per Filter gewählten Anschlüsse (z.B. nur CCS)
|
||||
- Links zu https://www.goingelectric.de/stromtankstellen können in EVMap geöffnet werden
|
||||
- Geteilte Standorte (z.B. aus Messenger-Apps) können in EVMap geöffnet werden
|
||||
|
||||
Fehlerkorrekturen:
|
||||
- Filtereinstellungen wurden bei Umbenennen eines Filterprofils fälschlicherweise gelöscht
|
||||
- Ausgewählter Kartentyp (Satellit, Gelände, Standard) bleibt beim App-Neustart erhalten
|
||||
- Copyright-Jahr aktualisiert
|
||||
7
fastlane/metadata/android/de-DE/changelogs/42.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Neue Features:
|
||||
- Unterstützung für Android Auto
|
||||
- Von der GoingElectric.de-Community verifizierte Ladestationen werden markiert
|
||||
|
||||
Verbesserungen:
|
||||
- Aktivierte Verkehrsdaten auf der Karte werden nach App-Neustart beibehalten
|
||||
- Abstürze behoben
|
||||
5
fastlane/metadata/android/de-DE/changelogs/43.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Verbesserungen:
|
||||
- Kartentypenmenü wird bei Verschieben der Karte automatisch geschlossen
|
||||
- Android Auto: Suchradius vergrößert, wenn wenige Ladestationen in der Nähe sind
|
||||
- Verifizierungstatus wird unabhängig von Störungsmeldungen angezeigt
|
||||
- Abstürze behoben
|
||||
9
fastlane/metadata/android/de-DE/changelogs/44.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Neue Features:
|
||||
- Native Integration von Chargeprice.app
|
||||
- Live-Daten: Unterscheidung zwischen belegten (türkis) und defekten (rot) Ladestationen
|
||||
- Information, ob Laden ohne Registrierung möglich ist, wird angezeigt
|
||||
|
||||
Verbesserungen:
|
||||
- Absturz auf Android Auto behoben, wenn eine Ladestation kein Foto hat
|
||||
- Verbesserung für das Teilen von Links und Standorten zu EVMap
|
||||
- Hinweis zur Behebung des Fehlers, dass beim Tippen auf den Bearbeiten-Knopf nur eine leere Seite angezeigt wird, hinzugefügt
|
||||
3
fastlane/metadata/android/de-DE/changelogs/45.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Verbesserungen:
|
||||
- Kleinere Verbesserungen für die Chargeprice.app-Integration
|
||||
- Verschiedene Abstürze behoben
|
||||
4
fastlane/metadata/android/de-DE/changelogs/46.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Verbesserungen für Chargeprice.app-Integration:
|
||||
- Währung kann in den Einstellungen gewählt werden
|
||||
- Ausgewählter Ladebereich wird gespeichert
|
||||
- Eigene Tarife können in den Einstellungen ausgewählt werden
|
||||
@@ -10,6 +10,7 @@ Funktionen:
|
||||
- Suche nach Orten
|
||||
- Erweiterte Filterfunktionen
|
||||
- Favoritenliste, auch mit Anzeige der Verfügbarkeit
|
||||
- Unterstützung für Android Auto
|
||||
- Keine nervige Werbung
|
||||
|
||||
EVMap ist ein Open-Source-Projekt und unter https://github.com/johan12345/EVMap zu finden.
|
||||
|
||||
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 158 KiB |