mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-25 08:07:46 -05:00
Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0998ed1f67 | ||
|
|
d7a644cb78 | ||
|
|
f2d98f9d82 | ||
|
|
09d6647ec0 | ||
|
|
1d3e3417aa | ||
|
|
20ae25cf8a | ||
|
|
087178193b | ||
|
|
60d54c989b | ||
|
|
c0555e7965 | ||
|
|
49c2fb3494 | ||
|
|
c1ec46917e | ||
|
|
ac11cddd42 | ||
|
|
6267e897d4 | ||
|
|
8a0224707b | ||
|
|
09ded65b4e | ||
|
|
c8f333ce89 | ||
|
|
4989aedd8b | ||
|
|
4beb1f92ad | ||
|
|
f24b7d1c2c | ||
|
|
094a26980f | ||
|
|
098f815ac9 | ||
|
|
6c51693b8d | ||
|
|
a360c71ecb | ||
|
|
b9da72e449 | ||
|
|
b0aeb8af98 | ||
|
|
1867d1bf7a | ||
|
|
31fcee97e1 | ||
|
|
de8fd364f4 | ||
|
|
e3f271be5d | ||
|
|
99a2540398 | ||
|
|
85173b438b | ||
|
|
c288883572 | ||
|
|
c8f949da01 | ||
|
|
fe33dca1bc | ||
|
|
4fb5090e9b | ||
|
|
d9b8bf382a | ||
|
|
d69456dfd0 | ||
|
|
4da2a273c7 | ||
|
|
8e622c881d | ||
|
|
89b2175d89 | ||
|
|
3c30481821 | ||
|
|
385353689d | ||
|
|
7f9c838b9d | ||
|
|
205814e6f6 | ||
|
|
f30ae4a720 | ||
|
|
a6117d3484 | ||
|
|
f650def803 | ||
|
|
09ec0d1635 | ||
|
|
cfc98209a1 | ||
|
|
17dbab4659 | ||
|
|
f4ed7f7397 | ||
|
|
c26253fe44 | ||
|
|
1c7cc32427 | ||
|
|
4ff944c4e4 | ||
|
|
2ec437a14b | ||
|
|
ae05d44649 | ||
|
|
a0f90a8c94 | ||
|
|
e1b7463490 | ||
|
|
27cbb5e208 | ||
|
|
5526e5d97c | ||
|
|
fc92ee9cdc | ||
|
|
f20fab965c | ||
|
|
9146e7dab8 | ||
|
|
54936b6e1a | ||
|
|
f3245dc29b | ||
|
|
90968029ad | ||
|
|
97cab1d007 | ||
|
|
0d41fb2685 | ||
|
|
2c0a5085ab | ||
|
|
9e80270a78 | ||
|
|
1b374cda1c | ||
|
|
b82f6f68fb | ||
|
|
8e19399aaa | ||
|
|
e315da926e | ||
|
|
9450230856 | ||
|
|
81d62860e2 | ||
|
|
e825654b9c | ||
|
|
0a4878a129 | ||
|
|
af50a95abd | ||
|
|
fe58551de9 | ||
|
|
abc85c136b | ||
|
|
0ad4691d30 | ||
|
|
d85a64ec77 | ||
|
|
52fefb564a | ||
|
|
d18b2e26b8 | ||
|
|
d5f55366a9 | ||
|
|
2f93e92b57 | ||
|
|
24e5d072d6 | ||
|
|
a9e9055671 | ||
|
|
53ab8dc4e8 | ||
|
|
c0d7d59817 | ||
|
|
42cfdfee1d | ||
|
|
41cb6cf6b0 | ||
|
|
64f50cc5e6 | ||
|
|
c24c03bb32 | ||
|
|
bec25dd4d2 | ||
|
|
4e4c5a0e9a | ||
|
|
17bd7f024e | ||
|
|
fc5e77b01a | ||
|
|
6a114fc2ea | ||
|
|
6e32c6644c | ||
|
|
8fb34ae66f | ||
|
|
ae3489621e | ||
|
|
ff0a110f51 | ||
|
|
24ef4888a8 | ||
|
|
13916b0c8d | ||
|
|
efdd0d6bc5 | ||
|
|
155aca0041 | ||
|
|
6393eadc81 | ||
|
|
4319ece4f3 | ||
|
|
62f2002e5c |
21
README.md
21
README.md
@@ -42,12 +42,23 @@ EVMap uses and put them into the app in the form of a resource file called `apik
|
||||
features and how they can be obtained in our [documentation page](doc/api_keys.md).
|
||||
|
||||
There are three different build flavors, `googleNormal`, `fossNormal` and `googleAutomotive`.
|
||||
- The `foss` variant only uses Mapbox data and should run on most Android devices, even without Google Play Services.
|
||||
- The `foss` variant only uses Mapbox data and should run on most Android devices, even without
|
||||
Google Play Services.
|
||||
- The `google` variants also include access to Google Maps data.
|
||||
- `googleNormal` is intended to run on smartphones and tablets, and also includes the Android Auto app for use
|
||||
on the car display.
|
||||
- `googleAutomotive` variant is intended to be installed directly on car infotainment systems using the
|
||||
Google-flavored Android Automotive OS. It does not provide the usual smartphone UI.
|
||||
- `googleNormal` is intended to run on smartphones and tablets, and also includes the Android
|
||||
Auto app for use on the car display.
|
||||
- `googleAutomotive` variant is intended to be installed directly on car infotainment systems
|
||||
using the Google-flavored Android Automotive OS. It does not provide the usual smartphone UI.
|
||||
|
||||
We also have a special [documentation page](doc/android_auto.md) on how to test the Android Auto
|
||||
app.
|
||||
|
||||
Translations
|
||||
------------
|
||||
|
||||
You can use our [Weblate page](https://hosted.weblate.org/projects/evmap/) to help translate EVMap
|
||||
into new languages.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/evmap/">
|
||||
<img src="https://hosted.weblate.org/widgets/evmap/-/open-graph.png" width="500" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
65
_img/app_logo.svg
Normal file
65
_img/app_logo.svg
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 25.3.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
|
||||
viewBox="0 0 663.6 219.8" style="enable-background:new 0 0 663.6 219.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFB300;}
|
||||
.st1{fill:#90A4AE;}
|
||||
.st2{fill:#546E7A;}
|
||||
.st3{fill:#00E676;}
|
||||
.st4{fill:#FFFFFF;fill-opacity:0.2;}
|
||||
.st5{fill:#3E2723;fill-opacity:0.2;}
|
||||
.st6{opacity:0.45;enable-background:new ;}
|
||||
.st7{enable-background:new ;}
|
||||
.st8{fill:#1D1D1B;}
|
||||
</style>
|
||||
<g id="Ebene_2_1_">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0"
|
||||
d="M19.4,161.7l-4-35.1l-6.1,0.6l4,35.1L19.4,161.7z M41.2,159.1l-4-35.1l-6.1,0.6l4,35.1L41.2,159.1z" />
|
||||
<path class="st1" d="M52.6,206.9c-1.9,2.3-3.4,3.8-3.6,4c-5.5,4.4-9.9,5.7-13.5,4c-6.3-3.2-5.9-15-5.7-16.3l4.4,0.2
|
||||
c-0.2,3.4,0.4,10.6,3.4,12c1.7,0.8,4.6-0.2,8.5-3.4l0,0c0,0,12.3-12.3,9.7-22c-3-11.6,10.6-28.3,15-34l0.6-0.6l3.6,2.7l-0.6,0.8
|
||||
c-13.7,16.9-15.2,25.6-14.2,30C62.3,192.9,56.6,202,52.6,206.9z" />
|
||||
<path class="st1"
|
||||
d="M5.9,161.2l1.7,14.4l13.3,8.9l18-1.9l11-11.6l-1.7-14.4L5.9,161.2z" />
|
||||
<g>
|
||||
<path class="st2" d="M38.6,182.6l-18,1.9l3.8,15.8l14.2-1.7V182.6L38.6,182.6z M51.5,144.5l1.5,13.1l-51.5,5.9L0,150.4
|
||||
L51.5,144.5z" />
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st3" d="M91.9,0c-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.9C158.8,29.8,128.8,0,91.9,0z" />
|
||||
<path class="st4" d="M91.9,1.5c36.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,67c0,0.2,0,0.4,0,0.6
|
||||
C25.3,31.1,55.1,1.5,91.9,1.5L91.9,1.5z" />
|
||||
<path class="st5" d="M95.9,214.3c-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.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
|
||||
C158.4,116,102.4,142.4,95.9,214.3L95.9,214.3z" />
|
||||
</g>
|
||||
<path class="st6"
|
||||
d="M76.5,34.3v40.6h11v33.2l25.8-44.4H98.5l14.8-29.6C113.4,34.3,76.5,34.3,76.5,34.3z" />
|
||||
</g>
|
||||
</g>
|
||||
<g class="st7">
|
||||
<path class="st8"
|
||||
d="M307.9,102.9h-45.4v39.6h52.2v6.9h-60.4V52.3h60.1v6.9h-51.9v36.7h45.4V102.9z" />
|
||||
<path class="st8"
|
||||
d="M361.2,137.4l0.5,2.1l0.6-2.1l30.5-85.1h9l-36.1,97.1h-7.9l-36.1-97.1h8.9L361.2,137.4z" />
|
||||
<path class="st8" d="M427,52.4l35.8,85.7l35.9-85.7h10.9v97.1h-8.2v-42.3l0.7-43.3L466,149.5h-6.3l-36-85.3l0.7,42.7v42.5h-8.2
|
||||
V52.3H427V52.4z" />
|
||||
<path class="st8" d="M578,149.4c-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.8s-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.2c0-5.2-1.6-9.2-4.8-12.2
|
||||
s-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.1c0-5.7,2.7-10.7,8-14.9s11.9-6.3,19.7-6.3
|
||||
c8,0,14.4,2,19,6s7,9.6,7.2,16.8v34.1c0,7,0.7,12.2,2.2,15.7v0.8H578V149.4z M552.9,143.7c5.3,0,10.1-1.3,14.3-3.9
|
||||
s7.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.6c0,4,1.5,7.4,4.5,10.1S548.1,143.7,552.9,143.7z
|
||||
" />
|
||||
<path class="st8" d="M663.6,114.1c0,11.2-2.5,20.2-7.5,26.8s-11.6,9.9-20,9.9c-9.9,0-17.4-3.5-22.7-10.4v36.8h-7.9V77.3h7.4
|
||||
l0.4,10.2C618.5,79.8,626,76,635.9,76c8.6,0,15.4,3.3,20.3,9.8c4.9,6.5,7.4,15.6,7.4,27.2V114.1z M655.6,112.7
|
||||
c0-9.2-1.9-16.5-5.7-21.8s-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-8C653.7,130.6,655.6,122.9,655.6,112.7z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,31 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<?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 233.8 368.4" style="enable-background:new 0 0 233.8 368.4;" xml:space="preserve">
|
||||
viewBox="0 0 233.8 368.4" style="enable-background:new 0 0 233.8 368.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{display:none;}
|
||||
.st2{display:inline;fill:#802C27;}
|
||||
.st3{fill:#808080;}
|
||||
.st4{display:none;fill:#802C27;}
|
||||
.st1{fill:#808080;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M109.8,0h13.6c33.9,1.9,67.1,18.5,87.7,45.8c13.5,17.2,21,38.6,22.7,60.3v8.1c-0.8,42.1-27.7,76.6-51,109.4
|
||||
c-26.2,37-50.4,77.3-57.1,122.9c-1.8,7.7,0.4,18.5-8.9,22c-2.2-1.7-4.7-3.1-6.2-5.4c-2.7-25.5-9.1-50.7-20-73.9
|
||||
c-12.3-27.1-29.5-51.6-47-75.6C33,199,23,184.2,14.7,168.3c-13-23.8-17.9-51.9-12.5-78.6c4.4-21.1,15.4-40.6,30.6-55.7
|
||||
C53.3,14,81.1,1.8,109.8,0z" />
|
||||
</g>
|
||||
</g>
|
||||
<g class="st1">
|
||||
<path class="st2" d="M107.2,74.1c18.9-4.8,40.4,5.5,47.7,23.7c6.1,14.5,1.9,32.5-9.9,42.9c-12.6,11.5-32.4,14-47.5,6
|
||||
c-13.9-6.8-23-22.6-21.3-38.1C77.6,92,91.1,77.7,107.2,74.1z" />
|
||||
</g>
|
||||
<path class="st0" d="M117,367.4c-0.4-0.3-0.8-0.6-1.2-0.9c-1.6-1.2-3.1-2.3-4.2-3.7c-2.9-26.9-9.6-51.7-20.1-74
|
||||
c-12.4-27.3-30.1-52.4-47.1-75.8c-8.7-12-19.8-27.9-28.8-45.2C2.3,143.6-2.1,115.9,3.2,89.9c4.3-20.4,15-40,30.3-55.2
|
||||
C53.6,15.1,81.5,2.8,109.9,1l13.5,0c34.4,1.9,66.9,18.9,86.9,45.4c12.8,16.3,20.8,37.5,22.5,59.8l0,8
|
||||
c-0.7,38.8-23.7,70.9-45.9,101.9c-1.7,2.3-3.3,4.6-5,6.9c-24.4,34.5-50.3,76.1-57.3,123.3c-0.5,2-0.7,4.3-0.9,6.5
|
||||
C123.3,359,122.8,364.9,117,367.4z" />
|
||||
<path class="st1" d="M123.3,2c34.1,1.9,66.3,18.8,86.2,45c12.6,16.1,20.5,37.1,22.3,59.1l0,8c-0.7,38.5-23.6,70.5-45.7,101.3
|
||||
c-1.7,2.3-3.3,4.6-5,6.9c-24.5,34.6-50.5,76.3-57.4,123.7c-0.5,2.1-0.7,4.4-0.9,6.7c-0.5,5.9-1,11-5.8,13.4
|
||||
c-0.2-0.2-0.5-0.4-0.7-0.5c-1.5-1.1-2.9-2-3.8-3.3c-2.9-26.9-9.7-51.8-20.1-74C80,261,62.3,235.8,45.2,212.4
|
||||
c-8.7-11.9-19.8-27.8-28.8-45.1C3.3,143.3-1,115.9,4.2,90.1c4.2-20.2,14.9-39.6,30-54.7C54.2,16,81.7,3.8,109.9,2H123.3 M123.4,0
|
||||
h-13.6c-28.7,1.8-56.5,14-77,34C17.6,49.1,6.6,68.6,2.2,89.7c-5.4,26.7-0.5,54.8,12.5,78.6C23,184.2,33,199,43.6,213.6
|
||||
c17.5,24,34.7,48.5,47,75.6c10.9,23.2,17.3,48.4,20,73.9c1.5,2.3,4,3.7,6.2,5.4c9.3-3.5,7.1-14.3,8.9-22
|
||||
c6.7-45.6,30.9-85.9,57.1-122.9c23.3-32.8,50.2-67.3,51-109.4v-8.1c-1.7-21.7-9.2-43.1-22.7-60.3C190.5,18.5,157.3,1.9,123.4,0
|
||||
L123.4,0z" />
|
||||
</g>
|
||||
<path class="st3" d="M90.9,57.3v68.2h18.6v55.8l43.4-74.4h-24.8l24.8-49.6H90.9z" />
|
||||
<path class="st4" d="M159,85.3L159,85.3l-20.8-20.9l-5.9,5.9l11.8,11.8c-5.3,2-9,7.1-9,13.1c0,7.7,6.3,14,14,14c2,0,3.9-0.4,5.6-1.2
|
||||
v40.4c0,3.1-2.5,5.6-5.6,5.6s-5.6-2.5-5.6-5.6v-25.2c0-6.2-5-11.2-11.2-11.2h-5.6V72.8c0-6.2-5-11.2-11.2-11.2H81.8
|
||||
c-6.2,0-11.2,5-11.2,11.2v89.7h56.1v-42.1h8.4v28c0,7.7,6.3,14,14,14s14-6.3,14-14V95.2C163.1,91.3,161.6,87.8,159,85.3
|
||||
M149.1,100.8c-3.1,0-5.6-2.5-5.6-5.6c0-3.1,2.5-5.6,5.6-5.6s5.6,2.5,5.6,5.6C154.7,98.3,152.2,100.8,149.1,100.8 M93.1,145.6v-25.2
|
||||
H81.8l22.4-42.1v28h11.2L93.1,145.6z" />
|
||||
<path class="st1"
|
||||
d="M90.9,57.3v68.2h18.6v55.8l43.4-74.4h-24.8l24.8-49.6C152.9,57.3,90.9,57.3,90.9,57.3z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,27 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<?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 233.8 368.4" style="enable-background:new 0 0 233.8 368.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#B5B5B5;}
|
||||
.st2{fill:#808080;}
|
||||
.st1{fill:#808080;}
|
||||
.st2{fill:#B5B5B5;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M109.8,0h13.6c33.9,1.9,67.1,18.5,87.7,45.8c13.5,17.2,21,38.6,22.7,60.3v8.1c-0.8,42.1-27.7,76.6-51,109.4
|
||||
c-26.2,37-50.4,77.3-57.1,122.9c-1.8,7.7,0.4,18.5-8.9,22c-2.2-1.7-4.7-3.1-6.2-5.4c-2.7-25.5-9.1-50.7-20-73.9
|
||||
c-12.3-27.1-29.5-51.6-47-75.6C33,199,23,184.2,14.7,168.3c-13-23.8-17.9-51.9-12.5-78.6C6.6,68.6,17.6,49.1,32.8,34
|
||||
C53.3,14,81.1,1.8,109.8,0z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<polygon class="st1"
|
||||
points="143.2,109.4 123.5,143.2 123.5,181.3 166.9,106.9 144.7,106.9 " />
|
||||
<path class="st1"
|
||||
d="M122.2,101.9h16.7h5.7l22.3-44.6c0,0-10.2,0-22.4,0l-1.1,2.2L122.2,101.9z" />
|
||||
<path class="st2" d="M138.9,57.3c-9.7,0-19.8,0-26.4,0c-2.5,0-5.1,0-7.6,0c-8.2,0-16.1,0-21.4,0c-4.1,0-6.6,0-6.6,0v68.2h18.6v55.8
|
||||
l43.4-74.4h-24.8L138.9,57.3z" />
|
||||
<path class="st0" d="M117,367.4c-0.4-0.3-0.8-0.6-1.2-0.9c-1.6-1.2-3.1-2.3-4.2-3.7c-2.9-26.9-9.6-51.7-20.1-74
|
||||
c-12.4-27.3-30.1-52.4-47.1-75.8c-8.7-12-19.8-27.9-28.8-45.2C2.3,143.6-2.1,115.9,3.2,89.9c4.3-20.4,15-40,30.3-55.2
|
||||
C53.6,15.1,81.5,2.8,109.9,1l13.5,0c34.4,1.9,66.9,18.9,86.9,45.4c12.8,16.3,20.8,37.5,22.5,59.8l0,8
|
||||
c-0.7,38.8-23.7,70.9-45.9,101.9c-1.7,2.3-3.3,4.6-5,6.9c-24.4,34.5-50.3,76.1-57.3,123.3c-0.5,2-0.7,4.3-0.9,6.5
|
||||
C123.3,359,122.8,364.9,117,367.4z" />
|
||||
<path class="st1" d="M123.3,2c34.1,1.9,66.3,18.8,86.2,45c12.6,16.1,20.5,37.1,22.3,59.1l0,8c-0.7,38.5-23.6,70.5-45.7,101.3
|
||||
c-1.7,2.3-3.3,4.6-5,6.9c-24.5,34.6-50.5,76.3-57.4,123.7c-0.5,2.1-0.7,4.4-0.9,6.7c-0.5,5.9-1,11-5.8,13.4
|
||||
c-0.2-0.2-0.5-0.4-0.7-0.5c-1.5-1.1-2.9-2-3.8-3.3c-2.9-26.9-9.7-51.8-20.1-74C80,261,62.3,235.8,45.2,212.4
|
||||
c-8.7-11.9-19.8-27.8-28.8-45.1C3.3,143.3-1,115.9,4.2,90.1c4.2-20.2,14.9-39.6,30-54.7C54.2,16,81.7,3.8,109.9,2H123.3 M123.4,0
|
||||
h-13.6c-28.7,1.8-56.5,14-77,34C17.6,49.1,6.6,68.6,2.2,89.7c-5.4,26.7-0.5,54.8,12.5,78.6C23,184.2,33,199,43.6,213.6
|
||||
c17.5,24,34.7,48.5,47,75.6c10.9,23.2,17.3,48.4,20,73.9c1.5,2.3,4,3.7,6.2,5.4c9.3-3.5,7.1-14.3,8.9-22
|
||||
c6.7-45.6,30.9-85.9,57.1-122.9c23.3-32.8,50.2-67.3,51-109.4v-8.1c-1.7-21.7-9.2-43.1-22.7-60.3C190.5,18.5,157.3,1.9,123.4,0
|
||||
L123.4,0z" />
|
||||
</g>
|
||||
<polygon class="st2" points="143.2,109.4 123.5,143.2 123.5,181.3 166.9,106.9 144.7,106.9 " />
|
||||
<path class="st2" d="M122.2,101.9h16.7h5.7l22.3-44.6c0,0-10.2,0-22.4,0l-1.1,2.2L122.2,101.9z" />
|
||||
<path class="st1" d="M138.9,57.3c-9.7,0-19.8,0-26.4,0c-2.5,0-5.1,0-7.6,0c-8.2,0-16.1,0-21.4,0c-4.1,0-6.6,0-6.6,0v68.2h18.6v55.8
|
||||
l43.4-74.4h-24.8L138.9,57.3z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -10,8 +10,10 @@ apply plugin: 'androidx.navigation.safeargs.kotlin'
|
||||
apply plugin: 'com.mikepenz.aboutlibraries.plugin'
|
||||
apply plugin: 'de.timfreiheit.resourceplaceholders'
|
||||
|
||||
def supportedLocales = "en,de,fr,nb-rNO"
|
||||
|
||||
android {
|
||||
compileSdkVersion 32
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
@@ -19,10 +21,12 @@ android {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
|
||||
versionCode 98
|
||||
versionName "1.3.7"
|
||||
versionCode 126
|
||||
versionName "1.3.14"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs supportedLocales.split(",")
|
||||
buildConfigField("String", "supportedLocales", '"' + supportedLocales + '"')
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -93,6 +97,7 @@ android {
|
||||
}
|
||||
lint {
|
||||
disable 'NullSafeMutableLiveData'
|
||||
warning 'MissingTranslation'
|
||||
}
|
||||
|
||||
testOptions {
|
||||
@@ -102,6 +107,7 @@ android {
|
||||
resourcePlaceholders {
|
||||
files = ['xml/shortcuts.xml']
|
||||
}
|
||||
namespace 'net.vonforst.evmap'
|
||||
|
||||
// add API keys from environment variable if not set in apikeys.xml
|
||||
applicationVariants.all { variant ->
|
||||
@@ -146,11 +152,11 @@ configurations {
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0-rc01'
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.0'
|
||||
implementation "androidx.activity:activity-ktx:1.5.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.1"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.2"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
@@ -158,15 +164,17 @@ dependencies {
|
||||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'com.github.johan12345:CustomBottomSheetBehavior:f69f532660'
|
||||
implementation 'com.github.johan12345:CustomBottomSheetBehavior:dd0167dbff'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.0'
|
||||
implementation 'com.squareup.moshi:moshi-kotlin:1.13.0'
|
||||
implementation 'com.squareup.moshi:moshi-adapters:1.13.0'
|
||||
implementation 'moe.banana:moshi-jsonapi:3.5.0'
|
||||
implementation 'moe.banana:moshi-jsonapi-retrofit-converter:3.5.0'
|
||||
implementation 'com.github.johan12345:jsonapi:50d72e7e55' // patched version for jsonapi-adapters
|
||||
implementation('com.markomilos.jsonapi:jsonapi-retrofit:1.0.1') {
|
||||
exclude group: 'com.markomilos.jsonapi', module: 'jsonapi-adapters'
|
||||
}
|
||||
implementation 'io.coil-kt:coil:1.1.0'
|
||||
implementation 'com.github.johan12345:StfalconImageViewer:5082ebd392'
|
||||
implementation "com.mikepenz:aboutlibraries-core:$about_libs_version"
|
||||
@@ -184,7 +192,7 @@ dependencies {
|
||||
googleAutomotiveImplementation "androidx.car.app:app-automotive:$carAppVersion"
|
||||
|
||||
// AnyMaps
|
||||
def anyMapsVersion = 'f36bb3c126'
|
||||
def anyMapsVersion = 'a9b3dd7d99'
|
||||
implementation "com.github.johan12345.AnyMaps:anymaps-base:$anyMapsVersion"
|
||||
googleImplementation "com.github.johan12345.AnyMaps:anymaps-google:$anyMapsVersion"
|
||||
googleImplementation 'com.google.android.gms:play-services-maps:18.1.0'
|
||||
@@ -250,7 +258,7 @@ dependencies {
|
||||
|
||||
kapt "com.squareup.moshi:moshi-kotlin-codegen:1.13.0"
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.2.2'
|
||||
}
|
||||
|
||||
private static String decode(String s, String key) {
|
||||
|
||||
5
app/src/debug/res/values/donottranslate.xml
Normal file
5
app/src/debug/res/values/donottranslate.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="chargeprice_api_url">https://staging-api.chargeprice.app/v1/</string>
|
||||
<string name="chargeprice_key">20c0d68918c9dc96c564784b711a6570</string>
|
||||
</resources>
|
||||
@@ -1,14 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.</string>
|
||||
<string name="donate_paypal">Mit PayPal spenden</string>
|
||||
<string name="data_sources_hint">Die Kartendaten für die App stammen von OpenStreetMap (Mapbox).</string>
|
||||
6
app/src/foss/res/values-fr/strings.xml
Normal file
6
app/src/foss/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Trouvez-vous EVMap utile \? Soutenez son développement en envoyant un don au développeur.</string>
|
||||
<string name="data_sources_hint">Les données cartographiques de l\'application sont fournies par OpenStreetMap (Mapbox).</string>
|
||||
<string name="donate_paypal">Faire un don avec PayPal</string>
|
||||
</resources>
|
||||
6
app/src/foss/res/values-nb-rNO/strings.xml
Normal file
6
app/src/foss/res/values-nb-rNO/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="donate_paypal">Doner med PayPal</string>
|
||||
<string name="data_sources_hint">Kartdata i programmet tilbys av OpenStreetMap (Mapbox).</string>
|
||||
<string name="donations_info" formatted="false">Synes du EVMap er nyttig\? Støtt utviklingen ved å sende en slant til utvikleren.</string>
|
||||
</resources>
|
||||
15
app/src/foss/res/values/arrays.xml
Normal file
15
app/src/foss/res/values/arrays.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>@string/pref_provider_osm_mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" translatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>@string/pref_provider_osm_mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" translatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
6
app/src/foss/res/values/donottranslate.xml
Normal file
6
app/src/foss/res/values/donottranslate.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pref_search_provider_default" translatable="false">mapbox</string>
|
||||
<string name="pref_map_provider_default" translatable="false">mapbox</string>
|
||||
<string name="paypal_link" translatable="false">https://paypal.me/johan98</string>
|
||||
</resources>
|
||||
6
app/src/foss/res/values/strings.xml
Normal file
6
app/src/foss/res/values/strings.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.</string>
|
||||
<string name="donate_paypal">Donate with PayPal</string>
|
||||
<string name="data_sources_hint">Map data in the app is provided by OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" tranlatable="false">
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="pref_search_provider_default" translatable="false">mapbox</string>
|
||||
<string name="pref_map_provider_default" translatable="false">mapbox</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.</string>
|
||||
<string name="donate_paypal">Donate with PayPal</string>
|
||||
<string name="paypal_link" translatable="false">https://paypal.me/johan98</string>
|
||||
<string name="data_sources_hint">Map data in the app is provided by OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?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">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="androidx.car.app.MAP_TEMPLATES" />
|
||||
|
||||
@@ -7,16 +7,10 @@ import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.maps.MapsInitializer
|
||||
import com.google.android.libraries.places.api.Places
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.utils.LocaleContextWrapper
|
||||
|
||||
fun init(context: Context) {
|
||||
Places.initialize(context, context.getString(R.string.google_maps_key))
|
||||
|
||||
val localeContext = LocaleContextWrapper.wrap(
|
||||
context.applicationContext, PreferenceDataSource(context).language
|
||||
)
|
||||
MapsInitializer.initialize(localeContext, MapsInitializer.Renderer.LATEST, null)
|
||||
MapsInitializer.initialize(context, MapsInitializer.Renderer.LATEST, null)
|
||||
}
|
||||
|
||||
fun checkPlayServices(activity: Activity): Boolean {
|
||||
|
||||
@@ -5,12 +5,9 @@ import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.location.Criteria
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresPermission
|
||||
@@ -31,6 +28,9 @@ import androidx.core.location.LocationListenerCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.location.FusionEngine
|
||||
import net.vonforst.evmap.location.LocationEngine
|
||||
import net.vonforst.evmap.location.Priority
|
||||
import net.vonforst.evmap.utils.checkFineLocationPermission
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
location?.let { value?.updateLocation(it) }
|
||||
}
|
||||
private var location: Location? = null
|
||||
private val locationManager: LocationManager by lazy {
|
||||
carContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
private val locationEngine: LocationEngine by lazy {
|
||||
FusionEngine(carContext)
|
||||
}
|
||||
|
||||
private val hardwareMan: CarHardwareManager by lazy {
|
||||
@@ -179,16 +179,11 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
|
||||
private fun requestPhoneLocationUpdates() {
|
||||
val provider = locationManager.getBestProvider(Criteria().apply {
|
||||
accuracy = Criteria.ACCURACY_FINE
|
||||
}, true) ?: return
|
||||
|
||||
val location = locationManager.getLastKnownLocation(provider)
|
||||
val location = locationEngine.getLastKnownLocation()
|
||||
updateLocation(location)
|
||||
locationManager.requestLocationUpdates(
|
||||
provider,
|
||||
locationEngine.requestLocationUpdates(
|
||||
Priority.HIGH_ACCURACY,
|
||||
1000,
|
||||
1f,
|
||||
phoneLocationListener
|
||||
)
|
||||
}
|
||||
@@ -209,7 +204,7 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION])
|
||||
private fun removePhoneLocationUpdates() {
|
||||
locationManager.removeUpdates(phoneLocationListener)
|
||||
locationEngine.removeUpdates(phoneLocationListener)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
|
||||
@@ -15,13 +15,13 @@ import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import jsonapi.Meta
|
||||
import jsonapi.Relationship
|
||||
import jsonapi.Relationships
|
||||
import jsonapi.ResourceIdentifier
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import moe.banana.jsonapi2.HasMany
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
import moe.banana.jsonapi2.JsonBuffer
|
||||
import moe.banana.jsonapi2.ResourceIdentifier
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
@@ -34,7 +34,10 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
private val prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
ChargepriceApi.create(carContext.getString(R.string.chargeprice_key))
|
||||
ChargepriceApi.create(
|
||||
carContext.getString(R.string.chargeprice_key),
|
||||
carContext.getString(R.string.chargeprice_api_url)
|
||||
)
|
||||
}
|
||||
private var prices: List<ChargePrice>? = null
|
||||
private var meta: ChargepriceChargepointMeta? = null
|
||||
@@ -94,7 +97,7 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
)
|
||||
.build().intent
|
||||
intent.data =
|
||||
Uri.parse("https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=${getDataAdapter()}")
|
||||
Uri.parse(ChargepriceApi.getPoiUrl(charger))
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
carContext.startActivity(intent)
|
||||
@@ -169,39 +172,44 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
}
|
||||
|
||||
private fun loadPrices(model: Model?) {
|
||||
val dataAdapter = getDataAdapter() ?: return
|
||||
val dataAdapter = ChargepriceApi.getDataAdapter(charger) ?: return
|
||||
val manufacturer = model?.manufacturer?.value
|
||||
val modelName = getVehicleModel(model?.manufacturer?.value, model?.name?.value)
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
val car = determineVehicle(manufacturer, modelName)
|
||||
val cpStation = ChargepriceStation.fromEvmap(charger, car.compatibleEvmapConnectors)
|
||||
val result = api.getChargePrices(ChargepriceRequest().apply {
|
||||
this.dataAdapter = dataAdapter
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
tariffs = if (!prefs.chargepriceMyTariffsAll) {
|
||||
val myTariffs = prefs.chargepriceMyTariffs ?: emptySet()
|
||||
HasMany<ChargepriceTariff>(*myTariffs.map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
it
|
||||
val result = api.getChargePrices(
|
||||
ChargepriceRequest(
|
||||
dataAdapter = dataAdapter,
|
||||
station = cpStation,
|
||||
vehicle = car,
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange.map { it.toDouble() },
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency,
|
||||
allowUnbalancedLoad = prefs.chargepriceAllowUnbalancedLoad
|
||||
),
|
||||
relationships = if (!prefs.chargepriceMyTariffsAll) {
|
||||
val myTariffs = prefs.chargepriceMyTariffs ?: emptySet()
|
||||
Relationships(
|
||||
"tariffs" to Relationship.ToMany(
|
||||
myTariffs.map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
id = it
|
||||
)
|
||||
},
|
||||
meta = Meta.from(
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS),
|
||||
ChargepriceApi.moshi
|
||||
)
|
||||
)
|
||||
)
|
||||
}.toTypedArray()).apply {
|
||||
meta = JsonBuffer.create(
|
||||
ChargepriceApi.moshi.adapter(ChargepriceRequestTariffMeta::class.java),
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS)
|
||||
)
|
||||
}
|
||||
} else null
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange.map { it.toDouble() },
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency,
|
||||
allowUnbalancedLoad = prefs.chargepriceAllowUnbalancedLoad
|
||||
)
|
||||
}, ChargepriceApi.getChargepriceLanguage())
|
||||
} else null
|
||||
), ChargepriceApi.getChargepriceLanguage()
|
||||
)
|
||||
|
||||
val myTariffs = prefs.chargepriceMyTariffs
|
||||
|
||||
@@ -215,14 +223,16 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
meta =
|
||||
(result.meta.get<ChargepriceMeta>(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta).chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull {
|
||||
it.power
|
||||
}
|
||||
|
||||
prices = result.map { cp ->
|
||||
val metaMapped =
|
||||
result.meta!!.map(ChargepriceMeta::class.java, ChargepriceApi.moshi)!!
|
||||
meta = metaMapped.chargePoints.filterIndexed { i, cp ->
|
||||
charger.chargepointsMerged[i].type in car.compatibleEvmapConnectors
|
||||
}.maxByOrNull {
|
||||
it.power
|
||||
}
|
||||
|
||||
prices = result.data!!.map { cp ->
|
||||
val filteredPrices =
|
||||
cp.chargepointPrices.filter {
|
||||
it.plug == chargepoint.plug && it.power == chargepoint.power
|
||||
@@ -230,15 +240,15 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
if (filteredPrices.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
cp.clone().apply {
|
||||
cp.copy(
|
||||
chargepointPrices = filteredPrices
|
||||
}
|
||||
)
|
||||
}
|
||||
}.filterNotNull()
|
||||
.sortedBy { it.chargepointPrices.first().price }
|
||||
.sortedByDescending {
|
||||
prefs.chargepriceMyTariffsAll ||
|
||||
myTariffs != null && it.tariff?.get()?.id in myTariffs
|
||||
myTariffs != null && it.tariffId in myTariffs
|
||||
}
|
||||
invalidate()
|
||||
} catch (e: IOException) {
|
||||
@@ -316,10 +326,4 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
}
|
||||
return vehicles[0]
|
||||
}
|
||||
|
||||
private fun getDataAdapter(): String? = when (charger.dataSource) {
|
||||
"goingelectric" -> ChargepriceApi.DATA_SOURCE_GOINGELECTRIC
|
||||
"openchargemap" -> ChargepriceApi.DATA_SOURCE_OPENCHARGEMAP
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package net.vonforst.evmap.auto
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.text.SpannableStringBuilder
|
||||
@@ -32,17 +34,19 @@ import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Favorite
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.ChargeLocationsRepository
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.ChargerIconGenerator
|
||||
import net.vonforst.evmap.ui.availabilityText
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import net.vonforst.evmap.viewmodel.getReferenceData
|
||||
import net.vonforst.evmap.viewmodel.awaitFinished
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) {
|
||||
var charger: ChargeLocation? = null
|
||||
var photo: Bitmap? = null
|
||||
@@ -50,10 +54,8 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
createApi(prefs.dataSource, ctx)
|
||||
}
|
||||
private val referenceData = api.getReferenceData(lifecycleScope, carContext)
|
||||
private val repo =
|
||||
ChargeLocationsRepository(createApi(prefs.dataSource, ctx), lifecycleScope, db, prefs)
|
||||
|
||||
private val imageSize = 128 // images should be 128dp according to docs
|
||||
private val imageSizeLarge = 480 // images should be 480 x 480 dp according to docs
|
||||
@@ -71,9 +73,7 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
private var favoriteUpdateJob: Job? = null
|
||||
|
||||
init {
|
||||
referenceData.observe(this) {
|
||||
loadCharger()
|
||||
}
|
||||
loadCharger()
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
@@ -356,24 +356,21 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
}
|
||||
|
||||
private fun loadCharger() {
|
||||
val referenceData = referenceData.value ?: return
|
||||
lifecycleScope.launch {
|
||||
favorite = db.favoritesDao().findFavorite(chargerSparse.id, chargerSparse.dataSource)
|
||||
|
||||
val response = api.getChargepointDetail(referenceData, chargerSparse.id)
|
||||
val response = repo.getChargepointDetail(chargerSparse.id).awaitFinished()
|
||||
if (response.status == Status.SUCCESS) {
|
||||
val charger = response.data!!
|
||||
|
||||
val photo = charger.photos?.firstOrNull()
|
||||
photo?.let {
|
||||
val density = carContext.resources.displayMetrics.density
|
||||
val url = if (largeImageSupported) {
|
||||
photo.getUrl(size = (imageSizeLarge * density).roundToInt())
|
||||
} else {
|
||||
photo.getUrl(size = (imageSize * density).roundToInt())
|
||||
}
|
||||
val size =
|
||||
(density * if (largeImageSupported) imageSizeLarge else imageSize).roundToInt()
|
||||
val url = photo.getUrl(size = size)
|
||||
val request = ImageRequest.Builder(carContext).data(url).build()
|
||||
var img =
|
||||
val img =
|
||||
(carContext.imageLoader.execute(request).drawable as BitmapDrawable).bitmap
|
||||
|
||||
// draw icon on top of image
|
||||
@@ -383,19 +380,29 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
multi = charger.isMulti()
|
||||
)
|
||||
|
||||
img = img.copy(Bitmap.Config.ARGB_8888, true)
|
||||
val outImg = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
val iconSmall = icon.scale(
|
||||
(img.height * 0.4 / icon.height * icon.width).roundToInt(),
|
||||
(img.height * 0.4).roundToInt()
|
||||
(size * 0.4 / icon.height * icon.width).roundToInt(),
|
||||
(size * 0.4).roundToInt()
|
||||
)
|
||||
val canvas = Canvas(outImg)
|
||||
|
||||
val m = Matrix()
|
||||
m.setRectToRect(
|
||||
RectF(0f, 0f, img.width.toFloat(), img.height.toFloat()),
|
||||
RectF(0f, 0f, size.toFloat(), size.toFloat()),
|
||||
Matrix.ScaleToFit.CENTER
|
||||
)
|
||||
canvas.drawBitmap(
|
||||
img.copy(Bitmap.Config.ARGB_8888, false), m, null
|
||||
)
|
||||
val canvas = Canvas(img)
|
||||
canvas.drawBitmap(
|
||||
iconSmall,
|
||||
0f,
|
||||
(img.height - iconSmall.height * 1.1).toFloat(),
|
||||
(size - iconSmall.height * 1.1).toFloat(),
|
||||
null
|
||||
)
|
||||
this@ChargerDetailScreen.photo = img
|
||||
this@ChargerDetailScreen.photo = outImg
|
||||
}
|
||||
this@ChargerDetailScreen.charger = charger
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.location.Location
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
@@ -13,7 +12,9 @@ import androidx.car.app.hardware.info.EnergyLevel
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.*
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.car2go.maps.model.LatLng
|
||||
import kotlinx.coroutines.*
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
@@ -24,14 +25,17 @@ import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.FILTERS_FAVORITES
|
||||
import net.vonforst.evmap.model.FilterValue
|
||||
import net.vonforst.evmap.model.FilterWithValue
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.ChargeLocationsRepository
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.availabilityText
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import net.vonforst.evmap.viewmodel.awaitFinished
|
||||
import net.vonforst.evmap.viewmodel.filtersWithValue
|
||||
import net.vonforst.evmap.viewmodel.getFilterValues
|
||||
import net.vonforst.evmap.viewmodel.getReferenceData
|
||||
import java.io.IOException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@@ -60,28 +64,25 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
private var location: Location? = null
|
||||
private var lastDistanceUpdateTime: Instant? = null
|
||||
private var chargers: List<ChargeLocation>? = null
|
||||
private var loadingError = false
|
||||
private var prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
createApi(prefs.dataSource, ctx)
|
||||
}
|
||||
private val repo =
|
||||
ChargeLocationsRepository(createApi(prefs.dataSource, ctx), lifecycleScope, db, prefs)
|
||||
private val searchRadius = 5 // kilometers
|
||||
private val distanceUpdateThreshold = Duration.ofSeconds(15)
|
||||
private val availabilityUpdateThreshold = Duration.ofMinutes(1)
|
||||
private var availabilities: MutableMap<Long, Pair<ZonedDateTime, ChargeLocationStatus?>> =
|
||||
HashMap()
|
||||
private val maxRows = if (ctx.carAppApiLevel >= 2) {
|
||||
ctx.constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_PLACE_LIST)
|
||||
min(
|
||||
ctx.constraintManager.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_PLACE_LIST),
|
||||
25
|
||||
)
|
||||
} else 6
|
||||
|
||||
private val referenceData = api.getReferenceData(lifecycleScope, carContext)
|
||||
private val filterStatus = MutableLiveData<Long>().apply {
|
||||
value = prefs.filterStatus
|
||||
}
|
||||
private val filterValues = db.filterValueDao().getFilterValues(filterStatus, prefs.dataSource)
|
||||
private val filters =
|
||||
Transformations.map(referenceData) { api.getFilters(it, carContext.stringProvider()) }
|
||||
private val filtersWithValue = filtersWithValue(filters, filterValues)
|
||||
private var filterStatus = prefs.filterStatus
|
||||
private var filtersWithValue: List<FilterWithValue<FilterValue>>? = null
|
||||
|
||||
private val hardwareMan: CarHardwareManager by lazy {
|
||||
ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager
|
||||
@@ -107,32 +108,36 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
}
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
session.requestLocationUpdates()
|
||||
|
||||
session.mapScreen = this
|
||||
return PlaceListMapTemplate.Builder().apply {
|
||||
setTitle(
|
||||
prefs.placeSearchResultAndroidAutoName?.let {
|
||||
carContext.getString(R.string.auto_chargers_near_location, it)
|
||||
} ?: carContext.getString(
|
||||
if (filterStatus.value == FILTERS_FAVORITES) {
|
||||
if (filterStatus == FILTERS_FAVORITES) {
|
||||
R.string.auto_favorites
|
||||
} else {
|
||||
R.string.auto_chargers_closeby
|
||||
}
|
||||
)
|
||||
)
|
||||
searchLocation?.let {
|
||||
setAnchor(Place.Builder(CarLocation.create(it.latitude, it.longitude)).apply {
|
||||
if (prefs.placeSearchResultAndroidAutoName != null) {
|
||||
setMarker(
|
||||
PlaceMarker.Builder()
|
||||
.setColor(CarColor.PRIMARY)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}.build())
|
||||
} ?: setLoading(true)
|
||||
if (prefs.placeSearchResultAndroidAutoName != null) {
|
||||
searchLocation?.let {
|
||||
setAnchor(Place.Builder(CarLocation.create(it.latitude, it.longitude)).apply {
|
||||
if (prefs.placeSearchResultAndroidAutoName != null) {
|
||||
setMarker(
|
||||
PlaceMarker.Builder()
|
||||
.setColor(CarColor.PRIMARY)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}.build())
|
||||
} ?: setLoading(true)
|
||||
} else {
|
||||
location?.let {
|
||||
setAnchor(Place.Builder(CarLocation.create(it.latitude, it.longitude)).build())
|
||||
} ?: setLoading(true)
|
||||
}
|
||||
chargers?.take(maxRows)?.let { chargerList ->
|
||||
val builder = ItemList.Builder()
|
||||
// only show the city if not all chargers are in the same city
|
||||
@@ -142,7 +147,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
}
|
||||
builder.setNoItemsMessage(
|
||||
carContext.getString(
|
||||
if (filterStatus.value == FILTERS_FAVORITES) {
|
||||
if (filterStatus == FILTERS_FAVORITES) {
|
||||
R.string.auto_no_favorites_found
|
||||
} else {
|
||||
R.string.auto_no_chargers_found
|
||||
@@ -151,21 +156,32 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
)
|
||||
builder.setOnItemsVisibilityChangedListener(this@MapScreen)
|
||||
setItemList(builder.build())
|
||||
} ?: setLoading(true)
|
||||
} ?: run {
|
||||
if (loadingError) {
|
||||
val builder = ItemList.Builder()
|
||||
builder.setNoItemsMessage(
|
||||
carContext.getString(R.string.connection_error)
|
||||
)
|
||||
setItemList(builder.build())
|
||||
} else {
|
||||
setLoading(true)
|
||||
}
|
||||
}
|
||||
setCurrentLocationEnabled(true)
|
||||
setHeaderAction(Action.APP_ICON)
|
||||
val filtersCount = if (filterStatus.value == FILTERS_FAVORITES) 1 else {
|
||||
filtersWithValue.value?.count {
|
||||
val filtersCount = if (filterStatus == FILTERS_FAVORITES) 1 else {
|
||||
filtersWithValue?.count {
|
||||
!it.value.hasSameValueAs(it.filter.defaultValue())
|
||||
}
|
||||
}
|
||||
|
||||
setActionStrip(
|
||||
ActionStrip.Builder()
|
||||
.addAction(Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
.addAction(
|
||||
Action.Builder()
|
||||
.setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_settings
|
||||
)
|
||||
@@ -189,9 +205,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
.build()
|
||||
)
|
||||
.setOnClickListener {
|
||||
screenManager.pushForResult(FilterScreen(carContext, session)) {
|
||||
filterStatus.value = prefs.filterStatus
|
||||
}
|
||||
screenManager.push(FilterScreen(carContext, session))
|
||||
session.mapScreen = null
|
||||
}
|
||||
.build())
|
||||
@@ -289,10 +303,10 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
) {
|
||||
return
|
||||
}
|
||||
val previousLocation = this.location
|
||||
this.location = location
|
||||
if (updateCoroutine != null) {
|
||||
// don't update while still loading last update
|
||||
return
|
||||
if (previousLocation == null) {
|
||||
loadChargers()
|
||||
}
|
||||
|
||||
val now = Instant.now()
|
||||
@@ -307,17 +321,22 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
|
||||
private fun loadChargers() {
|
||||
val location = location ?: return
|
||||
val referenceData = referenceData.value ?: return
|
||||
val filters = filtersWithValue.value ?: return
|
||||
|
||||
val searchLocation =
|
||||
prefs.placeSearchResultAndroidAuto ?: LatLng.fromLocation(location)
|
||||
this.searchLocation = searchLocation
|
||||
|
||||
updateCoroutine = lifecycleScope.launch {
|
||||
loadingError = false
|
||||
try {
|
||||
filterStatus = prefs.filterStatus
|
||||
val filterValues =
|
||||
db.filterValueDao().getFilterValuesAsync(filterStatus, prefs.dataSource)
|
||||
val filters = repo.getFiltersAsync(carContext.stringProvider())
|
||||
filtersWithValue = filtersWithValue(filters, filterValues)
|
||||
|
||||
// load chargers
|
||||
if (filterStatus.value == FILTERS_FAVORITES) {
|
||||
if (filterStatus == FILTERS_FAVORITES) {
|
||||
chargers =
|
||||
db.favoritesDao().getAllFavoritesAsync().map { it.charger }.sortedBy {
|
||||
distanceBetween(
|
||||
@@ -326,38 +345,44 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val response = api.getChargepointsRadius(
|
||||
referenceData,
|
||||
val response = repo.getChargepointsRadius(
|
||||
searchLocation,
|
||||
searchRadius,
|
||||
zoom = 16f,
|
||||
filters
|
||||
)
|
||||
chargers = response.data?.filterIsInstance(ChargeLocation::class.java)
|
||||
filtersWithValue
|
||||
).awaitFinished()
|
||||
if (response.status == Status.ERROR) {
|
||||
loadingError = true
|
||||
return@launch
|
||||
}
|
||||
var chargers = response.data?.filterIsInstance(ChargeLocation::class.java)
|
||||
chargers?.let {
|
||||
if (it.size < maxRows) {
|
||||
// try again with larger radius
|
||||
val response = api.getChargepointsRadius(
|
||||
referenceData,
|
||||
val response = repo.getChargepointsRadius(
|
||||
searchLocation,
|
||||
searchRadius * 10,
|
||||
zoom = 16f,
|
||||
filters
|
||||
)
|
||||
filtersWithValue
|
||||
).awaitFinished()
|
||||
if (response.status == Status.ERROR) {
|
||||
loadingError = true
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
chargers =
|
||||
response.data?.filterIsInstance(ChargeLocation::class.java)
|
||||
}
|
||||
}
|
||||
this@MapScreen.chargers = chargers
|
||||
}
|
||||
|
||||
updateCoroutine = null
|
||||
lastDistanceUpdateTime = Instant.now()
|
||||
invalidate()
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
CarToast.makeText(carContext, R.string.connection_error, CarToast.LENGTH_LONG)
|
||||
.show()
|
||||
}
|
||||
loadingError = true
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -370,15 +395,15 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
setupListeners()
|
||||
session.requestLocationUpdates()
|
||||
|
||||
// Reloading chargers in onStart does not seem to count towards content limit.
|
||||
// So let's do this so the user gets fresh chargers when re-entering the app.
|
||||
chargers = null
|
||||
availabilities.clear()
|
||||
invalidate()
|
||||
filtersWithValue.observe(this) {
|
||||
loadChargers()
|
||||
if (prefs.dataSource != repo.api.value?.id) {
|
||||
repo.api.value = createApi(prefs.dataSource, carContext)
|
||||
}
|
||||
invalidate()
|
||||
loadChargers()
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
@@ -398,6 +423,12 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
// Reloading chargers in onStart does not seem to count towards content limit.
|
||||
// So let's do this so the user gets fresh chargers when re-entering the app.
|
||||
// Deleting the data already in onStop makes sure that we show a loading screen directly
|
||||
// (i.e. onGetTemplate is not called while the old data is still there)
|
||||
chargers = null
|
||||
availabilities.clear()
|
||||
removeListeners()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
@@ -107,6 +108,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
screenManager.push(
|
||||
ChooseDataSourceScreen(
|
||||
carContext,
|
||||
R.string.pref_data_source,
|
||||
dataSourceNames,
|
||||
dataSourceValues,
|
||||
prefs.dataSource,
|
||||
@@ -127,6 +129,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
screenManager.push(
|
||||
ChooseDataSourceScreen(
|
||||
carContext,
|
||||
R.string.pref_search_provider,
|
||||
searchProviderNames,
|
||||
searchProviderValues,
|
||||
prefs.searchProvider
|
||||
@@ -155,6 +158,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
|
||||
class ChooseDataSourceScreen(
|
||||
ctx: CarContext,
|
||||
@StringRes val title: Int,
|
||||
val names: Array<String>,
|
||||
val values: Array<String>,
|
||||
val currentValue: String,
|
||||
@@ -165,7 +169,7 @@ class ChooseDataSourceScreen(
|
||||
|
||||
override fun onGetTemplate(): Template {
|
||||
return ListTemplate.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_data_source))
|
||||
setTitle(carContext.getString(title))
|
||||
setHeaderAction(Action.BACK)
|
||||
setSingleList(ItemList.Builder().apply {
|
||||
for (i in names.indices) {
|
||||
@@ -218,7 +222,10 @@ class ChargepriceSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
R.plurals.chargeprice_some_tariffs_selected,
|
||||
n,
|
||||
n
|
||||
) + "\n" + carContext.getString(R.string.pref_my_tariffs_summary)
|
||||
) + "\n" + carContext.resources.getQuantityString(
|
||||
R.plurals.pref_my_tariffs_summary,
|
||||
n
|
||||
)
|
||||
}
|
||||
)
|
||||
}.build())
|
||||
@@ -283,7 +290,10 @@ class ChargepriceSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
|
||||
class SelectVehiclesScreen(ctx: CarContext) : MultiSelectSearchScreen<ChargepriceCar>(ctx) {
|
||||
private val prefs = PreferenceDataSource(carContext)
|
||||
private var api = ChargepriceApi.create(carContext.getString(R.string.chargeprice_key))
|
||||
private var api = ChargepriceApi.create(
|
||||
carContext.getString(R.string.chargeprice_key),
|
||||
carContext.getString(R.string.chargeprice_api_url)
|
||||
)
|
||||
override val isMultiSelect = true
|
||||
override val shouldShowSelectAll = false
|
||||
|
||||
@@ -308,7 +318,10 @@ class SelectVehiclesScreen(ctx: CarContext) : MultiSelectSearchScreen<Chargepric
|
||||
|
||||
class SelectTariffsScreen(ctx: CarContext) : MultiSelectSearchScreen<ChargepriceTariff>(ctx) {
|
||||
private val prefs = PreferenceDataSource(carContext)
|
||||
private var api = ChargepriceApi.create(carContext.getString(R.string.chargeprice_key))
|
||||
private var api = ChargepriceApi.create(
|
||||
carContext.getString(R.string.chargeprice_key),
|
||||
carContext.getString(R.string.chargeprice_api_url)
|
||||
)
|
||||
override val isMultiSelect = true
|
||||
override val shouldShowSelectAll = true
|
||||
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string name="donations_info" formatted="false">Findest du EVMap nützlich? Unterstütze die Weiterentwicklung der App mit einer Spende an den Entwickler.\n\nGoogle zieht von der Spende 15% Gebühren ab.</string>
|
||||
<string name="auto_location_service">EVMap läuft unter Android Auto und nutzt dafür deinen Standort.</string>
|
||||
<string name="auto_no_chargers_found">Keine Ladestationen in der Nähe gefunden</string>
|
||||
37
app/src/google/res/values-fr/strings.xml
Normal file
37
app/src/google/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Trouvez-vous EVMap utile \? Soutenez son développement en envoyant un don au développeur.
|
||||
\n
|
||||
\nGoogle prend 15% sur chaque don.</string>
|
||||
<string name="auto_location_service">EVMap fonctionne sur Android Auto et utilise votre position.</string>
|
||||
<string name="open_in_app">Ouvrir dans l\'application</string>
|
||||
<string name="opened_on_phone">Ouvert sur le téléphone</string>
|
||||
<string name="auto_location_permission_needed">Pour exécuter EVMap sur Android Auto, vous devez autoriser l\'accès à votre emplacement.</string>
|
||||
<string name="grant_on_phone">Grant au téléphone</string>
|
||||
<string name="auto_prices">Prix</string>
|
||||
<string name="auto_vehicle_data">Données sur le véhicule</string>
|
||||
<string name="auto_range">Autonomie</string>
|
||||
<string name="auto_speed">Vitesse</string>
|
||||
<string name="welcome_android_auto">Prise en charge d’Android Auto</string>
|
||||
<string name="sounds_cool">ça a l\'air cool</string>
|
||||
<string name="auto_chargeprice_vehicle_unknown">Aucun des véhicules sélectionnés dans l\'application ne correspond à ce véhicule (%1$s %2$s).</string>
|
||||
<string name="auto_chargeprice_vehicle_ambiguous">Plusieurs véhicules sélectionnés dans l\'application correspondent à ce véhicule (%1$s %2$s).</string>
|
||||
<string name="selecting_all">tous les éléments sélectionnés</string>
|
||||
<string name="data_sources_hint">Dans les paramètres, vous pouvez également choisir entre Google Maps et OpenStreetMap (Mapbox) pour les données cartographiques.</string>
|
||||
<string name="auto_chargeprice_vehicle_unavailable">EVMap n\'a pas pu déterminer le modèle de votre véhicule.</string>
|
||||
<string name="auto_no_chargers_found">Aucun chargeur à proximité n\'a été trouvé</string>
|
||||
<string name="auto_no_favorites_found">Pas de favoris trouvés</string>
|
||||
<string name="auto_charging_level">Niveau de charge</string>
|
||||
<string name="auto_chargers_closeby">Chargeurs à proximité</string>
|
||||
<string name="auto_chargers_near_location">Près de %s</string>
|
||||
<string name="auto_fault_report_date">⚠️ Rapport d\'anomalie (%s)</string>
|
||||
<string name="auto_no_data">Indisponible</string>
|
||||
<string name="auto_settings">Paramètres</string>
|
||||
<string name="selecting_none">désélectionner tous les éléments</string>
|
||||
<string name="auto_vehicle_data_permission_needed">Pour cette fonction, EVMap doit avoir accès aux données de votre véhicule.</string>
|
||||
<string name="auto_heading">Direction</string>
|
||||
<string name="auto_favorites">Favoris</string>
|
||||
<string name="auto_no_refresh_possible">D\'autres mises à jour ne sont pas possibles. Veuillez revenir en arrière et redémarrer.</string>
|
||||
<string name="settings_android_auto_chargeprice_range">Plage de charge pour la comparaison des prix</string>
|
||||
<string name="welcome_android_auto_detail">Vous pouvez également utiliser EVMap à partir d\'Android Auto sur les voitures prises en charge. Il suffit de sélectionner l\'application EVMap dans le menu Android Auto.</string>
|
||||
</resources>
|
||||
37
app/src/google/res/values-nb-rNO/strings.xml
Normal file
37
app/src/google/res/values-nb-rNO/strings.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Synes du EVMap er nyttig\? Støtt utviklingen ved å sende penger til utvikleren.
|
||||
\n
|
||||
\nGoogle tar 15% av alle donasjoner.</string>
|
||||
<string name="auto_favorites">Favoritter</string>
|
||||
<string name="auto_charging_level">Ladingsnivå</string>
|
||||
<string name="auto_chargeprice_vehicle_unavailable">EVMap kunne ikke fastsette kjøretøymodellen.</string>
|
||||
<string name="selecting_none">fravalgte alle elementer</string>
|
||||
<string name="grant_on_phone">Innvilg på mobilenheten</string>
|
||||
<string name="auto_chargers_closeby">Ladere i nærheten</string>
|
||||
<string name="auto_prices">Pris</string>
|
||||
<string name="auto_no_chargers_found">Ingen ladere i nærheten</string>
|
||||
<string name="auto_no_favorites_found">Fant ikke noen favoritter</string>
|
||||
<string name="open_in_app">Åpne i programmet</string>
|
||||
<string name="auto_location_service">EVMap kjører på Android Auto og bruker posisjonen din.</string>
|
||||
<string name="auto_heading">Fartsretning</string>
|
||||
<string name="opened_on_phone">Åpnet på mobilenheten</string>
|
||||
<string name="auto_location_permission_needed">Innvilg posisjonstilgang for å bruke EVMap på Android Auto.</string>
|
||||
<string name="auto_chargers_near_location">Nær %s</string>
|
||||
<string name="auto_fault_report_date">⚠️ Feilrapport (%s)</string>
|
||||
<string name="auto_vehicle_data">Kjøretøydata</string>
|
||||
<string name="auto_no_data">Utilgjengelig</string>
|
||||
<string name="auto_speed">Hastighet</string>
|
||||
<string name="auto_settings">Innstillinger</string>
|
||||
<string name="auto_chargeprice_vehicle_unknown">Ingen av kjøretøyene valgt i programmet samsvarer med dette kjøretøyet (%1$s %2$s).</string>
|
||||
<string name="welcome_android_auto">Android Auto-støtte</string>
|
||||
<string name="auto_chargeprice_vehicle_ambiguous">Flere kjøretøy valgt i programmet samsvarer med dette kjøretøyet (%1$s %2$s).</string>
|
||||
<string name="auto_vehicle_data_permission_needed">EvMap trenger tilgang til kjøretøydata for å bruke denne funksjonen.</string>
|
||||
<string name="auto_no_refresh_possible">Videre oppdateringer er ikke mulig. Gå tilbake og start på ny.</string>
|
||||
<string name="auto_range">Rekkevidde</string>
|
||||
<string name="welcome_android_auto_detail">Du kan også bruke EVMap inne i Android Auto på bilder som støtter dette ved å velge det i Android Auto-menyen.</string>
|
||||
<string name="settings_android_auto_chargeprice_range">Prissammenligning for laderekkevidde fordelt på pris</string>
|
||||
<string name="data_sources_hint">I innstillingene kan du også bytte mellom Google Maps og OpenStreetMap (Mapbox) for kartdata.</string>
|
||||
<string name="selecting_all">valgte alle elementene</string>
|
||||
<string name="sounds_cool">den er grei</string>
|
||||
</resources>
|
||||
19
app/src/google/res/values/arrays.xml
Normal file
19
app/src/google/res/values/arrays.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>@string/pref_provider_google_maps</item>
|
||||
<item>@string/pref_provider_osm_mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" translatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>@string/pref_provider_google_maps</item>
|
||||
<item>@string/pref_provider_osm_mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" translatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -3,5 +3,5 @@
|
||||
<color name="gauge_active">#00e676</color>
|
||||
<color name="gauge_middle">#087f23</color>
|
||||
<color name="gauge_inactive">#9e9e9e</color>
|
||||
<color name="charger_100kw_dark">#fdd835</color>
|
||||
<color name="charger_100kw_dark">#FBC02D</color>
|
||||
</resources>
|
||||
5
app/src/google/res/values/donottranslate.xml
Normal file
5
app/src/google/res/values/donottranslate.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="pref_map_provider_default" translatable="false">google</string>
|
||||
<string name="pref_search_provider_default" translatable="false">mapbox</string>
|
||||
</resources>
|
||||
@@ -1,23 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" tranlatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_names">
|
||||
<item>Google Maps</item>
|
||||
<item>OpenStreetMap (Mapbox)</item>
|
||||
</string-array>
|
||||
<string-array name="pref_search_provider_values" tranlatable="false">
|
||||
<item>google</item>
|
||||
<item>mapbox</item>
|
||||
</string-array>
|
||||
<string name="pref_map_provider_default" translatable="false">google</string>
|
||||
<string name="pref_search_provider_default" translatable="false">mapbox</string>
|
||||
<string name="donations_info" formatted="false">Do you find EVMap useful? Support its development by sending a donation to the developer.\n\nGoogle takes 15% off every donation.</string>
|
||||
<string name="auto_location_service">EVMap is running on Android Auto and using your location.</string>
|
||||
<string name="auto_no_chargers_found">No nearby chargers found</string>
|
||||
@@ -45,7 +27,7 @@
|
||||
<string name="sounds_cool">sounds cool</string>
|
||||
<string name="auto_chargeprice_vehicle_unavailable">EVMap could not determine your vehicle model.</string>
|
||||
<string name="auto_chargeprice_vehicle_unknown">None of the vehicles selected in the app matches this vehicle (%1$s %2$s).</string>
|
||||
<string name="auto_chargeprice_vehicle_ambiguous">Mehrere der in der App ausgewählten Fahrzeuge passen zu diesem Fahrzeug (%1$s %2$s).</string>
|
||||
<string name="auto_chargeprice_vehicle_ambiguous">Multiple vehicles selected in the app match this vehicle (%1$s %2$s).</string>
|
||||
<string name="settings_android_auto_chargeprice_range">Charging range for price comparison</string>
|
||||
<string name="data_sources_hint">In the settings you can also switch between Google Maps and OpenStreetMap (Mapbox) for the map data.</string>
|
||||
<string name="selecting_all">selected all items</string>
|
||||
@@ -1,7 +1,6 @@
|
||||
<?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">
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.car.permission.CAR_INFO" />
|
||||
<uses-permission android:name="android.car.permission.CAR_ENERGY" />
|
||||
|
||||
5
app/src/googleAutomotive/res/values-fr/strings.xml
Normal file
5
app/src/googleAutomotive/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="grant_on_phone">Autoriser</string>
|
||||
<string name="auto_location_permission_needed">Pour exécuter EVMap sur Android Auto, vous devez autoriser l\'accès à votre emplacement.</string>
|
||||
</resources>
|
||||
5
app/src/googleAutomotive/res/values-nb-rNO/strings.xml
Normal file
5
app/src/googleAutomotive/res/values-nb-rNO/strings.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="auto_location_permission_needed">Du må du innvilge posisjonstilgang for å kjøre EVMap i bilen din.</string>
|
||||
<string name="grant_on_phone">Tillat</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="auto_location_permission_needed">To run EVMap on your car, you need to grant access to your location.</string>
|
||||
<string name="grant_on_phone">Allow</string>
|
||||
<string name="auto_location_permission_needed">To run EVMap on your car, you need to grant access to your location.</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="net.vonforst.evmap">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
@@ -24,7 +23,8 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme"
|
||||
android:localeConfig="@xml/locales_config">
|
||||
|
||||
<meta-data
|
||||
android:name="com.mapbox.ACCESS_TOKEN"
|
||||
@@ -261,6 +261,15 @@
|
||||
android:name="android.app.shortcuts"
|
||||
android:resource="@xml/shortcuts" />
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -3,6 +3,7 @@ package net.vonforst.evmap
|
||||
import android.app.Application
|
||||
import com.facebook.stetho.Stetho
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateAppLocale
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
import org.acra.config.dialog
|
||||
import org.acra.config.limiter
|
||||
@@ -13,7 +14,16 @@ import org.acra.ktx.initAcra
|
||||
class EvMapApplication : Application() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
updateNightMode(PreferenceDataSource(this))
|
||||
val prefs = PreferenceDataSource(this)
|
||||
updateNightMode(prefs)
|
||||
|
||||
// Convert to new AppCompat storage for app language
|
||||
val lang = prefs.language
|
||||
if (lang != null && lang !in listOf("", "default")) {
|
||||
updateAppLocale(lang)
|
||||
prefs.language = null
|
||||
}
|
||||
|
||||
Stetho.initializeWithDefaults(this);
|
||||
init(applicationContext)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package net.vonforst.evmap
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -32,7 +31,6 @@ import net.vonforst.evmap.fragment.MapFragmentArgs
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.navigation.NavHostFragment
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.utils.LocaleContextWrapper
|
||||
import net.vonforst.evmap.utils.getLocationFromIntent
|
||||
|
||||
|
||||
@@ -53,14 +51,6 @@ class MapsActivity : AppCompatActivity(),
|
||||
var fragmentCallback: FragmentCallback? = null
|
||||
private lateinit var prefs: PreferenceDataSource
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
return super.attachBaseContext(
|
||||
LocaleContextWrapper.wrap(
|
||||
newBase, PreferenceDataSource(newBase).language
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val splashScreen = installSplashScreen()
|
||||
@@ -86,7 +76,7 @@ class MapsActivity : AppCompatActivity(),
|
||||
ViewCompat.setOnApplyWindowInsetsListener(navView) { v, insets ->
|
||||
val header = navView.getHeaderView(0)
|
||||
header.setPadding(0, insets.getInsets(WindowInsetsCompat.Type.statusBars()).top, 0, 0)
|
||||
WindowInsetsCompat.CONSUMED
|
||||
insets
|
||||
}
|
||||
|
||||
prefs = PreferenceDataSource(this)
|
||||
|
||||
@@ -6,11 +6,6 @@ import android.graphics.Typeface
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
import android.text.style.StyleSpan
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
fun Bundle.optDouble(name: String): Double? {
|
||||
@@ -85,25 +80,6 @@ fun max(a: Int?, b: Int?): Int? {
|
||||
|
||||
fun <T> List<T>.containsAny(vararg values: T) = values.any { this.contains(it) }
|
||||
|
||||
public suspend fun <T> LiveData<T>.await(): T {
|
||||
return withContext(Dispatchers.Main.immediate) {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val observer = object : Observer<T> {
|
||||
override fun onChanged(value: T) {
|
||||
removeObserver(this)
|
||||
continuation.resume(value, null)
|
||||
}
|
||||
}
|
||||
|
||||
observeForever(observer)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isDarkMode() =
|
||||
(resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ interface ChargepointApi<out T : ReferenceData> {
|
||||
|
||||
fun getFilters(referenceData: ReferenceData, sp: StringProvider): List<Filter<FilterValue>>
|
||||
|
||||
fun getName(): String
|
||||
val name: String
|
||||
val id: String
|
||||
}
|
||||
|
||||
interface StringProvider {
|
||||
|
||||
@@ -3,14 +3,16 @@ 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 com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
|
||||
import jsonapi.Document
|
||||
import jsonapi.JsonApiFactory
|
||||
import jsonapi.retrofit.DocumentConverterFactory
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
@@ -20,34 +22,45 @@ import java.util.*
|
||||
interface ChargepriceApi {
|
||||
@POST("charge_prices")
|
||||
suspend fun getChargePrices(
|
||||
@Body request: ChargepriceRequest,
|
||||
@Body @jsonapi.retrofit.Document request: ChargepriceRequest,
|
||||
@Header("Accept-Language") language: String
|
||||
): ArrayDocument<ChargePrice>
|
||||
): Document<List<ChargePrice>>
|
||||
|
||||
@GET("vehicles")
|
||||
suspend fun getVehicles(): ArrayDocument<ChargepriceCar>
|
||||
@jsonapi.retrofit.Document
|
||||
suspend fun getVehicles(): List<ChargepriceCar>
|
||||
|
||||
@GET("tariffs")
|
||||
suspend fun getTariffs(): ArrayDocument<ChargepriceTariff>
|
||||
@jsonapi.retrofit.Document
|
||||
suspend fun getTariffs(): List<ChargepriceTariff>
|
||||
|
||||
@POST("user_feedback")
|
||||
suspend fun userFeedback(@Body @jsonapi.retrofit.Document feedback: ChargepriceUserFeedback)
|
||||
|
||||
companion object {
|
||||
private val cacheSize = 1L * 1024 * 1024 // 1MB
|
||||
val supportedLanguages = setOf("de", "en", "fr", "nl")
|
||||
|
||||
val DATA_SOURCE_GOINGELECTRIC = "going_electric"
|
||||
val DATA_SOURCE_OPENCHARGEMAP = "open_charge_map"
|
||||
private val DATA_SOURCE_GOINGELECTRIC = "going_electric"
|
||||
private val DATA_SOURCE_OPENCHARGEMAP = "open_charge_map"
|
||||
|
||||
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)
|
||||
private val jsonApiAdapterFactory = JsonApiFactory.Builder()
|
||||
.addType(ChargepriceRequest::class.java)
|
||||
.addType(ChargepriceTariff::class.java)
|
||||
.addType(ChargepriceBrand::class.java)
|
||||
.addType(ChargePrice::class.java)
|
||||
.addType(ChargepriceCar::class.java)
|
||||
.build()
|
||||
val moshi = Moshi.Builder()
|
||||
.add(jsonApiAdapterFactory)
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.add(
|
||||
PolymorphicJsonAdapterFactory.of(ChargepriceUserFeedback::class.java, "type")
|
||||
.withSubtype(ChargepriceMissingPriceFeedback::class.java, "missing_price")
|
||||
.withSubtype(ChargepriceWrongPriceFeedback::class.java, "wrong_price")
|
||||
.withSubtype(ChargepriceMissingVehicleFeedback::class.java, "missing_vehicle")
|
||||
)
|
||||
.build()
|
||||
|
||||
fun create(
|
||||
apikey: String,
|
||||
baseurl: String = "https://api.chargeprice.app/v1/",
|
||||
@@ -73,7 +86,8 @@ interface ChargepriceApi {
|
||||
|
||||
val retrofit = Retrofit.Builder()
|
||||
.baseUrl(baseurl)
|
||||
.addConverterFactory(JsonApiConverterFactory.create(moshi))
|
||||
.addConverterFactory(DocumentConverterFactory.create())
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.client(client)
|
||||
.build()
|
||||
return retrofit.create(ChargepriceApi::class.java)
|
||||
@@ -89,10 +103,19 @@ interface ChargepriceApi {
|
||||
}
|
||||
}
|
||||
|
||||
fun getPoiUrl(charger: ChargeLocation) =
|
||||
"https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=${getDataAdapter(charger)}"
|
||||
|
||||
fun getDataAdapter(charger: ChargeLocation) = when (charger.dataSource) {
|
||||
"goingelectric" -> DATA_SOURCE_GOINGELECTRIC
|
||||
"openchargemap" -> DATA_SOURCE_OPENCHARGEMAP
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isCountrySupported(country: String, dataSource: String): Boolean = when (dataSource) {
|
||||
// list of countries updated 2021/08/24
|
||||
"goingelectric" -> country in listOf(
|
||||
// list of countries according to Chargeprice.app, 2021/08/24
|
||||
"Deutschland",
|
||||
"Österreich",
|
||||
"Schweiz",
|
||||
@@ -110,9 +133,28 @@ interface ChargepriceApi {
|
||||
"Italien",
|
||||
"Spanien",
|
||||
"Großbritannien",
|
||||
"Irland"
|
||||
"Irland",
|
||||
// additional countries found 2022/09/17, https://github.com/johan12345/EVMap/issues/234
|
||||
"Finnland",
|
||||
"Lettland",
|
||||
"Litauen",
|
||||
"Estland",
|
||||
"Liechtenstein",
|
||||
"Rumänien",
|
||||
"Slowakei",
|
||||
"Slowenien",
|
||||
"Polen",
|
||||
"Serbien",
|
||||
"Bulgarien",
|
||||
"Kosovo",
|
||||
"Montenegro",
|
||||
"Albanien",
|
||||
"Griechenland",
|
||||
"Portugal",
|
||||
"Island"
|
||||
)
|
||||
"openchargemap" -> country in listOf(
|
||||
// list of countries according to Chargeprice.app, 2021/08/24
|
||||
"DE",
|
||||
"AT",
|
||||
"CH",
|
||||
@@ -130,7 +172,25 @@ interface ChargepriceApi {
|
||||
"IT",
|
||||
"ES",
|
||||
"GB",
|
||||
"IE"
|
||||
"IE",
|
||||
// additional countries found 2022/09/17, https://github.com/johan12345/EVMap/issues/234
|
||||
"FI",
|
||||
"LV",
|
||||
"LT",
|
||||
"EE",
|
||||
"LI",
|
||||
"RO",
|
||||
"SK",
|
||||
"SI",
|
||||
"PL",
|
||||
"RS",
|
||||
"BG",
|
||||
"XK",
|
||||
"ME",
|
||||
"AL",
|
||||
"GR",
|
||||
"PT",
|
||||
"IS"
|
||||
)
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package net.vonforst.evmap.api.chargeprice
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.util.Patterns
|
||||
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 com.squareup.moshi.JsonClass
|
||||
import jsonapi.*
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.Equatable
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
@@ -17,16 +17,21 @@ 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
|
||||
}
|
||||
@Resource("charge_price_request")
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceRequest(
|
||||
@Json(name = "data_adapter")
|
||||
val dataAdapter: String,
|
||||
val station: ChargepriceStation,
|
||||
val options: ChargepriceOptions,
|
||||
@ToMany("tariffs")
|
||||
val tariffs: List<ChargepriceTariff>? = null,
|
||||
@ToOne("vehicle")
|
||||
val vehicle: ChargepriceCar? = null,
|
||||
@RelationshipsObject var relationships: Relationships? = null
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceStation(
|
||||
val longitude: Double,
|
||||
val latitude: Double,
|
||||
@@ -56,11 +61,13 @@ data class ChargepriceStation(
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceChargepoint(
|
||||
val power: Double,
|
||||
val plug: String
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceOptions(
|
||||
@Json(name = "max_monthly_fees") val maxMonthlyFees: Double? = null,
|
||||
val energy: Double? = null,
|
||||
@@ -73,142 +80,107 @@ data class ChargepriceOptions(
|
||||
@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
|
||||
}
|
||||
@Resource("tariff")
|
||||
@Parcelize
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceTariff(
|
||||
@Id val id_: String?,
|
||||
val provider: String,
|
||||
val name: String,
|
||||
@Json(name = "direct_payment")
|
||||
val directPayment: Boolean = false,
|
||||
@Json(name = "provider_customer_tariff")
|
||||
val providerCustomerTariff: Boolean = false,
|
||||
@Json(name = "supported_countries")
|
||||
val supportedCountries: Set<String>,
|
||||
@Json(name = "charge_card_id")
|
||||
val chargeCardId: String?, // GE charge card ID
|
||||
) : Parcelable {
|
||||
val id: String
|
||||
get() = id_!!
|
||||
}
|
||||
|
||||
@JsonApi(type = "car")
|
||||
class ChargepriceCar : Resource(), Equatable {
|
||||
lateinit var name: String
|
||||
lateinit var brand: String
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Resource("car")
|
||||
@Parcelize
|
||||
data class ChargepriceCar(
|
||||
@Id val id_: String?,
|
||||
val name: String,
|
||||
val brand: String,
|
||||
|
||||
@field:Json(name = "dc_charge_ports")
|
||||
lateinit var dcChargePorts: List<String>
|
||||
lateinit var manufacturer: HasOne<ChargepriceBrand>
|
||||
@Json(name = "dc_charge_ports")
|
||||
val dcChargePorts: List<String>
|
||||
) : Equatable, Parcelable {
|
||||
|
||||
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
|
||||
companion object {
|
||||
private val acConnectors = listOf(
|
||||
Chargepoint.CEE_BLAU,
|
||||
Chargepoint.CEE_ROT,
|
||||
Chargepoint.SCHUKO,
|
||||
Chargepoint.TYPE_1,
|
||||
Chargepoint.TYPE_2_UNKNOWN,
|
||||
Chargepoint.TYPE_2_SOCKET,
|
||||
Chargepoint.TYPE_2_PLUG
|
||||
)
|
||||
private val plugMapping = mapOf(
|
||||
"ccs" to Chargepoint.CCS_UNKNOWN,
|
||||
"tesla_suc" to Chargepoint.SUPERCHARGER,
|
||||
"tesla_ccs" to Chargepoint.CCS_UNKNOWN,
|
||||
"chademo" to Chargepoint.CHADEMO
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
val id: String
|
||||
get() = id_!!
|
||||
|
||||
private val acConnectors = listOf(
|
||||
Chargepoint.CEE_BLAU,
|
||||
Chargepoint.CEE_ROT,
|
||||
Chargepoint.SCHUKO,
|
||||
Chargepoint.TYPE_1,
|
||||
Chargepoint.TYPE_2_UNKNOWN,
|
||||
Chargepoint.TYPE_2_SOCKET,
|
||||
Chargepoint.TYPE_2_PLUG
|
||||
)
|
||||
private val plugMapping = mapOf(
|
||||
"ccs" to Chargepoint.CCS_UNKNOWN,
|
||||
"tesla_suc" to Chargepoint.SUPERCHARGER,
|
||||
"tesla_ccs" to Chargepoint.CCS_UNKNOWN,
|
||||
"chademo" to Chargepoint.CHADEMO
|
||||
)
|
||||
val compatibleEvmapConnectors: List<String>
|
||||
get() = dcChargePorts.map {
|
||||
plugMapping[it]
|
||||
}.filterNotNull().plus(acConnectors)
|
||||
}
|
||||
|
||||
@JsonApi(type = "brand")
|
||||
class ChargepriceBrand : Resource()
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Resource("brand")
|
||||
@Parcelize
|
||||
data class ChargepriceBrand(
|
||||
@Id val id: String?
|
||||
) : Parcelable
|
||||
|
||||
@JsonApi(type = "charge_price")
|
||||
class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
lateinit var provider: String
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Resource("charge_price")
|
||||
@Parcelize
|
||||
data class ChargePrice(
|
||||
val provider: String,
|
||||
@Json(name = "tariff_name")
|
||||
val tariffName: String,
|
||||
val url: String,
|
||||
@Json(name = "monthly_min_sales")
|
||||
val monthlyMinSales: Double = 0.0,
|
||||
@Json(name = "total_monthly_fee")
|
||||
val totalMonthlyFee: Double = 0.0,
|
||||
@Json(name = "flat_rate")
|
||||
val flatRate: Boolean = false,
|
||||
|
||||
@field:Json(name = "tariff_name")
|
||||
lateinit var tariffName: String
|
||||
lateinit var url: String
|
||||
@Json(name = "direct_payment")
|
||||
val directPayment: Boolean = false,
|
||||
|
||||
@field:Json(name = "monthly_min_sales")
|
||||
var monthlyMinSales: Double = 0.0
|
||||
@Json(name = "provider_customer_tariff")
|
||||
val providerCustomerTariff: Boolean = false,
|
||||
val currency: String,
|
||||
|
||||
@field:Json(name = "total_monthly_fee")
|
||||
var totalMonthlyFee: Double = 0.0
|
||||
@Json(name = "start_time")
|
||||
val startTime: Int = 0,
|
||||
val tags: List<ChargepriceTag>,
|
||||
|
||||
@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>
|
||||
|
||||
@field:Json(name = "branding")
|
||||
var branding: ChargepriceBranding? = null
|
||||
|
||||
var tariff: HasOne<ChargepriceTariff>? = null
|
||||
@Json(name = "charge_point_prices")
|
||||
val chargepointPrices: List<ChargepointPrice>,
|
||||
|
||||
@Json(name = "branding")
|
||||
val branding: ChargepriceBranding? = null,
|
||||
|
||||
@ToOne("tariff")
|
||||
val tariffId: String?
|
||||
) : Equatable, Cloneable, Parcelable {
|
||||
fun formatMonthlyFees(ctx: Context): String {
|
||||
return listOfNotNull(
|
||||
if (totalMonthlyFee > 0) {
|
||||
@@ -219,69 +191,10 @@ class ChargePrice : Resource(), Equatable, Cloneable {
|
||||
} 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
|
||||
if (branding != other.branding) 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()
|
||||
result = 31 * result + branding.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
|
||||
branding = this@ChargePrice.branding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
data class ChargepointPrice(
|
||||
val power: Double,
|
||||
val plug: String,
|
||||
@@ -289,7 +202,7 @@ data class ChargepointPrice(
|
||||
@Json(name = "price_distribution") val priceDistribution: PriceDistribution,
|
||||
@Json(name = "blocking_fee_start") val blockingFeeStart: Int?,
|
||||
@Json(name = "no_price_reason") var noPriceReason: String?
|
||||
) {
|
||||
) : Parcelable {
|
||||
fun formatDistribution(ctx: Context): String {
|
||||
fun percent(value: Double): String {
|
||||
return ctx.getString(R.string.percent_format, value * 100) + "\u00a0"
|
||||
@@ -332,19 +245,28 @@ data class ChargepointPrice(
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
data class ChargepriceBranding(
|
||||
@Json(name = "background_color") val backgroundColor: String,
|
||||
@Json(name = "text_color") val textColor: String,
|
||||
@Json(name = "logo_url") val logoUrl: String
|
||||
)
|
||||
) : Parcelable
|
||||
|
||||
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)
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
data class PriceDistribution(val kwh: Double?, val session: Double?, val minute: Double?) :
|
||||
Parcelable {
|
||||
val isOnlyKwh
|
||||
get() = 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
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
data class ChargepriceTag(val kind: String, val text: String, val url: String?) : Equatable,
|
||||
Parcelable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceMeta(
|
||||
@Json(name = "charge_points") val chargePoints: List<ChargepriceChargepointMeta>
|
||||
)
|
||||
@@ -358,13 +280,97 @@ enum class ChargepriceInclude {
|
||||
EXCLUSIVE
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Parcelize
|
||||
data class ChargepriceRequestTariffMeta(
|
||||
val include: ChargepriceInclude
|
||||
)
|
||||
) : Parcelable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ChargepriceChargepointMeta(
|
||||
val power: Double,
|
||||
val plug: String,
|
||||
val energy: Double,
|
||||
val duration: Double
|
||||
)
|
||||
)
|
||||
|
||||
@Resource("user_feedback")
|
||||
sealed class ChargepriceUserFeedback(
|
||||
val notes: String,
|
||||
val email: String,
|
||||
val context: String,
|
||||
val language: String
|
||||
) {
|
||||
init {
|
||||
if (email.isBlank() || email.length > 100 || !Patterns.EMAIL_ADDRESS.matcher(email)
|
||||
.matches()
|
||||
) {
|
||||
throw IllegalArgumentException("invalid email")
|
||||
}
|
||||
if (!ChargepriceApi.supportedLanguages.contains(language)) {
|
||||
throw IllegalArgumentException("invalid language")
|
||||
}
|
||||
if (context.length > 500) throw IllegalArgumentException("invalid context")
|
||||
if (notes.length > 1000) throw IllegalArgumentException("invalid notes")
|
||||
}
|
||||
}
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Resource(type = "missing_price")
|
||||
class ChargepriceMissingPriceFeedback(
|
||||
val tariff: String,
|
||||
val cpo: String,
|
||||
val price: String,
|
||||
@Json(name = "poi_link") val poiLink: String,
|
||||
notes: String,
|
||||
email: String,
|
||||
context: String,
|
||||
language: String
|
||||
) : ChargepriceUserFeedback(notes, email, context, language) {
|
||||
init {
|
||||
if (tariff.isBlank() || tariff.length > 100) throw IllegalArgumentException("invalid tariff")
|
||||
if (cpo.length > 200) throw IllegalArgumentException("invalid cpo")
|
||||
if (price.isBlank() || price.length > 100) throw IllegalArgumentException("invalid price")
|
||||
if (poiLink.isBlank() || poiLink.length > 200) throw IllegalArgumentException("invalid poiLink")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Resource(type = "wrong_price")
|
||||
class ChargepriceWrongPriceFeedback(
|
||||
val tariff: String,
|
||||
val cpo: String,
|
||||
@Json(name = "displayed_price") val displayedPrice: String,
|
||||
@Json(name = "actual_price") val actualPrice: String,
|
||||
@Json(name = "poi_link") val poiLink: String,
|
||||
notes: String,
|
||||
email: String,
|
||||
context: String,
|
||||
language: String,
|
||||
) : ChargepriceUserFeedback(notes, email, context, language) {
|
||||
init {
|
||||
if (tariff.length > 100) throw IllegalArgumentException("invalid tariff")
|
||||
if (cpo.length > 200) throw IllegalArgumentException("invalid cpo")
|
||||
if (displayedPrice.length > 100) throw IllegalArgumentException("invalid displayedPrice")
|
||||
if (actualPrice.length > 100) throw IllegalArgumentException("invalid actualPrice")
|
||||
if (poiLink.length > 200) throw IllegalArgumentException("invalid poiLink")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
@Resource(type = "missing_vehicle")
|
||||
class ChargepriceMissingVehicleFeedback(
|
||||
val brand: String,
|
||||
val model: String,
|
||||
notes: String,
|
||||
email: String,
|
||||
context: String,
|
||||
language: String,
|
||||
) : ChargepriceUserFeedback(notes, email, context, language) {
|
||||
init {
|
||||
if (brand.length > 100) throw IllegalArgumentException("invalid brand")
|
||||
if (model.length > 100) throw IllegalArgumentException("invalid model")
|
||||
}
|
||||
}
|
||||
@@ -129,7 +129,8 @@ class GoingElectricApiWrapper(
|
||||
private val clusterThreshold = 11f
|
||||
val api = GoingElectricApi.create(apikey, baseurl, context)
|
||||
|
||||
override fun getName() = "GoingElectric.de"
|
||||
override val name = "GoingElectric.de"
|
||||
override val id = "going_electric"
|
||||
|
||||
override suspend fun getChargepoints(
|
||||
referenceData: ReferenceData,
|
||||
@@ -467,17 +468,17 @@ class GoingElectricApiWrapper(
|
||||
sp.getString(R.string.filter_networks), "networks",
|
||||
networkMap, manyChoices = true
|
||||
),
|
||||
MultipleChoiceFilter(
|
||||
sp.getString(R.string.categories), "categories",
|
||||
categoryMap,
|
||||
manyChoices = true
|
||||
),
|
||||
BooleanFilter(sp.getString(R.string.filter_exclude_faults), "exclude_faults"),
|
||||
BooleanFilter(sp.getString(R.string.filter_barrierfree), "barrierfree"),
|
||||
MultipleChoiceFilter(
|
||||
sp.getString(R.string.filter_chargecards), "chargecards",
|
||||
chargecardMap, manyChoices = true
|
||||
),
|
||||
BooleanFilter(sp.getString(R.string.filter_exclude_faults), "exclude_faults")
|
||||
MultipleChoiceFilter(
|
||||
sp.getString(R.string.categories), "categories",
|
||||
categoryMap,
|
||||
manyChoices = true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,7 +108,8 @@ class OpenChargeMapApiWrapper(
|
||||
private val clusterThreshold = 11
|
||||
val api = OpenChargeMapApi.create(apikey, baseurl, context)
|
||||
|
||||
override fun getName() = "OpenChargeMap.org"
|
||||
override val name = "OpenChargeMap.org"
|
||||
override val id = "open_charge_map"
|
||||
|
||||
private fun formatMultipleChoice(value: MultipleChoiceFilterValue?) =
|
||||
if (value == null || value.all) null else value.values.joinToString(",")
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.view.LayoutInflater
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
@@ -17,6 +16,7 @@ import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.recyclerview.widget.DividerItemDecoration
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialContainerTransform
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
@@ -24,6 +24,7 @@ import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.ChargepriceAdapter
|
||||
import net.vonforst.evmap.adapter.CheckableChargepriceCarAdapter
|
||||
import net.vonforst.evmap.adapter.CheckableConnectorAdapter
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceCar
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
import net.vonforst.evmap.databinding.FragmentChargepriceBinding
|
||||
@@ -31,7 +32,7 @@ import net.vonforst.evmap.model.Chargepoint
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.viewmodel.ChargepriceViewModel
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
import net.vonforst.evmap.viewmodel.savedStateViewModelFactory
|
||||
import java.text.NumberFormat
|
||||
|
||||
class ChargepriceFragment : Fragment() {
|
||||
@@ -39,10 +40,12 @@ class ChargepriceFragment : Fragment() {
|
||||
private var connectionErrorSnackbar: Snackbar? = null
|
||||
|
||||
private val vm: ChargepriceViewModel by viewModels(factoryProducer = {
|
||||
viewModelFactory {
|
||||
savedStateViewModelFactory { state ->
|
||||
ChargepriceViewModel(
|
||||
requireActivity().application,
|
||||
getString(R.string.chargeprice_key)
|
||||
getString(R.string.chargeprice_key),
|
||||
getString(R.string.chargeprice_api_url),
|
||||
state
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -60,8 +63,13 @@ class ChargepriceFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
vm.reloadPrefs()
|
||||
}
|
||||
|
||||
private fun showDonationDialog() {
|
||||
AlertDialog.Builder(requireContext())
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.chargeprice_donation_dialog_title)
|
||||
.setMessage(R.string.chargeprice_donation_dialog_detail)
|
||||
.setNegativeButton(R.string.ok) { di, _ ->
|
||||
@@ -103,9 +111,7 @@ class ChargepriceFragment : Fragment() {
|
||||
|
||||
val fragmentArgs: ChargepriceFragmentArgs by navArgs()
|
||||
val charger = fragmentArgs.charger
|
||||
val dataSource = fragmentArgs.dataSource
|
||||
vm.charger.value = charger
|
||||
vm.dataSource.value = dataSource
|
||||
if (vm.chargepoint.value == null) {
|
||||
vm.chargepoint.value = charger.chargepointsMerged.get(0)
|
||||
}
|
||||
@@ -170,7 +176,7 @@ class ChargepriceFragment : Fragment() {
|
||||
}
|
||||
|
||||
binding.imgChargepriceLogo.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl("https://www.chargeprice.app/?poi_id=${charger.id}&poi_source=${dataSource}")
|
||||
(requireActivity() as MapsActivity).openUrl(ChargepriceApi.getPoiUrl(charger))
|
||||
}
|
||||
|
||||
binding.btnSettings.setOnClickListener {
|
||||
|
||||
@@ -4,13 +4,12 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import net.vonforst.evmap.databinding.DialogDataSourceSelectBinding
|
||||
import net.vonforst.evmap.model.FILTERS_DISABLED
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import java.util.*
|
||||
import net.vonforst.evmap.ui.MaterialDialogFragment
|
||||
|
||||
class DataSourceSelectDialog : AppCompatDialogFragment() {
|
||||
class DataSourceSelectDialog : MaterialDialogFragment() {
|
||||
private lateinit var binding: DialogDataSourceSelectBinding
|
||||
var okListener: ((String) -> Unit)? = null
|
||||
|
||||
@@ -28,7 +27,7 @@ class DataSourceSelectDialog : AppCompatDialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
override fun createView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -41,16 +40,12 @@ class DataSourceSelectDialog : AppCompatDialogFragment() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
// dialog with 95% screen height
|
||||
dialog?.window?.setLayout(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
(resources.displayMetrics.heightPixels * 0.95).toInt()
|
||||
)
|
||||
setFullSize()
|
||||
}
|
||||
|
||||
private lateinit var prefs: PreferenceDataSource
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
override fun initView(view: View, savedInstanceState: Bundle?) {
|
||||
val args = requireArguments()
|
||||
binding.btnCancel.visibility =
|
||||
if (args.getBoolean("cancel_enabled")) View.VISIBLE else View.GONE
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Canvas
|
||||
import android.location.Criteria
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
@@ -29,6 +26,8 @@ import net.vonforst.evmap.adapter.DataBindingAdapter
|
||||
import net.vonforst.evmap.adapter.FavoritesAdapter
|
||||
import net.vonforst.evmap.databinding.FragmentFavoritesBinding
|
||||
import net.vonforst.evmap.databinding.ItemFavoriteBinding
|
||||
import net.vonforst.evmap.location.FusionEngine
|
||||
import net.vonforst.evmap.location.LocationEngine
|
||||
import net.vonforst.evmap.model.Favorite
|
||||
import net.vonforst.evmap.model.FavoriteWithDetail
|
||||
import net.vonforst.evmap.utils.checkAnyLocationPermission
|
||||
@@ -37,7 +36,7 @@ import net.vonforst.evmap.viewmodel.viewModelFactory
|
||||
|
||||
class FavoritesFragment : Fragment() {
|
||||
private lateinit var binding: FragmentFavoritesBinding
|
||||
private lateinit var locationManager: LocationManager
|
||||
private lateinit var locationEngine: LocationEngine
|
||||
private var toDelete: Favorite? = null
|
||||
private var deleteSnackbar: Snackbar? = null
|
||||
private lateinit var adapter: FavoritesAdapter
|
||||
@@ -54,8 +53,7 @@ class FavoritesFragment : Fragment() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
locationManager =
|
||||
requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
locationEngine = FusionEngine(requireContext())
|
||||
|
||||
enterTransition = MaterialFadeThrough()
|
||||
exitTransition = MaterialFadeThrough()
|
||||
@@ -122,11 +120,7 @@ class FavoritesFragment : Fragment() {
|
||||
super.onStart()
|
||||
|
||||
if (requireContext().checkAnyLocationPermission()) {
|
||||
val provider = locationManager.getBestProvider(Criteria().apply {
|
||||
accuracy = Criteria.ACCURACY_FINE
|
||||
}, true) ?: return
|
||||
|
||||
val location = locationManager.getLastKnownLocation(provider)
|
||||
val location = locationEngine.getLastKnownLocation()
|
||||
location?.let {
|
||||
vm.location.value = LatLng(it.latitude, it.longitude)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.location.Criteria
|
||||
import android.location.Geocoder
|
||||
import android.location.LocationManager
|
||||
import android.os.Bundle
|
||||
import android.text.method.KeyListener
|
||||
import android.view.*
|
||||
@@ -20,7 +18,6 @@ import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
@@ -52,6 +49,7 @@ import com.car2go.maps.model.BitmapDescriptor
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.Marker
|
||||
import com.car2go.maps.model.MarkerOptions
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.android.material.transition.MaterialArcMotion
|
||||
import com.google.android.material.transition.MaterialContainerTransform
|
||||
@@ -74,12 +72,13 @@ import net.vonforst.evmap.adapter.ConnectorAdapter
|
||||
import net.vonforst.evmap.adapter.DetailsAdapter
|
||||
import net.vonforst.evmap.adapter.GalleryAdapter
|
||||
import net.vonforst.evmap.adapter.PlaceAutocompleteAdapter
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
|
||||
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
|
||||
import net.vonforst.evmap.autocomplete.ApiUnavailableException
|
||||
import net.vonforst.evmap.autocomplete.PlaceWithBounds
|
||||
import net.vonforst.evmap.bold
|
||||
import net.vonforst.evmap.databinding.FragmentMapBinding
|
||||
import net.vonforst.evmap.location.FusionEngine
|
||||
import net.vonforst.evmap.location.LocationEngine
|
||||
import net.vonforst.evmap.location.Priority
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.*
|
||||
@@ -101,7 +100,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private val galleryVm: GalleryViewModel by activityViewModels()
|
||||
private var mapFragment: MapFragment? = null
|
||||
private var map: AnyMap? = null
|
||||
private lateinit var locationManager: LocationManager
|
||||
private lateinit var locationEngine: LocationEngine
|
||||
private var requestingLocationUpdates = false
|
||||
private lateinit var bottomSheetBehavior: BottomSheetBehaviorGoogleMapsLike<View>
|
||||
private lateinit var detailAppBarBehavior: MergedAppBarLayoutBehavior
|
||||
@@ -146,8 +145,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
prefs = PreferenceDataSource(requireContext())
|
||||
|
||||
locationManager =
|
||||
requireContext().getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
locationEngine = FusionEngine(requireContext())
|
||||
clusterIconGenerator = ClusterIconGenerator(requireContext())
|
||||
|
||||
enterTransition = MaterialFadeThrough()
|
||||
@@ -363,16 +361,11 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
binding.detailView.btnChargeprice.setOnClickListener {
|
||||
val charger = vm.charger.value?.data ?: return@setOnClickListener
|
||||
val dataSource = when (vm.apiType) {
|
||||
GoingElectricApiWrapper::class.java -> "going_electric"
|
||||
OpenChargeMapApiWrapper::class.java -> "open_charge_map"
|
||||
else -> throw IllegalArgumentException("unsupported data source")
|
||||
}
|
||||
val extras =
|
||||
FragmentNavigatorExtras(binding.detailView.btnChargeprice to getString(R.string.shared_element_chargeprice))
|
||||
findNavController().navigate(
|
||||
R.id.action_map_to_chargepriceFragment,
|
||||
ChargepriceFragmentArgs(charger, dataSource).toBundle(),
|
||||
ChargepriceFragmentArgs(charger).toBundle(),
|
||||
null, extras
|
||||
)
|
||||
}
|
||||
@@ -400,7 +393,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val charger = vm.charger.value?.data
|
||||
if (charger?.editUrl != null) {
|
||||
(activity as? MapsActivity)?.openUrl(charger.editUrl)
|
||||
if (vm.apiType == GoingElectricApiWrapper::class.java) {
|
||||
if (vm.apiId.value == "going_electric") {
|
||||
// instructions specific to GoingElectric
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
@@ -605,35 +598,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
updateFavoriteToggle()
|
||||
})
|
||||
vm.searchResult.observe(viewLifecycleOwner, Observer { place ->
|
||||
val map = this.map ?: return@Observer
|
||||
searchResultMarker?.remove()
|
||||
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 {
|
||||
map.animateCamera(map.cameraUpdateFactory.newLatLngZoom(place.latLng, 12f))
|
||||
}
|
||||
|
||||
if (searchResultIcon == null) {
|
||||
searchResultIcon =
|
||||
map.bitmapDescriptorFactory.fromResource(R.drawable.ic_map_marker)
|
||||
}
|
||||
searchResultMarker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.z(placeSearchZ)
|
||||
.position(place.latLng)
|
||||
.icon(searchResultIcon)
|
||||
.anchor(0.5f, 1f)
|
||||
)
|
||||
} else {
|
||||
binding.search.setText("")
|
||||
}
|
||||
|
||||
updateBackPressedCallback()
|
||||
displaySearchResult(place, moveCamera = true)
|
||||
})
|
||||
vm.layersMenuOpen.observe(viewLifecycleOwner, Observer { open ->
|
||||
binding.fabLayers.visibility = if (open) View.INVISIBLE else View.VISIBLE
|
||||
@@ -650,6 +615,40 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
updateBackPressedCallback()
|
||||
}
|
||||
|
||||
private fun displaySearchResult(place: PlaceWithBounds?, moveCamera: Boolean) {
|
||||
val map = this.map ?: return
|
||||
searchResultMarker?.remove()
|
||||
searchResultMarker = null
|
||||
|
||||
if (place != null) {
|
||||
// disable location following when search result is shown
|
||||
if (moveCamera) {
|
||||
vm.myLocationEnabled.value = false
|
||||
if (place.viewport != null) {
|
||||
map.animateCamera(map.cameraUpdateFactory.newLatLngBounds(place.viewport, 0))
|
||||
} else {
|
||||
map.animateCamera(map.cameraUpdateFactory.newLatLngZoom(place.latLng, 12f))
|
||||
}
|
||||
}
|
||||
|
||||
if (searchResultIcon == null) {
|
||||
searchResultIcon =
|
||||
map.bitmapDescriptorFactory.fromResource(R.drawable.ic_map_marker)
|
||||
}
|
||||
searchResultMarker = map.addMarker(
|
||||
MarkerOptions()
|
||||
.z(placeSearchZ)
|
||||
.position(place.latLng)
|
||||
.icon(searchResultIcon)
|
||||
.anchor(0.5f, 1f)
|
||||
)
|
||||
} else {
|
||||
binding.search.setText("")
|
||||
}
|
||||
|
||||
updateBackPressedCallback()
|
||||
}
|
||||
|
||||
private fun updateBackPressedCallback() {
|
||||
backPressedCallback.isEnabled =
|
||||
vm.bottomSheetState.value != null && vm.bottomSheetState.value != STATE_HIDDEN
|
||||
@@ -738,6 +737,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
.withStartPosition(position)
|
||||
.withHiddenStatusBar(false)
|
||||
.show()
|
||||
|
||||
}
|
||||
@@ -810,7 +810,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
it.name
|
||||
}
|
||||
}
|
||||
AlertDialog.Builder(activity)
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.charge_cards)
|
||||
.setItems(names.toTypedArray()) { _, i ->
|
||||
val card = data[i]
|
||||
@@ -969,7 +969,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
lifecycleScope.launch {
|
||||
val address = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
Geocoder(requireContext()).getFromLocationName(locationName, 1).getOrNull(0)
|
||||
Geocoder(requireContext()).getFromLocationName(locationName, 1)
|
||||
?.getOrNull(0)
|
||||
} catch (e: IOException) {
|
||||
null
|
||||
}
|
||||
@@ -1008,7 +1009,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
if (vm.searchResult.value != null) {
|
||||
// show search result (after configuration change)
|
||||
vm.searchResult.postValue(vm.searchResult.value)
|
||||
displaySearchResult(vm.searchResult.value, moveCamera = !positionSet)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1026,9 +1027,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
@RequiresPermission(anyOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
|
||||
private fun moveToLastLocation(map: AnyMap, animate: Boolean) {
|
||||
val provider = getLocationProvider() ?: return
|
||||
|
||||
val location = locationManager.getLastKnownLocation(provider)
|
||||
val location = locationEngine.getLastKnownLocation()
|
||||
if (location != null) {
|
||||
val latLng = LatLng(location.latitude, location.longitude)
|
||||
vm.location.value = latLng
|
||||
@@ -1041,10 +1040,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLocationProvider() = locationManager.getBestProvider(Criteria().apply {
|
||||
accuracy = Criteria.ACCURACY_FINE
|
||||
}, true)
|
||||
|
||||
@Synchronized
|
||||
private fun updateMap(chargepoints: List<ChargepointListItem>) {
|
||||
val map = this.map ?: return
|
||||
@@ -1264,6 +1259,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
})
|
||||
})
|
||||
popup.setTouchModal(false)
|
||||
popup.show()
|
||||
}
|
||||
|
||||
@@ -1298,12 +1294,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
@RequiresPermission(anyOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
|
||||
private fun requestLocationUpdates() {
|
||||
val provider = getLocationProvider() ?: return
|
||||
|
||||
locationManager.requestLocationUpdates(
|
||||
provider,
|
||||
locationEngine.requestLocationUpdates(
|
||||
Priority.HIGH_ACCURACY,
|
||||
5000,
|
||||
1f,
|
||||
locationListener
|
||||
)
|
||||
requestingLocationUpdates = true
|
||||
@@ -1312,7 +1305,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
@SuppressLint("MissingPermission")
|
||||
private fun removeLocationUpdates() {
|
||||
if (context?.checkAnyLocationPermission() == true) {
|
||||
locationManager.removeUpdates(locationListener)
|
||||
locationEngine.removeUpdates(locationListener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,20 +4,16 @@ 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 androidx.core.widget.doAfterTextChanged
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.DataBindingAdapter
|
||||
import net.vonforst.evmap.adapter.Equatable
|
||||
import net.vonforst.evmap.databinding.DialogMultiSelectBinding
|
||||
import net.vonforst.evmap.ui.MaterialDialogFragment
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class MultiSelectDialog : AppCompatDialogFragment() {
|
||||
class MultiSelectDialog : MaterialDialogFragment() {
|
||||
companion object {
|
||||
fun getInstance(
|
||||
title: String,
|
||||
@@ -43,7 +39,7 @@ class MultiSelectDialog : AppCompatDialogFragment() {
|
||||
private lateinit var items: List<MultiSelectItem>
|
||||
private lateinit var binding: DialogMultiSelectBinding
|
||||
|
||||
override fun onCreateView(
|
||||
override fun createView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -54,19 +50,10 @@ class MultiSelectDialog : AppCompatDialogFragment() {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
val width = resources.displayMetrics.widthPixels
|
||||
val maxWidth = (500 * density).roundToInt()
|
||||
|
||||
// dialog with 95% screen height
|
||||
dialog?.window?.setLayout(
|
||||
if (width < maxWidth) WindowManager.LayoutParams.MATCH_PARENT else maxWidth,
|
||||
(resources.displayMetrics.heightPixels * 0.95).toInt()
|
||||
)
|
||||
setFullSize(maxWidthDp = 500)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
override fun initView(view: View, savedInstanceState: Bundle?) {
|
||||
val args = requireArguments()
|
||||
val data = args.getSerializable("data") as HashMap<String, String>
|
||||
val selected = args.getSerializable("selected") as HashSet<String>
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import com.mikepenz.aboutlibraries.LibsBuilder
|
||||
@@ -44,6 +45,14 @@ class AboutFragment : PreferenceFragmentCompat() {
|
||||
|
||||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
return when (preference.key) {
|
||||
"contributors" -> {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(R.string.about_contributors)
|
||||
.setMessage(getString(R.string.about_contributors_text) + "\n\n" + getString(R.string.about_contributors_list))
|
||||
.setPositiveButton(R.string.ok) { _, _ -> }
|
||||
.show()
|
||||
true
|
||||
}
|
||||
"github_link" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.github_link))
|
||||
true
|
||||
|
||||
@@ -16,7 +16,8 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
viewModelFactory {
|
||||
SettingsViewModel(
|
||||
requireActivity().application,
|
||||
getString(R.string.chargeprice_key)
|
||||
getString(R.string.chargeprice_key),
|
||||
getString(R.string.chargeprice_api_url)
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -69,7 +70,8 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
R.plurals.chargeprice_some_tariffs_selected,
|
||||
n,
|
||||
n
|
||||
) + "\n" + getString(R.string.pref_my_tariffs_summary)
|
||||
) + "\n" + requireContext().resources
|
||||
.getQuantityString(R.plurals.pref_my_tariffs_summary, n)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@ class DataSettingsFragment : BaseSettingsFragment() {
|
||||
viewModelFactory {
|
||||
SettingsViewModel(
|
||||
requireActivity().application,
|
||||
getString(R.string.chargeprice_key)
|
||||
getString(R.string.chargeprice_key),
|
||||
getString(R.string.chargeprice_api_url)
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -2,24 +2,33 @@ package net.vonforst.evmap.fragment.preference
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import androidx.preference.ListPreference
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.ui.getAppLocale
|
||||
import net.vonforst.evmap.ui.updateAppLocale
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
|
||||
class UiSettingsFragment : BaseSettingsFragment() {
|
||||
override val isTopLevel = false
|
||||
lateinit var langPref: ListPreference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.settings_ui, rootKey)
|
||||
|
||||
langPref = findPreference("language")!!
|
||||
langPref.setOnPreferenceChangeListener { _, newValue ->
|
||||
updateAppLocale(newValue as String)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
langPref.value = getAppLocale()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
when (key) {
|
||||
"language" -> {
|
||||
activity?.let {
|
||||
it.finish();
|
||||
it.startActivity(it.intent);
|
||||
}
|
||||
}
|
||||
"darkmode" -> {
|
||||
updateNightMode(prefs)
|
||||
}
|
||||
|
||||
@@ -4,18 +4,16 @@ 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 androidx.navigation.fragment.findNavController
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.databinding.DialogOpensourceDonationsBinding
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import kotlin.math.roundToInt
|
||||
import net.vonforst.evmap.ui.MaterialDialogFragment
|
||||
|
||||
class OpensourceDonationsDialogFramgent : AppCompatDialogFragment() {
|
||||
class OpensourceDonationsDialogFragment : MaterialDialogFragment() {
|
||||
private lateinit var binding: DialogOpensourceDonationsBinding
|
||||
|
||||
override fun onCreateView(
|
||||
override fun createView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
@@ -24,9 +22,7 @@ class OpensourceDonationsDialogFramgent : AppCompatDialogFragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
override fun initView(view: View, savedInstanceState: Bundle?) {
|
||||
val prefs = PreferenceDataSource(requireContext())
|
||||
binding.btnOk.setOnClickListener {
|
||||
prefs.opensourceDonationsDialogShown = true
|
||||
@@ -44,14 +40,5 @@ class OpensourceDonationsDialogFramgent : AppCompatDialogFragment() {
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
val width = resources.displayMetrics.widthPixels
|
||||
val maxWidth = (500 * density).roundToInt()
|
||||
|
||||
dialog?.window?.setLayout(
|
||||
if (width < maxWidth) WindowManager.LayoutParams.MATCH_PARENT else maxWidth,
|
||||
WindowManager.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
}
|
||||
}
|
||||
271
app/src/main/java/net/vonforst/evmap/location/FusionEngine.kt
Normal file
271
app/src/main/java/net/vonforst/evmap/location/FusionEngine.kt
Normal file
@@ -0,0 +1,271 @@
|
||||
package net.vonforst.evmap.location
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
|
||||
/**
|
||||
* Location engine that fuses GPS and network locations.
|
||||
*
|
||||
* Simplified version of
|
||||
* https://github.com/lostzen/lost/blob/master/lost/src/main/java/com/mapzen/android/lost/internal/FusionEngine.java
|
||||
*/
|
||||
class FusionEngine(context: Context) : LocationEngine(context),
|
||||
LocationListenerCompat {
|
||||
|
||||
/**
|
||||
* Location updates more than 60 seconds old are considered stale.
|
||||
*/
|
||||
private val RECENT_UPDATE_THRESHOLD_IN_MILLIS = (60 * 1000).toLong()
|
||||
private val RECENT_UPDATE_THRESHOLD_IN_NANOS = RECENT_UPDATE_THRESHOLD_IN_MILLIS * 1000000
|
||||
private val TAG = FusionEngine::class.java.simpleName
|
||||
|
||||
private val locationManager =
|
||||
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
private var gpsLocation: Location? = null
|
||||
private var networkLocation: Location? = null
|
||||
|
||||
private val supportsSystemFusedProvider: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && locationManager.allProviders.contains(
|
||||
LocationManager.FUSED_PROVIDER
|
||||
)
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
override fun getLastKnownLocation(): Location? {
|
||||
if (supportsSystemFusedProvider) {
|
||||
try {
|
||||
return locationManager.getLastKnownLocation(LocationManager.FUSED_PROVIDER)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Permissions not granted for fused provider", e)
|
||||
}
|
||||
}
|
||||
|
||||
val minTime = SystemClock.elapsedRealtimeNanos() - RECENT_UPDATE_THRESHOLD_IN_NANOS
|
||||
var bestLocation: Location? = null
|
||||
var bestAccuracy = Float.MAX_VALUE
|
||||
var bestTime = Long.MIN_VALUE
|
||||
for (provider in locationManager.allProviders) {
|
||||
try {
|
||||
val location = locationManager.getLastKnownLocation(provider)
|
||||
if (location != null) {
|
||||
val accuracy = location.accuracy
|
||||
val time = location.elapsedRealtimeNanos
|
||||
if (time > minTime && accuracy < bestAccuracy) {
|
||||
bestLocation = location
|
||||
bestAccuracy = accuracy
|
||||
bestTime = time
|
||||
} else if (time < minTime && bestAccuracy == Float.MAX_VALUE && time > bestTime) {
|
||||
bestLocation = location
|
||||
bestTime = time
|
||||
}
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Permissions not granted for provider: $provider", e)
|
||||
}
|
||||
}
|
||||
return bestLocation
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
override fun enable() {
|
||||
var networkInterval = Long.MAX_VALUE
|
||||
var gpsInterval = Long.MAX_VALUE
|
||||
var passiveInterval = Long.MAX_VALUE
|
||||
for ((priority, interval) in requests) {
|
||||
when (priority) {
|
||||
Priority.HIGH_ACCURACY -> {
|
||||
if (interval < gpsInterval) {
|
||||
gpsInterval = interval
|
||||
}
|
||||
if (interval < networkInterval) {
|
||||
networkInterval = interval
|
||||
}
|
||||
}
|
||||
Priority.BALANCED_POWER_ACCURACY, Priority.LOW_POWER -> if (interval < networkInterval) {
|
||||
networkInterval = interval
|
||||
}
|
||||
Priority.NO_POWER -> if (interval < passiveInterval) {
|
||||
passiveInterval = interval
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (supportsSystemFusedProvider && gpsInterval < Long.MAX_VALUE) {
|
||||
try {
|
||||
enableFused(gpsInterval)
|
||||
checkLastKnownFused()
|
||||
return
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Permissions not granted for fused provider", e)
|
||||
}
|
||||
}
|
||||
|
||||
var checkGps = false
|
||||
if (gpsInterval < Long.MAX_VALUE) {
|
||||
enableGps(gpsInterval)
|
||||
checkGps = true
|
||||
}
|
||||
if (networkInterval < Long.MAX_VALUE) {
|
||||
enableNetwork(networkInterval)
|
||||
if (checkGps) {
|
||||
val lastGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER)
|
||||
val lastNetwork =
|
||||
locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
|
||||
if (lastGps != null && lastNetwork != null) {
|
||||
val useGps = lastGps.isBetterThan(lastNetwork)
|
||||
if (useGps) {
|
||||
checkLastKnownGps()
|
||||
} else {
|
||||
checkLastKnownNetwork()
|
||||
}
|
||||
} else if (lastGps != null) {
|
||||
checkLastKnownGps()
|
||||
} else {
|
||||
checkLastKnownNetwork()
|
||||
}
|
||||
} else {
|
||||
checkLastKnownNetwork()
|
||||
}
|
||||
}
|
||||
if (passiveInterval < Long.MAX_VALUE) {
|
||||
enablePassive(passiveInterval)
|
||||
checkLastKnownPassive()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
override fun disable() {
|
||||
locationManager.removeUpdates(this)
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun enableGps(interval: Long) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.GPS_PROVIDER,
|
||||
interval,
|
||||
0f,
|
||||
this,
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for GPS updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun enableNetwork(interval: Long) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.NETWORK_PROVIDER,
|
||||
interval,
|
||||
0f,
|
||||
this,
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for network updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun enablePassive(interval: Long) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.PASSIVE_PROVIDER,
|
||||
interval,
|
||||
0f,
|
||||
this,
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for passive updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun enableFused(interval: Long) {
|
||||
try {
|
||||
locationManager.requestLocationUpdates(
|
||||
LocationManager.FUSED_PROVIDER,
|
||||
interval,
|
||||
0f,
|
||||
this,
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for passive updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun checkLastKnownGps() {
|
||||
checkLastKnownAndNotify(LocationManager.GPS_PROVIDER)
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun checkLastKnownNetwork() {
|
||||
checkLastKnownAndNotify(LocationManager.NETWORK_PROVIDER)
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun checkLastKnownPassive() {
|
||||
checkLastKnownAndNotify(LocationManager.PASSIVE_PROVIDER)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.S)
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun checkLastKnownFused() {
|
||||
checkLastKnownAndNotify(LocationManager.FUSED_PROVIDER)
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
private fun checkLastKnownAndNotify(provider: String) {
|
||||
val location = locationManager.getLastKnownLocation(provider)
|
||||
location?.let { onLocationChanged(it) }
|
||||
}
|
||||
|
||||
override fun onLocationChanged(location: Location) {
|
||||
if (LocationManager.FUSED_PROVIDER == location.provider) {
|
||||
requests.forEach { it.listener.onLocationChanged(location) }
|
||||
} else if (LocationManager.GPS_PROVIDER == location.provider) {
|
||||
gpsLocation = location
|
||||
if (gpsLocation.isBetterThan(networkLocation)) {
|
||||
requests.forEach { it.listener.onLocationChanged(location) }
|
||||
}
|
||||
} else if (LocationManager.NETWORK_PROVIDER == location.provider) {
|
||||
networkLocation = location
|
||||
if (networkLocation.isBetterThan(gpsLocation)) {
|
||||
requests.forEach { it.listener.onLocationChanged(location) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Location?.isBetterThan(other: Location?): Boolean {
|
||||
if (this == null) {
|
||||
return false
|
||||
}
|
||||
if (other == null) {
|
||||
return true
|
||||
}
|
||||
if (this.elapsedRealtimeNanos
|
||||
> other.elapsedRealtimeNanos + RECENT_UPDATE_THRESHOLD_IN_NANOS
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (!this.hasAccuracy()) {
|
||||
return false
|
||||
}
|
||||
return if (!other.hasAccuracy()) {
|
||||
true
|
||||
} else this.accuracy < other.accuracy
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package net.vonforst.evmap.location
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationListener
|
||||
import android.os.Looper
|
||||
import androidx.annotation.RequiresPermission
|
||||
|
||||
/**
|
||||
* Base class for [com.mapzen.android.lost.internal.FusionEngine].
|
||||
*/
|
||||
abstract class LocationEngine(protected val context: Context) {
|
||||
protected val requests: MutableList<LocationRequest> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Return most best recent location available.
|
||||
*/
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
abstract fun getLastKnownLocation(): Location?
|
||||
|
||||
/**
|
||||
* Enables the engine on receiving a valid location request.
|
||||
*
|
||||
* @param request Valid location request to enable.
|
||||
*/
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
fun requestLocationUpdates(priority: Priority, intervalMs: Long, listener: LocationListener) {
|
||||
if (!requests.any { it.listener == listener }) {
|
||||
requests.add(LocationRequest(priority, intervalMs, listener))
|
||||
}
|
||||
enable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the engine when no requests remain, otherwise updates the engine's configuration.
|
||||
*
|
||||
* @param requests Valid location request to enable.
|
||||
*/
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
fun removeUpdates(listener: LocationListener) {
|
||||
this.requests.removeIf { it.listener == listener }
|
||||
disable()
|
||||
if (this.requests.isNotEmpty()) enable()
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
fun removeAllRequests() {
|
||||
requests.clear()
|
||||
disable()
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclass should perform all operations required to enable the engine. (ex. Register for
|
||||
* location updates.)
|
||||
*/
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
protected abstract fun enable()
|
||||
|
||||
/**
|
||||
* Subclass should perform all operations required to disable the engine. (ex. Remove location
|
||||
* updates.)
|
||||
*/
|
||||
@RequiresPermission(anyOf = [Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION])
|
||||
protected abstract fun disable()
|
||||
protected val looper: Looper
|
||||
get() = context.mainLooper
|
||||
|
||||
interface Callback {
|
||||
fun reportLocation(location: Location)
|
||||
}
|
||||
}
|
||||
|
||||
data class LocationRequest(
|
||||
val priority: Priority,
|
||||
val intervalMs: Long,
|
||||
val listener: LocationListener
|
||||
)
|
||||
|
||||
enum class Priority {
|
||||
HIGH_ACCURACY,
|
||||
BALANCED_POWER_ACCURACY,
|
||||
LOW_POWER,
|
||||
NO_POWER
|
||||
}
|
||||
@@ -167,9 +167,9 @@ data class Cost(
|
||||
fun getStatusText(ctx: Context, emoji: Boolean = false): CharSequence {
|
||||
if (freecharging != null && freeparking != null) {
|
||||
val charging =
|
||||
if (freecharging) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
if (freecharging) ctx.getString(R.string.charging_free) else ctx.getString(R.string.charging_paid)
|
||||
val parking =
|
||||
if (freeparking) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
if (freeparking) ctx.getString(R.string.parking_free) else ctx.getString(R.string.parking_paid)
|
||||
return if (emoji) {
|
||||
"⚡ $charging · \uD83C\uDD7F️ $parking"
|
||||
} else {
|
||||
@@ -177,7 +177,7 @@ data class Cost(
|
||||
}
|
||||
} else if (freecharging != null) {
|
||||
val charging =
|
||||
if (freecharging) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
if (freecharging) ctx.getString(R.string.charging_free) else ctx.getString(R.string.charging_paid)
|
||||
return if (emoji) {
|
||||
"⚡ $charging"
|
||||
} else {
|
||||
@@ -185,7 +185,7 @@ data class Cost(
|
||||
}
|
||||
} else if (freeparking != null) {
|
||||
val parking =
|
||||
if (freeparking) ctx.getString(R.string.free) else ctx.getString(R.string.paid)
|
||||
if (freeparking) ctx.getString(R.string.parking_free) else ctx.getString(R.string.parking_paid)
|
||||
return if (emoji) {
|
||||
"\uD83C\uDD7F $parking"
|
||||
} else {
|
||||
|
||||
@@ -1,34 +1,124 @@
|
||||
package net.vonforst.evmap.storage
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import androidx.lifecycle.*
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Delete
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.vonforst.evmap.api.ChargepointApi
|
||||
import net.vonforst.evmap.api.StringProvider
|
||||
import net.vonforst.evmap.api.goingelectric.GEReferenceData
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
|
||||
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.viewmodel.Resource
|
||||
import net.vonforst.evmap.viewmodel.await
|
||||
|
||||
@Dao
|
||||
interface ChargeLocationsDao {
|
||||
abstract class ChargeLocationsDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insert(vararg locations: ChargeLocation)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertBlocking(vararg locations: ChargeLocation)
|
||||
abstract suspend fun insert(vararg locations: ChargeLocation)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(vararg locations: ChargeLocation)
|
||||
abstract suspend fun delete(vararg locations: ChargeLocation)
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM chargelocation")
|
||||
fun getAllChargeLocations(): LiveData<List<ChargeLocation>>
|
||||
/**
|
||||
* The ChargeLocationsRepository wraps the ChargepointApi and the DB to provide caching
|
||||
* functionality.
|
||||
*/
|
||||
class ChargeLocationsRepository(
|
||||
api: ChargepointApi<ReferenceData>, private val scope: CoroutineScope,
|
||||
private val db: AppDatabase, private val prefs: PreferenceDataSource
|
||||
) {
|
||||
val api = MutableLiveData<ChargepointApi<ReferenceData>>().apply { value = api }
|
||||
|
||||
@Query("SELECT * FROM chargelocation")
|
||||
suspend fun getAllChargeLocationsAsync(): List<ChargeLocation>
|
||||
val referenceData = this.api.switchMap { api ->
|
||||
when (api) {
|
||||
is GoingElectricApiWrapper -> {
|
||||
GEReferenceDataRepository(
|
||||
api,
|
||||
scope,
|
||||
db.geReferenceDataDao(),
|
||||
prefs
|
||||
).getReferenceData()
|
||||
}
|
||||
is OpenChargeMapApiWrapper -> {
|
||||
OCMReferenceDataRepository(
|
||||
api,
|
||||
scope,
|
||||
db.ocmReferenceDataDao(),
|
||||
prefs
|
||||
).getReferenceData()
|
||||
}
|
||||
else -> {
|
||||
throw RuntimeException("no reference data implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Query("SELECT * FROM chargelocation")
|
||||
fun getAllChargeLocationsBlocking(): List<ChargeLocation>
|
||||
private val chargeLocationsDao = db.chargeLocationsDao()
|
||||
|
||||
@Query("SELECT * FROM chargelocation WHERE lat >= :lat1 AND lat <= :lat2 AND lng >= :lng1 AND lng <= :lng2")
|
||||
suspend fun getChargeLocationsInBoundsAsync(
|
||||
lat1: Double,
|
||||
lat2: Double,
|
||||
lng1: Double,
|
||||
lng2: Double
|
||||
): List<ChargeLocation>
|
||||
fun getChargepoints(
|
||||
bounds: LatLngBounds,
|
||||
zoom: Float,
|
||||
filters: FilterValues?
|
||||
): LiveData<Resource<List<ChargepointListItem>>> {
|
||||
return liveData {
|
||||
val refData = referenceData.await()
|
||||
val result = api.value!!.getChargepoints(refData, bounds, zoom, filters)
|
||||
|
||||
emit(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun getChargepointsRadius(
|
||||
location: LatLng,
|
||||
radius: Int,
|
||||
zoom: Float,
|
||||
filters: FilterValues?
|
||||
): LiveData<Resource<List<ChargepointListItem>>> {
|
||||
return liveData {
|
||||
val refData = referenceData.await()
|
||||
val result = api.value!!.getChargepointsRadius(refData, location, radius, zoom, filters)
|
||||
|
||||
emit(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun getChargepointDetail(
|
||||
id: Long
|
||||
): LiveData<Resource<ChargeLocation>> {
|
||||
return liveData {
|
||||
val refData = referenceData.await()
|
||||
val result = api.value!!.getChargepointDetail(refData, id)
|
||||
emit(result)
|
||||
}
|
||||
}
|
||||
|
||||
fun getFilters(sp: StringProvider) = MediatorLiveData<List<Filter<FilterValue>>>().apply {
|
||||
addSource(referenceData) { refData: ReferenceData? ->
|
||||
refData?.let { value = api.value!!.getFilters(refData, sp) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getFiltersAsync(sp: StringProvider): List<Filter<FilterValue>> {
|
||||
val refData = referenceData.await()
|
||||
return api.value!!.getFilters(refData, sp)
|
||||
}
|
||||
|
||||
val chargeCardMap by lazy {
|
||||
referenceData.map { refData: ReferenceData? ->
|
||||
if (refData is GEReferenceData) {
|
||||
refData.chargecards.associate {
|
||||
it.id to it.convert()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,31 +1,28 @@
|
||||
package net.vonforst.evmap.storage
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.room.*
|
||||
import net.vonforst.evmap.await
|
||||
import net.vonforst.evmap.model.*
|
||||
|
||||
@Dao
|
||||
abstract class FilterValueDao {
|
||||
@Query("SELECT * FROM booleanfiltervalue WHERE profile = :profile AND dataSource = :dataSource")
|
||||
protected abstract fun getBooleanFilterValues(
|
||||
protected abstract suspend fun getBooleanFilterValues(
|
||||
profile: Long,
|
||||
dataSource: String
|
||||
): LiveData<List<BooleanFilterValue>>
|
||||
): List<BooleanFilterValue>
|
||||
|
||||
@Query("SELECT * FROM multiplechoicefiltervalue WHERE profile = :profile AND dataSource = :dataSource")
|
||||
protected abstract fun getMultipleChoiceFilterValues(
|
||||
protected abstract suspend fun getMultipleChoiceFilterValues(
|
||||
profile: Long,
|
||||
dataSource: String
|
||||
): LiveData<List<MultipleChoiceFilterValue>>
|
||||
): List<MultipleChoiceFilterValue>
|
||||
|
||||
@Query("SELECT * FROM sliderfiltervalue WHERE profile = :profile AND dataSource = :dataSource")
|
||||
protected abstract fun getSliderFilterValues(
|
||||
protected abstract suspend fun getSliderFilterValues(
|
||||
profile: Long,
|
||||
dataSource: String
|
||||
): LiveData<List<SliderFilterValue>>
|
||||
): List<SliderFilterValue>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
protected abstract suspend fun insert(vararg values: BooleanFilterValue)
|
||||
@@ -54,27 +51,23 @@ abstract class FilterValueDao {
|
||||
dataSource: String
|
||||
)
|
||||
|
||||
open fun getFilterValues(filterStatus: Long, dataSource: String): LiveData<List<FilterValue>> =
|
||||
open suspend fun getFilterValuesAsync(
|
||||
filterStatus: Long,
|
||||
dataSource: String
|
||||
): List<FilterValue> =
|
||||
if (filterStatus == FILTERS_DISABLED || filterStatus == FILTERS_FAVORITES) {
|
||||
MutableLiveData(emptyList())
|
||||
emptyList()
|
||||
} else {
|
||||
MediatorLiveData<List<FilterValue>>().apply {
|
||||
val sources = listOf(
|
||||
getBooleanFilterValues(filterStatus, dataSource),
|
||||
getMultipleChoiceFilterValues(filterStatus, dataSource),
|
||||
getBooleanFilterValues(filterStatus, dataSource) +
|
||||
getMultipleChoiceFilterValues(filterStatus, dataSource) +
|
||||
getSliderFilterValues(filterStatus, dataSource)
|
||||
)
|
||||
for (source in sources) {
|
||||
addSource(source) {
|
||||
val values = sources.map { it.value }
|
||||
if (values.all { it != null }) {
|
||||
value = values.filterNotNull().flatten()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun getFilterValues(filterStatus: Long, dataSource: String) = liveData {
|
||||
emit(null)
|
||||
emit(getFilterValuesAsync(filterStatus, dataSource))
|
||||
}
|
||||
|
||||
@Transaction
|
||||
open suspend fun insert(vararg values: FilterValue) {
|
||||
values.forEach {
|
||||
@@ -98,7 +91,7 @@ abstract class FilterValueDao {
|
||||
if (filterStatus == FILTERS_CUSTOM) return
|
||||
|
||||
deleteFilterValuesForProfile(FILTERS_CUSTOM, dataSource)
|
||||
val values = getFilterValues(filterStatus, dataSource).await().onEach {
|
||||
val values = getFilterValuesAsync(filterStatus, dataSource).onEach {
|
||||
it.profile = FILTERS_CUSTOM
|
||||
}
|
||||
insert(*values.toTypedArray())
|
||||
|
||||
@@ -87,6 +87,7 @@ class GEReferenceDataRepository(
|
||||
val networks = dao.getAllNetworks()
|
||||
val chargeCards = dao.getAllChargeCards()
|
||||
return MediatorLiveData<GEReferenceData>().apply {
|
||||
value = null
|
||||
listOf(chargeCards, networks, plugs).map { source ->
|
||||
addSource(source) { _ ->
|
||||
val p = plugs.value ?: return@addSource
|
||||
|
||||
@@ -79,6 +79,7 @@ class OCMReferenceDataRepository(
|
||||
val countries = dao.getAllCountries()
|
||||
val operators = dao.getAllOperators()
|
||||
return MediatorLiveData<OCMReferenceData>().apply {
|
||||
value = null
|
||||
listOf(countries, connectionTypes, operators).map { source ->
|
||||
addSource(source) { _ ->
|
||||
val ct = connectionTypes.value
|
||||
|
||||
@@ -75,8 +75,15 @@ class PreferenceDataSource(val context: Context) {
|
||||
}
|
||||
|
||||
|
||||
val language: String
|
||||
get() = sp.getString("language", "default")!!
|
||||
/**
|
||||
* Sets app language. Will be removed and set to null with the next update because storage is
|
||||
* handled by AppCompat.
|
||||
*/
|
||||
var language: String?
|
||||
get() = sp.getString("language", null)
|
||||
set(lang) {
|
||||
sp.edit().putString("language", lang).apply()
|
||||
}
|
||||
|
||||
val darkmode: String
|
||||
get() = sp.getString("darkmode", "default")!!
|
||||
|
||||
@@ -32,7 +32,7 @@ fun View.exitCircularReveal(block: () -> Unit) {
|
||||
duration = 350
|
||||
interpolator = DecelerateInterpolator(1f)
|
||||
addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator?) {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
visibility = View.INVISIBLE
|
||||
block()
|
||||
super.onAnimationEnd(animation)
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
fun updateNightMode(prefs: PreferenceDataSource) {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
when (prefs.darkmode) {
|
||||
"on" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
"off" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun updateAppLocale(language: String) {
|
||||
AppCompatDelegate.setApplicationLocales(
|
||||
if (language in listOf("", "default")) {
|
||||
LocaleListCompat.getEmptyLocaleList()
|
||||
} else {
|
||||
LocaleListCompat.forLanguageTags(language)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun getAppLocale(): String? {
|
||||
val locales = AppCompatDelegate.getApplicationLocales()
|
||||
return if (locales.isEmpty) {
|
||||
"default"
|
||||
} else {
|
||||
val arr = Array(locales.size()) { locales.get(it)!!.toLanguageTag() }
|
||||
LocaleListCompat.forLanguageTags(BuildConfig.supportedLocales).getFirstMatch(arr)
|
||||
?.toLanguageTag()
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.view.Gravity
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private fun dialogEditText(ctx: Context): Pair<View, EditText> {
|
||||
val container = FrameLayout(ctx)
|
||||
@@ -24,30 +30,19 @@ private fun dialogEditText(ctx: Context): Pair<View, EditText> {
|
||||
|
||||
fun showEditTextDialog(
|
||||
ctx: Context,
|
||||
customize: (AlertDialog.Builder, EditText) -> Unit
|
||||
customize: (MaterialAlertDialogBuilder, EditText) -> Unit
|
||||
): AlertDialog {
|
||||
val (container, input) = dialogEditText(ctx)
|
||||
val dialogBuilder = AlertDialog.Builder(ctx)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(ctx)
|
||||
.setView(container)
|
||||
|
||||
customize(dialogBuilder, input)
|
||||
|
||||
val dialog = dialogBuilder.show()
|
||||
|
||||
|
||||
// move dialog to top
|
||||
val attrs = dialog.window?.attributes?.apply {
|
||||
gravity = Gravity.TOP
|
||||
}
|
||||
dialog.window?.attributes = attrs
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
|
||||
// focus and show keyboard
|
||||
input.requestFocus()
|
||||
input.postDelayed({
|
||||
val imm =
|
||||
ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT)
|
||||
}, 100)
|
||||
input.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val text = input.text
|
||||
@@ -60,4 +55,63 @@ fun showEditTextDialog(
|
||||
false
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
/**
|
||||
* DialogFragment that uses Material styling.
|
||||
* This needs a bit of a workaround, see also
|
||||
* https://github.com/material-components/material-components-android/issues/540 and
|
||||
* https://dev.to/bhullnatik/how-to-use-material-dialogs-with-dialogfragment-28i1
|
||||
*/
|
||||
abstract class MaterialDialogFragment : AppCompatDialogFragment() {
|
||||
|
||||
private lateinit var dialogView: View
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext(), theme).apply {
|
||||
dialogView =
|
||||
createView(LayoutInflater.from(requireContext()), null, savedInstanceState)
|
||||
|
||||
setView(dialogView)
|
||||
}.create()
|
||||
initView(dialogView, savedInstanceState)
|
||||
return dialog
|
||||
}
|
||||
|
||||
abstract fun createView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View
|
||||
|
||||
abstract fun initView(view: View, savedInstanceState: Bundle?)
|
||||
|
||||
override fun getView(): View {
|
||||
return dialogView
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
// make sure that custom view fills whole dialog height
|
||||
(view.parent as View).layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
(view.parent.parent as View).layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT
|
||||
(view.parent.parent.parent as View).layoutParams.height =
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes the dialog fill the whole width & height of the screen, with an optional maximum
|
||||
* width in dp. Call this during onStart.
|
||||
*/
|
||||
fun setFullSize(maxWidthDp: Int? = null) {
|
||||
val width = resources.displayMetrics.widthPixels
|
||||
val maxWidth = if (maxWidthDp != null) {
|
||||
val density = resources.displayMetrics.density
|
||||
(maxWidthDp * density).roundToInt()
|
||||
} else null
|
||||
|
||||
dialog?.window?.setLayout(
|
||||
if (maxWidth == null || width < maxWidth) WindowManager.LayoutParams.MATCH_PARENT else maxWidth,
|
||||
WindowManager.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
fun updateNightMode(prefs: PreferenceDataSource) {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
when (prefs.darkmode) {
|
||||
"on" -> AppCompatDelegate.MODE_NIGHT_YES
|
||||
"off" -> AppCompatDelegate.MODE_NIGHT_NO
|
||||
else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.appcompat.view.menu.MenuPopupHelper
|
||||
import androidx.appcompat.widget.MenuPopupWindow
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
|
||||
/**
|
||||
* Reflection workaround to make setTouchModal accessible for
|
||||
*/
|
||||
@SuppressLint("RestrictedApi")
|
||||
fun PopupMenu.setTouchModal(modal: Boolean) {
|
||||
try {
|
||||
val mPopup = javaClass.getDeclaredField("mPopup").let { field ->
|
||||
field.isAccessible = true
|
||||
field.get(this)
|
||||
} as MenuPopupHelper
|
||||
val mPopup2 = mPopup.javaClass.getDeclaredMethod("getPopup").let { method ->
|
||||
method.isAccessible = true
|
||||
method.invoke(mPopup)
|
||||
}
|
||||
val mPopup3 = mPopup2.javaClass.getDeclaredField("mPopup").let { field ->
|
||||
field.isAccessible = true
|
||||
field.get(mPopup2)
|
||||
} as MenuPopupWindow
|
||||
mPopup3.setTouchModal(modal)
|
||||
} catch (e: NoSuchFieldException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NoSuchMethodException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package net.vonforst.evmap.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import java.util.*
|
||||
|
||||
|
||||
class LocaleContextWrapper(base: Context?) : ContextWrapper(base) {
|
||||
companion object {
|
||||
fun wrap(context: Context, language: String): ContextWrapper {
|
||||
val sysConfig: Configuration = context.applicationContext.resources.configuration
|
||||
val appConfig: Configuration = context.resources.configuration
|
||||
|
||||
if (language == "" || language == "default") {
|
||||
// set default locale
|
||||
Locale.setDefault(ConfigurationCompat.getLocales(sysConfig)[0])
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
appConfig.setLocales(sysConfig.locales)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
appConfig.locale = sysConfig.locale
|
||||
}
|
||||
} else {
|
||||
// set selected locale
|
||||
val locale = Locale(language)
|
||||
Locale.setDefault(locale)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
appConfig.setLocale(locale)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
appConfig.locale = locale
|
||||
}
|
||||
}
|
||||
|
||||
return LocaleContextWrapper(context.createConfigurationContext(appConfig))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,12 @@ package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.*
|
||||
import jsonapi.Meta
|
||||
import jsonapi.Relationship
|
||||
import jsonapi.Relationships
|
||||
import jsonapi.ResourceIdentifier
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.banana.jsonapi2.HasMany
|
||||
import moe.banana.jsonapi2.HasOne
|
||||
import moe.banana.jsonapi2.JsonBuffer
|
||||
import moe.banana.jsonapi2.ResourceIdentifier
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
@@ -16,30 +16,48 @@ import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
|
||||
class ChargepriceViewModel(application: Application, chargepriceApiKey: String) :
|
||||
class ChargepriceViewModel(
|
||||
application: Application,
|
||||
chargepriceApiKey: String,
|
||||
chargepriceApiUrl: String,
|
||||
private val state: SavedStateHandle
|
||||
) :
|
||||
AndroidViewModel(application) {
|
||||
private var api = ChargepriceApi.create(chargepriceApiKey)
|
||||
private var api = ChargepriceApi.create(chargepriceApiKey, chargepriceApiUrl)
|
||||
private var prefs = PreferenceDataSource(application)
|
||||
|
||||
val charger: MutableLiveData<ChargeLocation> by lazy {
|
||||
MutableLiveData<ChargeLocation>()
|
||||
}
|
||||
|
||||
val dataSource: MutableLiveData<String> by lazy {
|
||||
MutableLiveData<String>()
|
||||
state.getLiveData("charger")
|
||||
}
|
||||
|
||||
val chargepoint: MutableLiveData<Chargepoint> by lazy {
|
||||
MutableLiveData<Chargepoint>()
|
||||
state.getLiveData("chargepoint")
|
||||
}
|
||||
|
||||
val vehicles: MutableLiveData<Resource<List<ChargepriceCar>>> by lazy {
|
||||
MutableLiveData<Resource<List<ChargepriceCar>>>().apply {
|
||||
if (prefs.chargepriceMyVehicles.isEmpty()) {
|
||||
value = Resource.success(emptyList())
|
||||
} else {
|
||||
value = Resource.loading(null)
|
||||
loadVehicles()
|
||||
private val vehicleIds: MutableLiveData<Set<String>> by lazy {
|
||||
MutableLiveData<Set<String>>().apply {
|
||||
value = prefs.chargepriceMyVehicles
|
||||
}
|
||||
}
|
||||
|
||||
val vehicles: LiveData<Resource<List<ChargepriceCar>>> by lazy {
|
||||
MediatorLiveData<Resource<List<ChargepriceCar>>>().apply {
|
||||
addSource(vehicleIds.distinctUntilChanged()) { vehicleIds ->
|
||||
if (vehicleIds.isEmpty()) {
|
||||
value = Resource.success(emptyList())
|
||||
} else {
|
||||
value = Resource.loading(null)
|
||||
viewModelScope.launch {
|
||||
value = try {
|
||||
val result = api.getVehicles()
|
||||
Resource.success(result.filter {
|
||||
it.id in vehicleIds
|
||||
})
|
||||
} catch (e: IOException) {
|
||||
Resource.error(e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
observeForever {
|
||||
vehicle.value = it.data?.firstOrNull()
|
||||
@@ -48,7 +66,7 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
}
|
||||
|
||||
val vehicle: MutableLiveData<ChargepriceCar> by lazy {
|
||||
MutableLiveData<ChargepriceCar>()
|
||||
state.getLiveData("vehicle")
|
||||
}
|
||||
|
||||
val vehicleCompatibleConnectors: LiveData<List<String>> by lazy {
|
||||
@@ -94,21 +112,24 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
}
|
||||
}
|
||||
|
||||
val chargePrices: MediatorLiveData<Resource<List<ChargePrice>>> by lazy {
|
||||
val chargePrices: MutableLiveData<Resource<List<ChargePrice>>> by lazy {
|
||||
MediatorLiveData<Resource<List<ChargePrice>>>().apply {
|
||||
value = Resource.loading(null)
|
||||
value = state["chargePrices"] ?: Resource.loading(null)
|
||||
listOf(
|
||||
charger,
|
||||
dataSource,
|
||||
batteryRange,
|
||||
batteryRangeSliderDragging,
|
||||
vehicleCompatibleConnectors,
|
||||
myTariffs, myTariffsAll
|
||||
).forEach {
|
||||
addSource(it) {
|
||||
addSource(it.distinctUntilChanged()) {
|
||||
if (!batteryRangeSliderDragging.value!!) loadPrices()
|
||||
}
|
||||
}
|
||||
observeForever {
|
||||
// persist data in case fragment gets recreated
|
||||
state["chargePrices"] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,15 +161,15 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
if (filteredPrices.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
cp.clone().apply {
|
||||
cp.copy(
|
||||
chargepointPrices = filteredPrices
|
||||
}
|
||||
)
|
||||
}
|
||||
}.filterNotNull()
|
||||
.sortedBy { it.chargepointPrices.first().price }
|
||||
.sortedByDescending {
|
||||
prefs.chargepriceMyTariffsAll ||
|
||||
myTariffs != null && it.tariff?.get()?.id in myTariffs
|
||||
myTariffs != null && it.tariffId in myTariffs
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -157,6 +178,10 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadPrefs() {
|
||||
vehicleIds.value = prefs.chargepriceMyVehicles
|
||||
}
|
||||
|
||||
private fun getChargepricePlugType(chargepoint: Chargepoint): String {
|
||||
val index = charger.value!!.chargepointsMerged.indexOf(chargepoint)
|
||||
val type = charger.value!!.chargepriceData!!.plugTypes?.get(index) ?: chargepoint.type
|
||||
@@ -210,10 +235,9 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
val charger = charger.value
|
||||
val car = vehicle.value
|
||||
val compatibleConnectors = vehicleCompatibleConnectors.value
|
||||
val dataSource = dataSource.value
|
||||
val myTariffs = myTariffs.value
|
||||
val myTariffsAll = myTariffsAll.value
|
||||
if (charger == null || car == null || compatibleConnectors == null || dataSource == null || myTariffsAll == null || myTariffsAll == false && myTariffs == null) {
|
||||
if (charger == null || car == null || compatibleConnectors == null || myTariffsAll == null || myTariffsAll == false && myTariffs == null) {
|
||||
chargePrices.value = Resource.error(null, null)
|
||||
return
|
||||
}
|
||||
@@ -223,34 +247,39 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
loadPricesJob?.cancel()
|
||||
loadPricesJob = viewModelScope.launch {
|
||||
try {
|
||||
val result = api.getChargePrices(ChargepriceRequest().apply {
|
||||
dataAdapter = dataSource
|
||||
station = cpStation
|
||||
vehicle = HasOne(car)
|
||||
tariffs = if (!myTariffsAll) {
|
||||
HasMany<ChargepriceTariff>(*myTariffs!!.map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
it
|
||||
val result = api.getChargePrices(
|
||||
ChargepriceRequest(
|
||||
dataAdapter = ChargepriceApi.getDataAdapter(charger),
|
||||
station = cpStation,
|
||||
vehicle = car,
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange.value!!.map { it.toDouble() },
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency,
|
||||
allowUnbalancedLoad = prefs.chargepriceAllowUnbalancedLoad
|
||||
),
|
||||
relationships = if (!myTariffsAll) {
|
||||
Relationships(
|
||||
"tariffs" to Relationship.ToMany(
|
||||
(myTariffs ?: emptySet()).map {
|
||||
ResourceIdentifier(
|
||||
"tariff",
|
||||
id = it
|
||||
)
|
||||
},
|
||||
meta = Meta.from(
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS),
|
||||
ChargepriceApi.moshi
|
||||
)
|
||||
)
|
||||
)
|
||||
}.toTypedArray()).apply {
|
||||
meta = JsonBuffer.create(
|
||||
ChargepriceApi.moshi.adapter(ChargepriceRequestTariffMeta::class.java),
|
||||
ChargepriceRequestTariffMeta(ChargepriceInclude.ALWAYS)
|
||||
)
|
||||
}
|
||||
} else null
|
||||
options = ChargepriceOptions(
|
||||
batteryRange = batteryRange.value!!.map { it.toDouble() },
|
||||
providerCustomerTariffs = prefs.chargepriceShowProviderCustomerTariffs,
|
||||
maxMonthlyFees = if (prefs.chargepriceNoBaseFee) 0.0 else null,
|
||||
currency = prefs.chargepriceCurrency,
|
||||
allowUnbalancedLoad = prefs.chargepriceAllowUnbalancedLoad
|
||||
)
|
||||
}, ChargepriceApi.getChargepriceLanguage())
|
||||
val meta =
|
||||
result.meta.get<ChargepriceMeta>(ChargepriceApi.moshi.adapter(ChargepriceMeta::class.java)) as ChargepriceMeta
|
||||
chargePrices.value = Resource.success(result)
|
||||
} else null
|
||||
), ChargepriceApi.getChargepriceLanguage()
|
||||
)
|
||||
|
||||
val meta = result.meta!!.map(ChargepriceMeta::class.java, ChargepriceApi.moshi)!!
|
||||
chargePrices.value = Resource.success(result.data)
|
||||
chargePriceMeta.value = Resource.success(meta)
|
||||
} catch (e: IOException) {
|
||||
chargePrices.value = Resource.error(e.message, null)
|
||||
@@ -261,17 +290,4 @@ class ChargepriceViewModel(application: Application, chargepriceApiKey: String)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVehicles() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val result = api.getVehicles()
|
||||
vehicles.value = Resource.success(result.filter {
|
||||
it.id in prefs.chargepriceMyVehicles
|
||||
})
|
||||
} catch (e: IOException) {
|
||||
vehicles.value = Resource.error(e.message, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,45 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.vonforst.evmap.api.ChargepointApi
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
|
||||
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.storage.*
|
||||
import androidx.lifecycle.switchMap
|
||||
import net.vonforst.evmap.model.Filter
|
||||
import net.vonforst.evmap.model.FilterValue
|
||||
import net.vonforst.evmap.model.FilterValues
|
||||
import net.vonforst.evmap.model.FilterWithValue
|
||||
import net.vonforst.evmap.storage.FilterValueDao
|
||||
import kotlin.reflect.full.cast
|
||||
|
||||
fun ChargepointApi<ReferenceData>.getReferenceData(
|
||||
scope: CoroutineScope,
|
||||
ctx: Context
|
||||
): LiveData<out ReferenceData> {
|
||||
val db = AppDatabase.getInstance(ctx)
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
return when (this) {
|
||||
is GoingElectricApiWrapper -> {
|
||||
GEReferenceDataRepository(
|
||||
this,
|
||||
scope,
|
||||
db.geReferenceDataDao(),
|
||||
prefs
|
||||
).getReferenceData()
|
||||
}
|
||||
is OpenChargeMapApiWrapper -> {
|
||||
OCMReferenceDataRepository(
|
||||
this,
|
||||
scope,
|
||||
db.ocmReferenceDataDao(),
|
||||
prefs
|
||||
).getReferenceData()
|
||||
}
|
||||
else -> {
|
||||
throw RuntimeException("no reference data implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun filtersWithValue(
|
||||
filters: LiveData<List<Filter<FilterValue>>>,
|
||||
filterValues: LiveData<List<FilterValue>>
|
||||
): MediatorLiveData<FilterValues> =
|
||||
MediatorLiveData<FilterValues>().apply {
|
||||
filterValues: LiveData<List<FilterValue>?>
|
||||
): MediatorLiveData<FilterValues?> =
|
||||
MediatorLiveData<FilterValues?>().apply {
|
||||
listOf(filters, filterValues).forEach {
|
||||
addSource(it) {
|
||||
val f = filters.value ?: return@addSource
|
||||
val values = filterValues.value ?: return@addSource
|
||||
value = f.map { filter ->
|
||||
val value =
|
||||
values.find { it.key == filter.key } ?: filter.defaultValue()
|
||||
FilterWithValue(filter, filter.valueClass.cast(value))
|
||||
val f = filters.value ?: run {
|
||||
value = null
|
||||
return@addSource
|
||||
}
|
||||
val values = filterValues.value ?: run {
|
||||
value = null
|
||||
return@addSource
|
||||
}
|
||||
value = filtersWithValue(f, values)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun filtersWithValue(
|
||||
filters: List<Filter<FilterValue>>,
|
||||
values: List<FilterValue>
|
||||
) = filters.map { filter ->
|
||||
val value =
|
||||
values.find { it.key == filter.key } ?: filter.defaultValue()
|
||||
FilterWithValue(filter, filter.valueClass.cast(value))
|
||||
}
|
||||
|
||||
fun FilterValueDao.getFilterValues(filterStatus: LiveData<Long>, dataSource: String) =
|
||||
MediatorLiveData<List<FilterValue>>().apply {
|
||||
var source: LiveData<List<FilterValue>>? = null
|
||||
addSource(filterStatus) { status ->
|
||||
source?.let { removeSource(it) }
|
||||
source = getFilterValues(status, dataSource)
|
||||
addSource(source!!) { result ->
|
||||
value = result
|
||||
}
|
||||
}
|
||||
filterStatus.switchMap {
|
||||
getFilterValues(it, dataSource)
|
||||
}
|
||||
@@ -8,27 +8,23 @@ import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.ChargeLocationsRepository
|
||||
import net.vonforst.evmap.storage.FilterProfile
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
|
||||
class FilterViewModel(application: Application) : AndroidViewModel(application) {
|
||||
private var db = AppDatabase.getInstance(application)
|
||||
private var prefs = PreferenceDataSource(application)
|
||||
private var api: ChargepointApi<ReferenceData> = createApi(prefs.dataSource, application)
|
||||
private val db = AppDatabase.getInstance(application)
|
||||
private val prefs = PreferenceDataSource(application)
|
||||
private val api: ChargepointApi<ReferenceData> = createApi(prefs.dataSource, application)
|
||||
private val repo = ChargeLocationsRepository(api, viewModelScope, db, prefs)
|
||||
private val filters = repo.getFilters(application.stringProvider())
|
||||
|
||||
private val referenceData = api.getReferenceData(viewModelScope, application)
|
||||
private val filters = MediatorLiveData<List<Filter<FilterValue>>>().apply {
|
||||
addSource(referenceData) { data ->
|
||||
value = api.getFilters(data, application.stringProvider())
|
||||
}
|
||||
}
|
||||
|
||||
private val filterValues: LiveData<List<FilterValue>> by lazy {
|
||||
private val filterValues: LiveData<List<FilterValue>?> by lazy {
|
||||
db.filterValueDao().getFilterValues(FILTERS_CUSTOM, prefs.dataSource)
|
||||
}
|
||||
|
||||
val filtersWithValue: LiveData<FilterValues> by lazy {
|
||||
val filtersWithValue: LiveData<FilterValues?> by lazy {
|
||||
filtersWithValue(filters, filterValues)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,28 +6,25 @@ import androidx.lifecycle.*
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import net.vonforst.evmap.api.ChargepointApi
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.getAvailability
|
||||
import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.goingelectric.GEChargepoint
|
||||
import net.vonforst.evmap.api.goingelectric.GEReferenceData
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
|
||||
import net.vonforst.evmap.api.openchargemap.OCMConnection
|
||||
import net.vonforst.evmap.api.openchargemap.OCMReferenceData
|
||||
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.autocomplete.PlaceWithBounds
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.ChargeLocationsRepository
|
||||
import net.vonforst.evmap.storage.FilterProfile
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.cluster
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import java.io.IOException
|
||||
|
||||
@Parcelize
|
||||
data class MapPosition(val bounds: LatLngBounds, val zoom: Float) : Parcelable
|
||||
@@ -42,17 +39,24 @@ internal fun getClusterDistance(zoom: Float): Int? {
|
||||
|
||||
class MapViewModel(application: Application, private val state: SavedStateHandle) :
|
||||
AndroidViewModel(application) {
|
||||
val apiType: Class<ChargepointApi<ReferenceData>>
|
||||
get() = api.value!!.javaClass
|
||||
val apiName: String
|
||||
get() = api.value!!.getName()
|
||||
private val db = AppDatabase.getInstance(application)
|
||||
private val prefs = PreferenceDataSource(application)
|
||||
private val repo = ChargeLocationsRepository(
|
||||
createApi(prefs.dataSource, application),
|
||||
viewModelScope,
|
||||
db,
|
||||
prefs
|
||||
)
|
||||
|
||||
private var db = AppDatabase.getInstance(application)
|
||||
private var prefs = PreferenceDataSource(application)
|
||||
private var api = MutableLiveData<ChargepointApi<ReferenceData>>().apply {
|
||||
value = createApi(prefs.dataSource, application)
|
||||
val apiId = repo.api.map { it.id }
|
||||
|
||||
init {
|
||||
// necessary so that apiId is updated
|
||||
apiId.observeForever { }
|
||||
}
|
||||
|
||||
val apiName = repo.api.map { it.name }
|
||||
|
||||
val bottomSheetState: MutableLiveData<Int> by lazy {
|
||||
state.getLiveData("bottomSheetState")
|
||||
}
|
||||
@@ -69,47 +73,28 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
}
|
||||
}
|
||||
private val filterValues: LiveData<List<FilterValue>> =
|
||||
private val filterValues: LiveData<List<FilterValue>?> = repo.api.switchMap {
|
||||
db.filterValueDao().getFilterValues(filterStatus, prefs.dataSource)
|
||||
private val referenceData =
|
||||
Transformations.switchMap(api) { it.getReferenceData(viewModelScope, application) }
|
||||
private val filters = Transformations.map(referenceData) {
|
||||
api.value!!.getFilters(
|
||||
it,
|
||||
application.stringProvider()
|
||||
)
|
||||
}
|
||||
private val filters = repo.getFilters(application.stringProvider())
|
||||
|
||||
private val filtersWithValue: LiveData<FilterValues> by lazy {
|
||||
private val filtersWithValue: LiveData<FilterValues?> by lazy {
|
||||
filtersWithValue(filters, filterValues)
|
||||
}
|
||||
|
||||
val filterProfiles: LiveData<List<FilterProfile>> by lazy {
|
||||
val filterProfiles: LiveData<List<FilterProfile>> = repo.api.switchMap {
|
||||
db.filterProfileDao().getProfiles(prefs.dataSource)
|
||||
}
|
||||
|
||||
val chargeCardMap: LiveData<Map<Long, ChargeCard>> by lazy {
|
||||
MediatorLiveData<Map<Long, ChargeCard>>().apply {
|
||||
value = null
|
||||
addSource(referenceData) { data ->
|
||||
value = if (data is GEReferenceData) {
|
||||
data.chargecards.map {
|
||||
it.id to it.convert()
|
||||
}.toMap()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val chargeCardMap = repo.chargeCardMap
|
||||
|
||||
val filtersCount: LiveData<Int> by lazy {
|
||||
MediatorLiveData<Int>().apply {
|
||||
value = 0
|
||||
addSource(filtersWithValue) { filtersWithValue ->
|
||||
value = filtersWithValue.count {
|
||||
value = filtersWithValue?.count {
|
||||
!it.value.hasSameValueAs(it.filter.defaultValue())
|
||||
}
|
||||
} ?: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +104,7 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
value = Resource.loading(emptyList())
|
||||
// this is not automatically updated with mapPosition, as we only want to update
|
||||
// when map is idle.
|
||||
listOf(filtersWithValue, referenceData).forEach {
|
||||
listOf(filtersWithValue, repo.api).forEach {
|
||||
addSource(it) {
|
||||
reloadChargepoints()
|
||||
}
|
||||
@@ -139,28 +124,17 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
val chargerSparse: MutableLiveData<ChargeLocation> by lazy {
|
||||
state.getLiveData("chargerSparse")
|
||||
}
|
||||
val chargerDetails: MediatorLiveData<Resource<ChargeLocation>> by lazy {
|
||||
MediatorLiveData<Resource<ChargeLocation>>().apply {
|
||||
value = state["chargerDetails"]
|
||||
listOf(chargerSparse, referenceData).forEach {
|
||||
addSource(it) { _ ->
|
||||
val charger = chargerSparse.value
|
||||
val refData = referenceData.value
|
||||
if (charger != null && refData != null) {
|
||||
if (charger.id != value?.data?.id) {
|
||||
loadChargerDetails(charger, refData)
|
||||
}
|
||||
} else {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
observeForever {
|
||||
// persist data in case fragment gets recreated
|
||||
state["chargerDetails"] = it
|
||||
}
|
||||
val chargerDetails: LiveData<Resource<ChargeLocation>> = chargerSparse.switchMap { charger ->
|
||||
charger?.id?.let {
|
||||
repo.getChargepointDetail(it)
|
||||
}
|
||||
}.apply {
|
||||
observeForever { chargerDetail ->
|
||||
// persist data in case fragment gets recreated
|
||||
state["chargerDetails"] = chargerDetail
|
||||
}
|
||||
}
|
||||
|
||||
val charger: MediatorLiveData<Resource<ChargeLocation>> by lazy {
|
||||
MediatorLiveData<Resource<ChargeLocation>>().apply {
|
||||
addSource(chargerDetails) {
|
||||
@@ -194,15 +168,15 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
val location: MutableLiveData<LatLng> by lazy {
|
||||
MutableLiveData<LatLng>()
|
||||
}
|
||||
val availability: MediatorLiveData<Resource<ChargeLocationStatus>> by lazy {
|
||||
MediatorLiveData<Resource<ChargeLocationStatus>>().apply {
|
||||
addSource(chargerSparse) { charger ->
|
||||
if (charger != null) {
|
||||
viewModelScope.launch {
|
||||
loadAvailability(charger)
|
||||
private val triggerAvailabilityRefresh = MutableLiveData<Boolean>(true)
|
||||
val availability: LiveData<Resource<ChargeLocationStatus>> by lazy {
|
||||
chargerSparse.switchMap { charger ->
|
||||
charger?.let {
|
||||
triggerAvailabilityRefresh.switchMap {
|
||||
liveData {
|
||||
emit(Resource.loading(null))
|
||||
emit(getAvailability(charger))
|
||||
}
|
||||
} else {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -265,7 +239,9 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
|
||||
fun reloadPrefs() {
|
||||
filterStatus.value = prefs.filterStatus
|
||||
api.value = createApi(prefs.dataSource, getApplication())
|
||||
if (prefs.dataSource != apiId.value) {
|
||||
repo.api.value = createApi(prefs.dataSource, getApplication())
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleFilters() {
|
||||
@@ -277,7 +253,11 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
|
||||
suspend fun copyFiltersToCustom() {
|
||||
filterStatus.value?.let { db.filterValueDao().copyFiltersToCustom(it, prefs.dataSource) }
|
||||
filterStatus.value?.let {
|
||||
withContext(Dispatchers.IO) {
|
||||
db.filterValueDao().copyFiltersToCustom(it, prefs.dataSource)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setMapType(type: AnyMap.Type) {
|
||||
@@ -301,8 +281,7 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
fun reloadChargepoints() {
|
||||
val pos = mapPosition.value ?: return
|
||||
val filters = filtersWithValue.value ?: return
|
||||
val referenceData = referenceData.value ?: return
|
||||
chargepointLoader(Triple(pos, filters, referenceData))
|
||||
chargepointLoader(pos to filters)
|
||||
}
|
||||
|
||||
private val miniMarkerThreshold = 13f
|
||||
@@ -329,17 +308,16 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
|
||||
private var chargepointsInternal: LiveData<Resource<List<ChargepointListItem>>>? = null
|
||||
private var chargepointLoader =
|
||||
throttleLatest(
|
||||
500L,
|
||||
viewModelScope
|
||||
) { data: Triple<MapPosition, FilterValues, ReferenceData> ->
|
||||
) { data: Pair<MapPosition, FilterValues> ->
|
||||
chargepoints.value = Resource.loading(chargepoints.value?.data)
|
||||
|
||||
val mapPosition = data.first
|
||||
val filters = data.second
|
||||
val api = api.value!!
|
||||
val refData = data.third
|
||||
|
||||
if (filterStatus.value == FILTERS_FAVORITES) {
|
||||
// load favorites from local DB
|
||||
@@ -362,96 +340,57 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
return@throttleLatest
|
||||
}
|
||||
|
||||
if (api is GoingElectricApiWrapper) {
|
||||
val chargeCardsVal = filters.getMultipleChoiceValue("chargecards")!!
|
||||
filteredChargeCards.value =
|
||||
if (chargeCardsVal.all) null else chargeCardsVal.values.map { it.toLong() }
|
||||
.toSet()
|
||||
val result = repo.getChargepoints(mapPosition.bounds, mapPosition.zoom, filters)
|
||||
chargepointsInternal?.let { chargepoints.removeSource(it) }
|
||||
chargepointsInternal = result
|
||||
chargepoints.addSource(result) {
|
||||
chargepoints.value = it
|
||||
|
||||
val connectorsVal = filters.getMultipleChoiceValue("connectors")!!
|
||||
filteredConnectors.value =
|
||||
if (connectorsVal.all) null else connectorsVal.values.map {
|
||||
GEChargepoint.convertTypeFromGE(it)
|
||||
}.toSet()
|
||||
filteredMinPower.value = filters.getSliderValue("min_power")
|
||||
} else if (api is OpenChargeMapApiWrapper) {
|
||||
val connectorsVal = filters.getMultipleChoiceValue("connectors")!!
|
||||
filteredConnectors.value =
|
||||
if (connectorsVal.all) null else connectorsVal.values.map {
|
||||
OCMConnection.convertConnectionTypeFromOCM(
|
||||
it.toLong(),
|
||||
refData as OCMReferenceData
|
||||
)
|
||||
}.toSet()
|
||||
filteredMinPower.value = filters.getSliderValue("min_power")
|
||||
} else {
|
||||
filteredConnectors.value = null
|
||||
filteredMinPower.value = null
|
||||
filteredChargeCards.value = null
|
||||
}
|
||||
val apiId = apiId.value
|
||||
when (apiId) {
|
||||
"going_electric" -> {
|
||||
val chargeCardsVal = filters.getMultipleChoiceValue("chargecards")!!
|
||||
filteredChargeCards.value =
|
||||
if (chargeCardsVal.all) null else chargeCardsVal.values.map { it.toLong() }
|
||||
.toSet()
|
||||
|
||||
var result = api.getChargepoints(refData, mapPosition.bounds, mapPosition.zoom, filters)
|
||||
if (result.status == Status.ERROR && result.data == null) {
|
||||
// keep old results if new data could not be loaded
|
||||
result = Resource.error(result.message, chargepoints.value?.data)
|
||||
}
|
||||
|
||||
chargepoints.value = result
|
||||
}
|
||||
|
||||
private suspend fun loadAvailability(charger: ChargeLocation) {
|
||||
availability.value = Resource.loading(null)
|
||||
availability.value = getAvailability(charger)
|
||||
}
|
||||
|
||||
fun reloadAvailability() {
|
||||
val charger = chargerSparse.value ?: return
|
||||
viewModelScope.launch {
|
||||
loadAvailability(charger)
|
||||
}
|
||||
}
|
||||
|
||||
private var chargerLoadingTask: Job? = null
|
||||
|
||||
private fun loadChargerDetails(charger: ChargeLocation, referenceData: ReferenceData) {
|
||||
chargerDetails.value = Resource.loading(null)
|
||||
chargerLoadingTask?.cancel()
|
||||
chargerLoadingTask = viewModelScope.launch {
|
||||
try {
|
||||
val chargerDetail = api.value!!.getChargepointDetail(referenceData, charger.id)
|
||||
chargerDetails.value = chargerDetail
|
||||
if (favorites.value?.any { it.charger.id == chargerDetail.data?.id } == true) {
|
||||
// update data of stored favorite
|
||||
db.chargeLocationsDao().insert(charger)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
chargerDetails.value = Resource.error(e.message, null)
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadChargerById(chargerId: Long) {
|
||||
chargerDetails.value = Resource.loading(null)
|
||||
chargerSparse.value = null
|
||||
referenceData.observeForever(object : Observer<ReferenceData> {
|
||||
override fun onChanged(refData: ReferenceData) {
|
||||
referenceData.removeObserver(this)
|
||||
viewModelScope.launch {
|
||||
val response = api.value!!.getChargepointDetail(refData, chargerId)
|
||||
chargerDetails.value = response
|
||||
if (response.status == Status.SUCCESS) {
|
||||
chargerSparse.value = response.data
|
||||
|
||||
if (response.data != null && favorites.value?.any { it.charger.id == response.data.id } == true) {
|
||||
// update data of stored favorite
|
||||
db.chargeLocationsDao().insert(response.data)
|
||||
}
|
||||
} else {
|
||||
chargerSparse.value = null
|
||||
val connectorsVal = filters.getMultipleChoiceValue("connectors")!!
|
||||
filteredConnectors.value =
|
||||
if (connectorsVal.all) null else connectorsVal.values.map {
|
||||
GEChargepoint.convertTypeFromGE(it)
|
||||
}.toSet()
|
||||
filteredMinPower.value = filters.getSliderValue("min_power")
|
||||
}
|
||||
"open_charge_map" -> {
|
||||
val connectorsVal = filters.getMultipleChoiceValue("connectors")!!
|
||||
filteredConnectors.value =
|
||||
if (connectorsVal.all) null else connectorsVal.values.map {
|
||||
OCMConnection.convertConnectionTypeFromOCM(
|
||||
it.toLong(),
|
||||
repo.referenceData.value!! as OCMReferenceData
|
||||
)
|
||||
}.toSet()
|
||||
filteredMinPower.value = filters.getSliderValue("min_power")
|
||||
}
|
||||
else -> {
|
||||
filteredConnectors.value = null
|
||||
filteredMinPower.value = null
|
||||
filteredChargeCards.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun reloadAvailability() {
|
||||
triggerAvailabilityRefresh.value = true
|
||||
}
|
||||
|
||||
fun loadChargerById(chargerId: Long) {
|
||||
chargerSparse.value = null
|
||||
repo.getChargepointDetail(chargerId).observeForever { response ->
|
||||
if (response.status == Status.SUCCESS) {
|
||||
chargerSparse.value = response.data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,13 @@ import net.vonforst.evmap.api.chargeprice.ChargepriceTariff
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import java.io.IOException
|
||||
|
||||
class SettingsViewModel(application: Application, chargepriceApiKey: String) :
|
||||
class SettingsViewModel(
|
||||
application: Application,
|
||||
chargepriceApiKey: String,
|
||||
chargepriceApiUrl: String
|
||||
) :
|
||||
AndroidViewModel(application) {
|
||||
private var api = ChargepriceApi.create(chargepriceApiKey)
|
||||
private var api = ChargepriceApi.create(chargepriceApiKey, chargepriceApiUrl)
|
||||
private var db = AppDatabase.getInstance(application)
|
||||
|
||||
val vehicles: MutableLiveData<Resource<List<ChargepriceCar>>> by lazy {
|
||||
|
||||
@@ -4,10 +4,7 @@ import android.os.Parcelable
|
||||
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 kotlinx.coroutines.*
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.RawValue
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
@@ -19,6 +16,16 @@ inline fun <VM : ViewModel> viewModelFactory(crossinline f: () -> VM) =
|
||||
override fun <T : ViewModel> create(aClass: Class<T>): T = f() as T
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <VM : ViewModel> savedStateViewModelFactory(crossinline f: (SavedStateHandle) -> VM) =
|
||||
object : AbstractSavedStateViewModelFactory() {
|
||||
override fun <T : ViewModel> create(
|
||||
key: String,
|
||||
modelClass: Class<T>,
|
||||
handle: SavedStateHandle
|
||||
) = f(handle) as T
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
SUCCESS,
|
||||
ERROR,
|
||||
@@ -97,4 +104,41 @@ fun <T> throttleLatest(
|
||||
waitingParam = param
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun <T> LiveData<T>.await(): T {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val observer = object : Observer<T> {
|
||||
override fun onChanged(value: T?) {
|
||||
if (value == null) return
|
||||
removeObserver(this)
|
||||
continuation.resume(value, null)
|
||||
}
|
||||
}
|
||||
|
||||
observeForever(observer)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public suspend fun <T> LiveData<Resource<T>>.awaitFinished(): Resource<T> {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
val observer = object : Observer<Resource<T>> {
|
||||
override fun onChanged(value: Resource<T>) {
|
||||
if (value.status != Status.LOADING) {
|
||||
removeObserver(this)
|
||||
continuation.resume(value, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observeForever(observer)
|
||||
|
||||
continuation.invokeOnCancellation {
|
||||
removeObserver(observer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
<vector android:height="44.11976dp"
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:viewportHeight="368.4"
|
||||
android:viewportWidth="233.8"
|
||||
android:width="28dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M109.8,0h13.6c33.9,1.9 67.1,18.5 87.7,45.8c13.5,17.2 21,38.6 22.7,60.3v8.1c-0.8,42.1 -27.7,76.6 -51,109.4c-26.2,37 -50.4,77.3 -57.1,122.9c-1.8,7.7 0.4,18.5 -8.9,22c-2.2,-1.7 -4.7,-3.1 -6.2,-5.4c-2.7,-25.5 -9.1,-50.7 -20,-73.9c-12.3,-27.1 -29.5,-51.6 -47,-75.6C33,199 23,184.2 14.7,168.3c-13,-23.8 -17.9,-51.9 -12.5,-78.6c4.4,-21.1 15.4,-40.6 30.6,-55.7C53.3,14 81.1,1.8 109.8,0z" />
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="M90.9,57.3v68.2h18.6v55.8l43.4,-74.4h-24.8l24.8,-49.6H90.9z" />
|
||||
android:height="44.11976dp">
|
||||
<path
|
||||
android:pathData="M117,367.4c-0.4,-0.3 -0.8,-0.6 -1.2,-0.9c-1.6,-1.2 -3.1,-2.3 -4.2,-3.7c-2.9,-26.9 -9.6,-51.7 -20.1,-74c-12.4,-27.3 -30.1,-52.4 -47.1,-75.8c-8.7,-12 -19.8,-27.9 -28.8,-45.2C2.3,143.6 -2.1,115.9 3.2,89.9c4.3,-20.4 15,-40 30.3,-55.2C53.6,15.1 81.5,2.8 109.9,1l13.5,0c34.4,1.9 66.9,18.9 86.9,45.4c12.8,16.3 20.8,37.5 22.5,59.8l0,8c-0.7,38.8 -23.7,70.9 -45.9,101.9c-1.7,2.3 -3.3,4.6 -5,6.9c-24.4,34.5 -50.3,76.1 -57.3,123.3c-0.5,2 -0.7,4.3 -0.9,6.5C123.3,359 122.8,364.9 117,367.4z"
|
||||
android:fillColor="#FFFFFF" />
|
||||
<path
|
||||
android:pathData="M123.3,2c34.1,1.9 66.3,18.8 86.2,45c12.6,16.1 20.5,37.1 22.3,59.1l0,8c-0.7,38.5 -23.6,70.5 -45.7,101.3c-1.7,2.3 -3.3,4.6 -5,6.9c-24.5,34.6 -50.5,76.3 -57.4,123.7c-0.5,2.1 -0.7,4.4 -0.9,6.7c-0.5,5.9 -1,11 -5.8,13.4c-0.2,-0.2 -0.5,-0.4 -0.7,-0.5c-1.5,-1.1 -2.9,-2 -3.8,-3.3c-2.9,-26.9 -9.7,-51.8 -20.1,-74C80,261 62.3,235.8 45.2,212.4c-8.7,-11.9 -19.8,-27.8 -28.8,-45.1C3.3,143.3 -1,115.9 4.2,90.1c4.2,-20.2 14.9,-39.6 30,-54.7C54.2,16 81.7,3.8 109.9,2H123.3M123.4,0h-13.6c-28.7,1.8 -56.5,14 -77,34C17.6,49.1 6.6,68.6 2.2,89.7c-5.4,26.7 -0.5,54.8 12.5,78.6C23,184.2 33,199 43.6,213.6c17.5,24 34.7,48.5 47,75.6c10.9,23.2 17.3,48.4 20,73.9c1.5,2.3 4,3.7 6.2,5.4c9.3,-3.5 7.1,-14.3 8.9,-22c6.7,-45.6 30.9,-85.9 57.1,-122.9c23.3,-32.8 50.2,-67.3 51,-109.4v-8.1c-1.7,-21.7 -9.2,-43.1 -22.7,-60.3C190.5,18.5 157.3,1.9 123.4,0L123.4,0z"
|
||||
android:fillColor="#808080" />
|
||||
<path
|
||||
android:pathData="M90.9,57.3v68.2h18.6v55.8l43.4,-74.4h-24.8l24.8,-49.6C152.9,57.3 90.9,57.3 90.9,57.3z"
|
||||
android:fillColor="#808080" />
|
||||
</vector>
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
android:height="16dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path
|
||||
android:fillColor="#dddddd"
|
||||
android:pathData="M12,12m-8.5,0a8.5,8.5 0,1 1,17 0a8.5,8.5 0,1 1,-17 0" />
|
||||
android:fillColor="#808080"
|
||||
android:pathData="M12,12m-9,0a9,9 0,1 1,18 0a9,9 0,1 1,-18 0" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,12m-7.5,0a7.5,7.5 0,1 1,15 0a7.5,7.5 0,1 1,-15 0" />
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<path
|
||||
android:fillColor="#B5B5B5"
|
||||
android:pathData="M122.2,101.9h16.7h5.7l22.3,-44.6c0,0 -10.2,0 -22.4,0l-1.1,2.2L122.2,101.9z" />
|
||||
<path
|
||||
android:pathData="M123.3,2c34.1,1.9 66.3,18.8 86.2,45c12.6,16.1 20.5,37.1 22.3,59.1l0,8c-0.7,38.5 -23.6,70.5 -45.7,101.3c-1.7,2.3 -3.3,4.6 -5,6.9c-24.5,34.6 -50.5,76.3 -57.4,123.7c-0.5,2.1 -0.7,4.4 -0.9,6.7c-0.5,5.9 -1,11 -5.8,13.4c-0.2,-0.2 -0.5,-0.4 -0.7,-0.5c-1.5,-1.1 -2.9,-2 -3.8,-3.3c-2.9,-26.9 -9.7,-51.8 -20.1,-74C80,261 62.3,235.8 45.2,212.4c-8.7,-11.9 -19.8,-27.8 -28.8,-45.1C3.3,143.3 -1,115.9 4.2,90.1c4.2,-20.2 14.9,-39.6 30,-54.7C54.2,16 81.7,3.8 109.9,2H123.3M123.4,0h-13.6c-28.7,1.8 -56.5,14 -77,34C17.6,49.1 6.6,68.6 2.2,89.7c-5.4,26.7 -0.5,54.8 12.5,78.6C23,184.2 33,199 43.6,213.6c17.5,24 34.7,48.5 47,75.6c10.9,23.2 17.3,48.4 20,73.9c1.5,2.3 4,3.7 6.2,5.4c9.3,-3.5 7.1,-14.3 8.9,-22c6.7,-45.6 30.9,-85.9 57.1,-122.9c23.3,-32.8 50.2,-67.3 51,-109.4v-8.1c-1.7,-21.7 -9.2,-43.1 -22.7,-60.3C190.5,18.5 157.3,1.9 123.4,0L123.4,0z"
|
||||
android:fillColor="#808080" />
|
||||
<path
|
||||
android:fillColor="#808080"
|
||||
android:pathData="M138.9,57.3c-9.7,0 -19.8,0 -26.4,0c-2.5,0 -5.1,0 -7.6,0c-8.2,0 -16.1,0 -21.4,0c-4.1,0 -6.6,0 -6.6,0v68.2h18.6v55.8l43.4,-74.4h-24.8L138.9,57.3z" />
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/topPanel"
|
||||
android:id="@+id/topPane"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="88dp"
|
||||
android:background="@color/colorPrimary"
|
||||
@@ -46,7 +46,7 @@
|
||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/topPanel" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/topPane" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcomeText1"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
android:paddingTop="8dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:background="@{BindingAdaptersKt.tariffBackground(context,!myTariffsAll && myTariffs.contains(item.tariff.get().id), item.branding.backgroundColor)}">
|
||||
android:background="@{BindingAdaptersKt.tariffBackground(context,!myTariffsAll && myTariffs.contains(item.tariffId), item.branding.backgroundColor)}">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtTariff"
|
||||
|
||||
@@ -130,9 +130,6 @@
|
||||
<argument
|
||||
android:name="charger"
|
||||
app:argType="net.vonforst.evmap.model.ChargeLocation" />
|
||||
<argument
|
||||
android:name="dataSource"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/donate"
|
||||
@@ -141,7 +138,7 @@
|
||||
tools:layout="@layout/fragment_donate" />
|
||||
<dialog
|
||||
android:id="@+id/opensource_donations"
|
||||
android:name="net.vonforst.evmap.fragment.updatedialogs.OpensourceDonationsDialogFramgent"
|
||||
android:name="net.vonforst.evmap.fragment.updatedialogs.OpensourceDonationsDialogFragment"
|
||||
android:label="@string/donation_dialog_title"
|
||||
tools:layout="@layout/dialog_opensource_donations">
|
||||
<action
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_language_names">
|
||||
<item>Gerätesprache verwenden</item>
|
||||
<item>Englisch</item>
|
||||
<item>Deutsch</item>
|
||||
</string-array>
|
||||
<string-array name="pref_darkmode_names">
|
||||
<item>Geräteeinstellung verwenden</item>
|
||||
<item>immer an</item>
|
||||
<item>immer aus</item>
|
||||
</string-array>
|
||||
<string-array name="pref_data_source_names">
|
||||
<item>GoingElectric.de</item>
|
||||
<item>Open Charge Map</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -3,8 +3,8 @@
|
||||
<string name="app_name">EVMap</string>
|
||||
<string name="title_activity_maps">EVMap</string>
|
||||
<string name="connectors">Anschlüsse</string>
|
||||
<string name="no_maps_app_found">Keine Navigations-App gefunden</string>
|
||||
<string name="no_browser_app_found">Kein Webbrowser gefunden</string>
|
||||
<string name="no_maps_app_found">Bitte installiere eine Navigations-App</string>
|
||||
<string name="no_browser_app_found">Bitte installiere einen Webbrowser</string>
|
||||
<string name="address">Adresse</string>
|
||||
<string name="operator">Betreiber</string>
|
||||
<string name="network">Verbund</string>
|
||||
@@ -19,10 +19,12 @@
|
||||
<string name="cost_detail"><![CDATA[<b>Laden:</b> %1$s · <b>Parken:</b> %2$s]]></string>
|
||||
<string name="cost_detail_charging"><![CDATA[<b>%s laden</b>]]></string>
|
||||
<string name="cost_detail_parking"><![CDATA[<b>%s parken</b>]]></string>
|
||||
<string name="free">Kostenlos</string>
|
||||
<string name="paid">Kostenpflichtig</string>
|
||||
<string name="amenities">Ladeweile</string>
|
||||
<string name="general_info">Allgemeine Hinweise</string>
|
||||
<string name="charging_free">Kostenlos</string>
|
||||
<string name="charging_paid">Kostenpflichtig</string>
|
||||
<string name="parking_free">Kostenlos</string>
|
||||
<string name="parking_paid">Kostenpflichtig</string>
|
||||
<string name="amenities">Ausstattung</string>
|
||||
<string name="general_info">Allgemein</string>
|
||||
<string name="realtime_data_unavailable">Echtzeitstatus nicht verfügbar</string>
|
||||
<string name="realtime_data_loading">Prüfe Echtzeitstatus…</string>
|
||||
<string name="realtime_data_source">Quelle Echtzeitdaten (beta): %s</string>
|
||||
@@ -35,7 +37,7 @@
|
||||
<string name="about">Über EVMap</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="github_link_title">Quellcode</string>
|
||||
<string name="oss_licenses">Open Source-Lizenzen</string>
|
||||
<string name="oss_licenses">Lizenzen</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="settings_ui">Oberfläche</string>
|
||||
<string name="settings_map">Karte</string>
|
||||
@@ -43,10 +45,10 @@
|
||||
<string name="copyright_summary">©2020–2022 Johan von Forstner</string>
|
||||
<string name="other">Sonstiges</string>
|
||||
<string name="privacy">Datenschutzerklärung</string>
|
||||
<string name="fav_add">Zu Favoriten hinzufügen</string>
|
||||
<string name="fav_add">Als Favorit speichern</string>
|
||||
<string name="fav_remove">Aus Favoriten entfernen</string>
|
||||
<string name="pref_navigate_use_maps">Navigation sofort starten</string>
|
||||
<string name="pref_navigate_use_maps_on">Navigationsbutton startet direkt Google Maps-Navigation</string>
|
||||
<string name="pref_navigate_use_maps">Sofort navigieren</string>
|
||||
<string name="pref_navigate_use_maps_on">Navigationsbutton startet Routenführung mit Google Maps</string>
|
||||
<string name="pref_navigate_use_maps_off">Navigationsbutton startet Karten-App mit Position der Ladesäule</string>
|
||||
<string name="coordinates">Koordinaten</string>
|
||||
<string name="share">Teilen</string>
|
||||
@@ -57,7 +59,7 @@
|
||||
<string name="filter_connectors">Anschlüsse</string>
|
||||
<string name="plug_type_1">Typ 1</string>
|
||||
<string name="plug_type_2">Typ 2</string>
|
||||
<string name="plug_type_3">Typ 3a</string>
|
||||
<string name="plug_type_3">Typ 3A</string>
|
||||
<string name="plug_ccs">CCS</string>
|
||||
<string name="plug_schuko">Schuko</string>
|
||||
<string name="plug_chademo">CHAdeMO</string>
|
||||
@@ -69,18 +71,17 @@
|
||||
<string name="none">keine</string>
|
||||
<string name="show_more">mehr…</string>
|
||||
<string name="show_less">weniger…</string>
|
||||
<string name="favorites_empty_state">Wenn du Ladestationen als Favorit markierst, tauchen sie hier auf.</string>
|
||||
<string name="favorites_empty_state">Als Favorit gespeicherte Ladestationen tauchen hier auf</string>
|
||||
<string name="donate">Spenden</string>
|
||||
<string name="donation_successful">Vielen Dank! ❤️</string>
|
||||
<string name="donation_failed">Etwas ist schiefgelaufen. 😕</string>
|
||||
<string name="donation_successful">Vielen Dank ❤️</string>
|
||||
<string name="donation_failed">Etwas ist schiefgelaufen 😕</string>
|
||||
<string name="map_type_normal">Standard</string>
|
||||
<string name="map_type_satellite">Satellit</string>
|
||||
<string name="map_type_terrain">Gelände</string>
|
||||
<string name="map_type">Kartentyp</string>
|
||||
<string name="map_details">Kartendetails</string>
|
||||
<string name="map_traffic">Verkehr</string>
|
||||
<string name="faq">FAQ</string>
|
||||
<string name="faq_desc">Häufig gestellte Fragen</string>
|
||||
<string name="faq">Häufig gestellte Fragen</string>
|
||||
<string name="menu_filters_active">Filter aktiv</string>
|
||||
<string name="filters_activated">Filter aktiviert</string>
|
||||
<string name="filters_deactivated">Filter deaktiviert</string>
|
||||
@@ -97,10 +98,8 @@
|
||||
<string name="edit">bearbeiten</string>
|
||||
<string name="cancel">Abbrechen</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="pref_language">Sprache</string>
|
||||
<string name="pref_language_summary">App-Sprache ändern</string>
|
||||
<string name="pref_language">App-Sprache</string>
|
||||
<string name="pref_darkmode">Dunkles Design</string>
|
||||
<string name="pref_darkmode_summary">Einstellen, wann der Nachtmodus genutzt wird</string>
|
||||
<string name="connection_error">Ladesäulen konnten nicht geladen werden</string>
|
||||
<string name="retry">Wiederholen</string>
|
||||
<string name="filter_open_247">24 Stunden geöffnet</string>
|
||||
@@ -150,16 +149,16 @@
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="save_as_profile">Als Profil speichern</string>
|
||||
<string name="save_profile_enter_name">Geben Sie den Namen des Filterprofils ein:</string>
|
||||
<string name="filterprofiles_empty_state">Du hast noch keine Filterprofile gespeichert.</string>
|
||||
<string name="filterprofiles_empty_state">Du hast keine Filterprofile gespeichert</string>
|
||||
<string name="welcome_to_evmap">Willkommen bei EVMap</string>
|
||||
<string name="welcome_1">Finde Ladestationen für Elektroautos in deiner Nähe.</string>
|
||||
<string name="welcome_1">Finde Ladestationen für Elektroautos in deiner Nähe</string>
|
||||
<string name="welcome_2_title">Auf die Leistung kommt es an</string>
|
||||
<string name="welcome_2">Die Farbe einer Ladestation auf der Karte zeigt dir die maximale Ladeleistung.</string>
|
||||
<string name="welcome_2_detail">Du kannst die Farben im Menü unter “Über EVMap → FAQ” erneut ansehen)</string>
|
||||
<string name="welcome_2">Die Farbe einer Ladestation zeigt dir die maximale Ladeleistung</string>
|
||||
<string name="welcome_2_detail">Die Farben kannst du unter “Über EVMap → Häufig gestellte Fragen” erneut ansehen</string>
|
||||
<string name="donation_dialog_title">Danke, dass du EVMap nutzt!</string>
|
||||
<string name="donation_dialog_detail">EVMap ist kostenlos und Open Source, ich entwickle es in meiner Freizeit. Über GitHub kann jeder zur Weiterentwicklung der App beitragen. Durch die steigende Beliebtheit der App müssen allerdings auch laufende Kosten, z.B. für den Zugriff auf die Datenquellen, gedeckt werden. Daher freue ich mich auch über Spenden in der App oder über GitHub Sponsors.</string>
|
||||
<string name="donation_dialog_detail">EVMap ist kostenlos und Open Source. Über GitHub kann jeder zur Weiterentwicklung der App beitragen. Um die laufenden Kosten für den für die Datenquellen zu decken, freue ich mich auch über Spenden in der App oder über GitHub Sponsors.</string>
|
||||
<string name="chargeprice_donation_dialog_title">Du bist ein richtiger Sparfuchs!</string>
|
||||
<string name="chargeprice_donation_dialog_detail">Es sieht so aus, als wenn du den Preisvergleich sehr gern nutzt. Für den Zugang zu den Preisinformationen muss der Entwickler von EVMap eine monatliche Gebühr an die Datenquelle Chargeprice.app zahlen. Um diesen Dienst weiter anbieten zu können, würde ich mich sehr über Spenden freuen.</string>
|
||||
<string name="chargeprice_donation_dialog_detail">Anscheinend nutzt du den Preisvergleich sehr gern. Mit einer Spende für EVMap kannst du helfen, die Kosten für den Datenzugriff zu decken.</string>
|
||||
<string name="deleted_filterprofile">„%s” gelöscht</string>
|
||||
<string name="undo">Rückgängig</string>
|
||||
<string name="rename">Umbenennen</string>
|
||||
@@ -170,7 +169,7 @@
|
||||
</plurals>
|
||||
<string name="navigate">Navigieren</string>
|
||||
<string name="verified">Verifiziert</string>
|
||||
<string name="verified_desc">Verifiziert von der %s Community – nicht zwangsläufig auch aktuell verfügbar.</string>
|
||||
<string name="verified_desc">Ladestation wurde mindestens einmal von einem Mitglied der %s Community getestet</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>
|
||||
@@ -180,67 +179,56 @@
|
||||
<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="chargeprice_blocking_fee">Blockiergeb. >%s</string>
|
||||
<string name="chargeprice_no_tariffs_found">Keine 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">Meine Fahrzeuge</string>
|
||||
<string name="pref_chargeprice_no_base_fee">Nur Tarife ohne monatliche Gebühren</string>
|
||||
<string name="pref_chargeprice_no_base_fee">Tarife mit monatlichen Gebühren ausschließen</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="pref_chargeprice_show_provider_customer_tariffs_summary">Einige Energieversorger bieten für ihre Kunden spezielle 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="chargeprice_battery_range_from">Laden von</string>
|
||||
<string name="chargeprice_battery_range_to">bis</string>
|
||||
<string name="chargeprice_stats">(%1$.0f kWh, ca. %2$s, ⌀ %3$.0f kW)</string>
|
||||
<string name="chargeprice_vehicle">Fahrzeug</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="edit_on_goingelectric_info">Logge dich zuerst bei GoingElectric.de ein, falls hier nur eine leere Seite erscheint</string>
|
||||
<string name="close">Schließen</string>
|
||||
<string name="chargeprice_title">Preise</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="chargeprice_no_compatible_connectors">Kein kompatibler Anschluss an dieser Ladestation</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 name="pref_my_tariffs_summary">(werden im Preisvergleich hervorgehoben)</string>
|
||||
<plurals name="pref_my_tariffs_summary">
|
||||
<item quantity="one">(wird im Preisvergleich hervorgehoben)</item>
|
||||
<item quantity="other">(werden im Preisvergleich hervorgehoben)</item>
|
||||
</plurals>
|
||||
<string name="license">Lizenz</string>
|
||||
<string name="settings_charger_data">Ladesäulen</string>
|
||||
<string name="pref_data_source">Datenquelle</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>
|
||||
<string name="unknown_operator">Unbekannter Betreiber</string>
|
||||
<string name="data_sources_description">EVMap unterstützt verschiedene Datenquellen für Ladestationen. Bitte wähle aus, welche du nutzen möchtest. Du kannst sie später in den Einstellungen der App ändern.</string>
|
||||
<string name="data_sources_description">Bitte wähle eine Datenquelle für Ladestationen aus. Du kannst sie später in den Einstellungen der App ändern.</string>
|
||||
<string name="data_source_goingelectric">GoingElectric.de</string>
|
||||
<string name="data_source_openchargemap">Open Charge Map</string>
|
||||
<string name="data_source_goingelectric_desc">Sehr gute Abdeckung in Deutschland, Österreich, Schweiz und vielen angrenzenden Ländern. Beschreibungen in Deutsch. Von der Community gepflegt.</string>
|
||||
<string name="data_source_openchargemap_desc"><![CDATA[Weltweite Abdeckung mit variierender Qualität. Beschreibungen in Englisch oder Landessprache. Von der Community gepflegt & offizielle Verzeichnisse einiger Länder (z.B. Nordamerika, UK, Frankreich, Norwegen).]]></string>
|
||||
<string name="data_source_goingelectric_desc">Sehr gute Abdeckung in den deutschsprachigen Ländern. Beschreibungen in Deutsch. Von der Community gepflegt.</string>
|
||||
<string name="data_source_openchargemap_desc"><![CDATA[Weltweite Abdeckung mit variierender Qualität. Beschreibungen in Englisch oder Landessprache. Von der Community gepflegt und offizielle Verzeichnisse einiger Länder (z.B. Nordamerika, UK, Frankreich, Norwegen).]]></string>
|
||||
<string name="next">weiter</string>
|
||||
<string name="get_started">Los geht\'s</string>
|
||||
<string name="got_it">Alles klar</string>
|
||||
<string name="lets_go">Und los</string>
|
||||
<string name="crash_report_text">Sorry, anscheinend ist EVMap abgestürzt. Bitte schicke einen Fehlerbericht an den Entwickler.</string>
|
||||
<string name="crash_report_text">EVMap ist abgestürzt. Bitte schicke einen Fehlerbericht an den Entwickler.</string>
|
||||
<string name="crash_report_comment_prompt">Du kannst unten noch einen Kommentar hinzufügen:</string>
|
||||
<string name="powered_by_mapbox">powered by Mapbox</string>
|
||||
<string name="pref_search_provider">Anbieter für Ortssuche</string>
|
||||
<string name="pref_search_provider_info"><![CDATA[Die Daten für die Ortssuche, vor allem von Google Maps, sind relativ teuer. Wenn du diese Funktion häufig nutzt, würde ich mich über eine Spende unter \"Über EVMap -> Spenden\" sehr freuen.]]></string>
|
||||
<string name="pref_search_provider_info">Die Daten für die Ortssuche, vor allem von Google Maps, sind relativ teuer. Über eine Spende unter \"Über EVMap -> Spenden\" würde ich mich sehr freuen.</string>
|
||||
<string name="github_sponsors">GitHub Sponsors</string>
|
||||
<string name="donate_desc">Unterstütze die Weiterentwicklung von EVMap mit einer einmaligen Spende</string>
|
||||
<string name="github_sponsors_desc">Unterstütze EVMap über GitHub Sponsors</string>
|
||||
@@ -256,10 +244,30 @@
|
||||
<string name="help">Hilfe</string>
|
||||
<string name="settings_android_auto">Android Auto</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load">Schieflast erlauben</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load_summary"><![CDATA[Erlaubt das Laden mit >4.5 kW an AC-Stationen für Autos mit 1-phasigem Lader]]></string>
|
||||
<string name="pref_map_rotate_gestures_enabled">Kartenrotation erlauben</string>
|
||||
<string name="pref_map_rotate_gestures_on">Karte kann mit Zweifingergeste rotiert werden</string>
|
||||
<string name="pref_map_rotate_gestures_off">Karte bleibt fest nach Norden ausgerichtet</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load_summary">Einphasiges Laden mit mehr als 4.5 kW erlauben</string>
|
||||
<string name="pref_map_rotate_gestures_enabled">Kartenrotation</string>
|
||||
<string name="pref_map_rotate_gestures_on">Karte mit zwei Fingern rotieren</string>
|
||||
<string name="pref_map_rotate_gestures_off">Karte immer nach Norden ausrichten</string>
|
||||
<string name="refresh_live_data">Echtzeitstatus aktualisieren</string>
|
||||
<string name="autocomplete_connection_error">Vorschläge konnten nicht geladen werden</string>
|
||||
</resources>
|
||||
<string name="pref_language_device_default">Gerätesprache verwenden</string>
|
||||
<string name="pref_darkmode_device_default">Geräteeinstellung verwenden</string>
|
||||
<string name="pref_darkmode_always_on">immer an</string>
|
||||
<string name="pref_darkmode_always_off">immer aus</string>
|
||||
<string name="pref_chargeprice_currency_chf">Schweizer Franken (CHF)</string>
|
||||
<string name="pref_chargeprice_currency_czk">Tschechische Krone (CZK)</string>
|
||||
<string name="pref_chargeprice_currency_dkk">Dänische Krone (DKK)</string>
|
||||
<string name="pref_chargeprice_currency_eur">Euro (EUR)</string>
|
||||
<string name="pref_chargeprice_currency_gbp">Britisches Pfund (GBP)</string>
|
||||
<string name="pref_chargeprice_currency_hrk">Kroatische Kuna (HRK)</string>
|
||||
<string name="pref_chargeprice_currency_huf">Ungarischer Forint (HUF)</string>
|
||||
<string name="pref_chargeprice_currency_isk">Isländische Krone (ISK)</string>
|
||||
<string name="pref_chargeprice_currency_nok">Norwegische Krone (NOK)</string>
|
||||
<string name="pref_chargeprice_currency_pln">Polnischer Złoty (PLN)</string>
|
||||
<string name="pref_chargeprice_currency_sek">Schwedische Krone (SEK)</string>
|
||||
<string name="pref_chargeprice_currency_usd">US-Dollar (USD)</string>
|
||||
<string name="pref_provider_google_maps">Google Maps</string>
|
||||
<string name="pref_provider_osm_mapbox">OpenStreetMap (Mapbox)</string>
|
||||
<string name="about_contributors">Mitwirkende</string>
|
||||
<string name="about_contributors_text">Dank an alle Mitwirkenden für ihre Beiträge von Code und Übersetzungen für EVMap:</string>
|
||||
</resources>
|
||||
276
app/src/main/res/values-fr/strings.xml
Normal file
276
app/src/main/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,276 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="app_name">EVMap</string>
|
||||
<string name="title_activity_maps">EVMap</string>
|
||||
<string name="connectors">Connecteurs</string>
|
||||
<string name="no_maps_app_found">Installez d\'abord une application de navigation</string>
|
||||
<string name="no_browser_app_found">Installez d\'abord un navigateur web</string>
|
||||
<string name="address">Adresse</string>
|
||||
<string name="operator">Opérateur</string>
|
||||
<string name="network">Réseau</string>
|
||||
<string name="hours">Heures d\'ouverture</string>
|
||||
<string name="open_247"><b>Ouvert 24h/24 et 7j/7</b></string>
|
||||
<string name="open_closesat"><b>Ouvert</b> · Ferme à %s</string>
|
||||
<string name="closed_unfmt">Fermé</string>
|
||||
<string name="cost">Coût</string>
|
||||
<string name="closed"><b>Fermé</b></string>
|
||||
<string name="closed_opensat"><b>Fermé</b> · Ouvre à %s</string>
|
||||
<string name="holiday">Jour férié</string>
|
||||
<string name="cost_detail"><b>Recharge :</b> %1$s · <b>Stationnement :</b> %2$s</string>
|
||||
<string name="realtime_data_unavailable">Statut en temps réel non disponible</string>
|
||||
<string name="source">Source : %s</string>
|
||||
<string name="menu_favs">Favoris</string>
|
||||
<string name="menu_filter">Filtre</string>
|
||||
<string name="not_implemented">pas encore mis en œuvre</string>
|
||||
<string name="about">À propos</string>
|
||||
<string name="github_link_title">Code source</string>
|
||||
<string name="settings_ui">Interface</string>
|
||||
<string name="privacy">Confidentialité</string>
|
||||
<string name="fav_add">Enregistrer comme favori</string>
|
||||
<string name="pref_navigate_use_maps">Naviguer maintenant</string>
|
||||
<string name="coordinates">Coordonnées</string>
|
||||
<string name="pref_navigate_use_maps_on">Le bouton de navigation démarre le guidage d\'itinéraire avec Google Maps</string>
|
||||
<string name="share">Partager</string>
|
||||
<string name="plug_chademo">CHAdeMO</string>
|
||||
<string name="plug_supercharger">Superchargeur Tesla</string>
|
||||
<string name="show_less">moins…</string>
|
||||
<string name="favorites_empty_state">Les chargeurs sauvegardés apparaissent ici</string>
|
||||
<string name="donate">Faire un don</string>
|
||||
<string name="map_type_satellite">Satellite</string>
|
||||
<string name="map_type_terrain">Terrain</string>
|
||||
<string name="map_type">Type de carte</string>
|
||||
<string name="map_details">Détails de la carte</string>
|
||||
<string name="map_traffic">Trafic</string>
|
||||
<string name="faq">Foire aux questions</string>
|
||||
<string name="menu_filters_active">Filtres actifs</string>
|
||||
<string name="filters_activated">Filtres activés</string>
|
||||
<string name="filters_deactivated">Filtres désactivés</string>
|
||||
<string name="menu_manage_filter_profiles">Gérer les profils de filtrage</string>
|
||||
<string name="edit">modifier</string>
|
||||
<string name="pref_language">Langue de l\'application</string>
|
||||
<string name="connection_error">Impossible de charger les stations de recharge</string>
|
||||
<string name="retry">Réessayer</string>
|
||||
<string name="filter_open_247">Disponible 24h/24 et 7j/7</string>
|
||||
<string name="filter_barrierfree">Utilisable sans enregistrement</string>
|
||||
<string name="filter_exclude_faults">Exclure les chargeurs avec des défauts signalés</string>
|
||||
<string name="charge_cards">Méthodes de paiement</string>
|
||||
<string name="goingelectric_forum">Fil de discussion du forum sur GoingElectric.de</string>
|
||||
<string name="edit_at_datasource">modifier à %s</string>
|
||||
<string name="categories">Catégories</string>
|
||||
<string name="category_car_dealership">Concessionnaire automobile</string>
|
||||
<string name="category_public_authorities">Pouvoirs publics</string>
|
||||
<string name="category_church">Église</string>
|
||||
<string name="category_hospital">Hôpital</string>
|
||||
<string name="category_museum">Musée</string>
|
||||
<string name="category_parking_multi">Parking à étages</string>
|
||||
<string name="category_parking">Parking</string>
|
||||
<string name="category_private_charger">Chargeur privé</string>
|
||||
<string name="category_rest_area">Aire de repos</string>
|
||||
<string name="category_parking_underground">Parking souterrain</string>
|
||||
<string name="category_zoo">Zoo</string>
|
||||
<string name="menu_apply">Appliquer les filtres</string>
|
||||
<string name="save_as_profile">Enregistrer en tant que profil</string>
|
||||
<string name="welcome_1">Trouvez des chargeurs de véhicules électriques autour de vous</string>
|
||||
<string name="welcome_2">La couleur d\'un chargeur sur la carte vous indique sa puissance de charge maximale</string>
|
||||
<string name="welcome_2_detail">Cela peut également être vu dans \"À propos\" → \"Foire aux questions\"</string>
|
||||
<string name="donation_dialog_title">Merci d\'utiliser EVMap</string>
|
||||
<string name="chargeprice_donation_dialog_title">Vous êtes un vrai chasseur de bonnes affaires !</string>
|
||||
<string name="deleted_filterprofile">\"%s\" supprimé</string>
|
||||
<string name="undo">Annuler</string>
|
||||
<string name="rename">Renommer</string>
|
||||
<string name="verified">vérifié</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d mode de paiement compatible</item>
|
||||
<item quantity="many">%d modes de paiement compatibles</item>
|
||||
<item quantity="other">%d modes de paiement compatibles</item>
|
||||
</plurals>
|
||||
<string name="verified_desc">Le fonctionnement du chargeur a été confirmé au moins une fois par un membre de la communauté %s</string>
|
||||
<string name="percent_format">%.0f%%</string>
|
||||
<string name="chargeprice_session_fee">frais de session</string>
|
||||
<string name="chargeprice_per_kwh">par kWh</string>
|
||||
<string name="chargeprice_per_minute">par min</string>
|
||||
<string name="chargeprice_blocking_fee">Frais de blocage >%s</string>
|
||||
<string name="chargeprice_no_tariffs_found">Aucun tarif de recharge pour ce chargeur sur Chargeprice.app</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs">Afficher les tarifs exclusifs aux clients</string>
|
||||
<string name="chargeprice_battery_range">Charge de %1$.0f%% à %2$.0f%%</string>
|
||||
<string name="chargeprice_battery_range_from">Charge de</string>
|
||||
<string name="chargeprice_stats">(%1$.0f kWh, approx. %2$s, ⌀ %3$.0f kW)</string>
|
||||
<string name="chargeprice_vehicle">Véhicule</string>
|
||||
<string name="close">Fermer</string>
|
||||
<string name="chargeprice_title">Prix</string>
|
||||
<string name="pref_chargeprice_currency">Devise</string>
|
||||
<string name="data_source_goingelectric">GoingElectric.de</string>
|
||||
<string name="chargeprice_all_tariffs_selected">tous les tarifs sélectionnés</string>
|
||||
<string name="pref_data_source">Source des données</string>
|
||||
<plurals name="chargeprice_some_tariffs_selected">
|
||||
<item quantity="one">%d tarif sélectionné</item>
|
||||
<item quantity="many">%d tarifs sélectionnés</item>
|
||||
<item quantity="other">%d tarifs sélectionnés</item>
|
||||
</plurals>
|
||||
<string name="data_source_openchargemap">Open Charge Map</string>
|
||||
<string name="next">suivant</string>
|
||||
<string name="get_started">Commencez</string>
|
||||
<string name="crash_report_comment_prompt">Vous pouvez ajouter un commentaire ci-dessous :</string>
|
||||
<string name="github_sponsors">GitHub Sponsors</string>
|
||||
<string name="donate_desc">Soutenir le développement d\'EVMap par un don unique</string>
|
||||
<string name="github_sponsors_desc">Soutenir EVMap sur GitHub Sponsors</string>
|
||||
<string name="unnamed_filter_profile">Profil de filtrage sans nom</string>
|
||||
<string name="privacy_link">https://evmap.vonforst.net/en/privacy.html</string>
|
||||
<string name="required">requis</string>
|
||||
<string name="edit_filter_profile">Modifier \"%s\"</string>
|
||||
<string name="pref_search_delete_recent">Supprimer les résultats de recherche récents</string>
|
||||
<string name="settings_android_auto">Android Auto</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load">Permettre une charge déséquilibrée</string>
|
||||
<string name="pref_map_rotate_gestures_enabled">Rotation de la carte</string>
|
||||
<string name="pref_map_rotate_gestures_off">Rotation désactivée (nord toujours en haut)</string>
|
||||
<string name="refresh_live_data">rafraîchir le statut en temps réel</string>
|
||||
<string name="pref_language_device_default">Utiliser la langue de l\'appareil</string>
|
||||
<string name="pref_darkmode_device_default">Utiliser le réglage de l\'appareil</string>
|
||||
<string name="pref_darkmode_always_on">toujours allumé</string>
|
||||
<string name="pref_darkmode_always_off">toujours éteint</string>
|
||||
<string name="pref_chargeprice_currency_czk">Couronne tchèque (CZK)</string>
|
||||
<string name="pref_chargeprice_currency_dkk">Couronne danoise (DKK)</string>
|
||||
<string name="pref_chargeprice_currency_eur">Euro (EUR)</string>
|
||||
<string name="show_more">plus…</string>
|
||||
<string name="fav_remove">Retirer des favoris</string>
|
||||
<string name="amenities">Commodités</string>
|
||||
<string name="search">Recherche</string>
|
||||
<string name="menu_map">Carte</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
<string name="copyright">Copyright</string>
|
||||
<string name="general_info">Informations générales</string>
|
||||
<string name="realtime_data_loading">Vérification du statut en temps réel…</string>
|
||||
<string name="plug_ccs">CCS</string>
|
||||
<string name="donation_successful">Merci ❤️</string>
|
||||
<string name="donation_failed">Quelque chose s\'est mal passé 😕</string>
|
||||
<string name="category_supermarket">Supermarché</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="oss_licenses">Licences</string>
|
||||
<string name="realtime_data_source">Source du statut en temps réel (bêta) : %s</string>
|
||||
<string name="plug_type_2">Type 2</string>
|
||||
<string name="plug_type_3">Type 3A</string>
|
||||
<string name="plug_cee_rot">CEE Rouge</string>
|
||||
<string name="all">tous</string>
|
||||
<string name="fault_report_date">Rapport d\'anomalie (dernière mise à jour : %s)</string>
|
||||
<string name="menu_report_new_charger">Nouveau chargeur</string>
|
||||
<string name="filter_connectors">Connecteurs</string>
|
||||
<string name="copyright_summary">©2020-2022 Johan von Forstner</string>
|
||||
<string name="other">Autre</string>
|
||||
<string name="pref_navigate_use_maps_off">Le bouton de navigation lance l’application de cartes à l’emplacement du chargeur</string>
|
||||
<string name="settings_map">Carte</string>
|
||||
<string name="fault_report">Rapport d\'anomalie</string>
|
||||
<string name="filter_free">Uniquement des chargeurs gratuits</string>
|
||||
<string name="filter_min_power">Puissance minimale</string>
|
||||
<string name="filter_free_parking">Uniquement les chargeurs avec un parking gratuit</string>
|
||||
<string name="filter_min_connectors">Nombre minimal de connecteurs</string>
|
||||
<string name="plug_type_1">Type 1</string>
|
||||
<string name="plug_schuko">Schuko</string>
|
||||
<string name="plug_cee_blau">CEE Bleu</string>
|
||||
<string name="plug_roadster_hpc">Tesla Roadster (2008) HPC</string>
|
||||
<string name="none">aucun</string>
|
||||
<string name="map_type_normal">Défaut</string>
|
||||
<string name="filter_networks">Réseaux</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="pref_darkmode">Mode sombre</string>
|
||||
<string name="menu_edit_filters">Modifier les filtres</string>
|
||||
<string name="go_to_chargeprice">Comparer les prix</string>
|
||||
<string name="filter_chargecards">Méthodes de paiement</string>
|
||||
<string name="all_selected">Tous sélectionnés</string>
|
||||
<string name="number_selected">%d sélectionné</string>
|
||||
<string name="cancel">Annuler</string>
|
||||
<string name="filter_operators">Opérateurs</string>
|
||||
<string name="chargeprice_donation_dialog_detail">Vous faites bon usage de la fonction de comparaison des prix. Aidez-nous à couvrir les coûts de ces données en soutenant EVMap par un don.</string>
|
||||
<string name="and_n_others">et %d autres</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="pref_map_provider">Fournisseur de cartes</string>
|
||||
<string name="twitter">Twitter</string>
|
||||
<string name="category_petrol_station">Station-service</string>
|
||||
<string name="edit_on_goingelectric_info">Veuillez vous connecter à GoingElectric.de si cette page est vide</string>
|
||||
<string name="settings_chargeprice">Comparaison des prix</string>
|
||||
<string name="category_service_on_motorway">Aire de service (sur autoroute)</string>
|
||||
<string name="category_railway_station">Gare ferroviaire</string>
|
||||
<string name="category_camping">Camping</string>
|
||||
<string name="category_airport">Aéroport</string>
|
||||
<string name="category_amusement_park">Parc d\'attractions</string>
|
||||
<string name="category_hotel">Hôtel</string>
|
||||
<string name="category_restaurant">Restaurant</string>
|
||||
<string name="filter_favorites">Favoris</string>
|
||||
<string name="reorder">réorganiser</string>
|
||||
<string name="delete">Supprimer</string>
|
||||
<string name="save_profile_enter_name">Saisissez le nom du profil de filtrage :</string>
|
||||
<string name="donation_dialog_detail">EVMap est un logiciel libre et gratuit. Les contributions de codage sur GitHub sont très appréciées. Pour aider à couvrir les frais de fonctionnement de l\'accès aux sources de données, veuillez envisager de faire un don du montant de votre choix au développeur.</string>
|
||||
<string name="charging_barrierfree">Utilisable sans enregistrement</string>
|
||||
<string name="chargeprice_battery_range_to">à</string>
|
||||
<string name="category_service_off_motorway">Aire de service (hors autoroute)</string>
|
||||
<string name="category_shopping_mall">Centre commercial</string>
|
||||
<string name="category_cinema">Cinéma</string>
|
||||
<string name="category_swimming_pool">Piscine</string>
|
||||
<string name="menu_save_profile">Enregistrer en tant que profil</string>
|
||||
<string name="no_filters">Aucun filtre</string>
|
||||
<string name="category_holiday_home">Maison de vacances</string>
|
||||
<string name="category_caravan_site">Emplacement pour caravanes</string>
|
||||
<string name="filter_custom">Filtre modifié</string>
|
||||
<string name="filterprofiles_empty_state">Vous n\'avez aucun profil de filtrage enregistré</string>
|
||||
<string name="welcome_to_evmap">Bienvenue sur EVMap</string>
|
||||
<string name="chargeprice_provider_customer_tariff">Uniquement pour les clients du fournisseur</string>
|
||||
<string name="powered_by_chargeprice">alimenté par Chargeprice</string>
|
||||
<string name="pref_my_vehicle">Mes véhicules</string>
|
||||
<string name="pref_my_tariffs">Mes tarifs de recharge</string>
|
||||
<string name="license">Licence</string>
|
||||
<string name="autocomplete_connection_error">Impossible de charger les suggestions</string>
|
||||
<string name="chargeprice_select_connector">Choisir le connecteur</string>
|
||||
<string name="chargeprice_select_car_first">Veuillez d\'abord sélectionner le modèle de votre voiture dans les paramètres</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs_summary">Certains fournisseurs d\'énergie offrent des tarifs moins chers exclusivement à leurs clients</string>
|
||||
<string name="pref_chargeprice_no_base_fee">Exclure les tarifs avec frais mensuels</string>
|
||||
<string name="chargeprice_no_compatible_connectors">Pas de connecteurs compatibles dans cette station de recharge</string>
|
||||
<string name="chargeprice_connection_error">Impossible de charger les prix</string>
|
||||
<string name="pref_search_provider">Fournisseur de recherche de lieux</string>
|
||||
<plurals name="pref_my_tariffs_summary">
|
||||
<item quantity="one" tools:ignore="ImpliedQuantity">(sera mis en évidence dans la comparaison des prix)</item>
|
||||
<item quantity="many">(seront mis en évidence dans la comparaison des prix)</item>
|
||||
<item quantity="other">(seront mis en évidence dans la comparaison des prix)</item>
|
||||
</plurals>
|
||||
<string name="deleted_recent_search_results">Les résultats de recherche récents ont été supprimés</string>
|
||||
<string name="pref_chargeprice_currency_gbp">Livre sterling (GBP)</string>
|
||||
<string name="pref_chargeprice_currency_isk">Couronne islandaise (ISK)</string>
|
||||
<string name="pref_chargeprice_currency_nok">Couronne norvégienne (NOK)</string>
|
||||
<string name="settings_charger_data">Stations de recharge</string>
|
||||
<string name="got_it">J\'ai compris</string>
|
||||
<string name="powered_by_mapbox">propulsé par Mapbox</string>
|
||||
<string name="lets_go">Allons-y</string>
|
||||
<string name="crash_report_text">EVMap a planté. Veuillez envoyer un rapport de plantage au développeur.</string>
|
||||
<string name="unknown_operator">Opérateur inconnu</string>
|
||||
<string name="data_source_goingelectric_desc">Idéal dans les pays germanophones. Descriptions en allemand. Maintenu par la communauté.</string>
|
||||
<string name="data_source_openchargemap_desc">Couverture mondiale avec une qualité variable. Descriptions en anglais ou dans la langue locale. Données ouvertes maintenues par la communauté et provenant de sources gouvernementales dans certains pays (par exemple, Amérique du Nord, Royaume-Uni, France, Norvège).</string>
|
||||
<string name="faq_link">https://evmap.vonforst.net/en/faq.html</string>
|
||||
<string name="chargeprice_faq_link">https://evmap.vonforst.net/en/chargeprice_faq.html</string>
|
||||
<string name="settings_data_sources">Sources de données</string>
|
||||
<string name="data_sources_description">Veuillez choisir une source de données pour les stations de recharge. Vous pourrez la modifier ultérieurement dans les paramètres de l\'application.</string>
|
||||
<string name="pref_search_provider_info">Les données pour la recherche de lieux, en particulier celles de Google Maps, sont relativement coûteuses à récupérer. Veuillez envisager de faire un don via \"À propos\" -> \"Faire un don\".</string>
|
||||
<string name="pref_chargeprice_currency_hrk">Kuna croate (HRK)</string>
|
||||
<string name="help">Aide</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load_summary">Autoriser la charge en courant alternatif monophasé de plus de 4,5 kW</string>
|
||||
<string name="pref_chargeprice_currency_huf">Forint hongrois (HUF)</string>
|
||||
<string name="pref_chargeprice_currency_pln">Złoty polonais (PLN)</string>
|
||||
<string name="pref_map_rotate_gestures_on">Utilisez deux doigts pour faire pivoter la carte</string>
|
||||
<string name="pref_chargeprice_currency_chf">Franc suisse (CHF)</string>
|
||||
<string name="pref_chargeprice_currency_usd">Dollar américain (USD)</string>
|
||||
<string name="pref_chargeprice_currency_sek">Couronne suédoise (SEK)</string>
|
||||
<string name="cost_detail_charging"><b>Recharge %s</b></string>
|
||||
<string name="cost_detail_parking"><b>Stationnement %s</b></string>
|
||||
<string name="navigate">Naviguer vers</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_base_fee">Frais fixes : %1$.2f %2$s/mois</string>
|
||||
<string name="chargeprice_min_spend">Dépenses minimales : %1$.2f %2$s/mois</string>
|
||||
<string name="welcome_2_title">Visualisez la puissance</string>
|
||||
<string name="pref_provider_google_maps">Google Maps</string>
|
||||
<string name="parking_free">gratuit</string>
|
||||
<string name="parking_paid">payant</string>
|
||||
<string name="pref_provider_osm_mapbox">OpenStreetMap (Mapbox)</string>
|
||||
<string name="charging_paid">payante</string>
|
||||
<string name="charging_free">gratuite</string>
|
||||
<string name="about_contributors">Contributeurs</string>
|
||||
<string name="about_contributors_text">Merci à tous les contributeurs pour leur contribution au codage et à la traduction d\'EVMap :</string>
|
||||
</resources>
|
||||
274
app/src/main/res/values-nb-rNO/strings.xml
Normal file
274
app/src/main/res/values-nb-rNO/strings.xml
Normal file
@@ -0,0 +1,274 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EVMap</string>
|
||||
<string name="no_maps_app_found">Installer et navigeringsprogram først</string>
|
||||
<string name="closed"><b>Stengt</b></string>
|
||||
<string name="open_closesat"><b>Åpen</b> · Stenger %s</string>
|
||||
<string name="holiday">Ferie</string>
|
||||
<string name="cost">Kostnad</string>
|
||||
<string name="general_info">Generell info</string>
|
||||
<string name="menu_filter">Filter</string>
|
||||
<string name="about">Om</string>
|
||||
<string name="version">Versjon</string>
|
||||
<string name="settings">Innstillinger</string>
|
||||
<string name="settings_map">Kart</string>
|
||||
<string name="fav_add">Lagre som favoritt</string>
|
||||
<string name="fav_remove">Fjern fra favoritter</string>
|
||||
<string name="share">Del</string>
|
||||
<string name="filter_free">Kun gratisladere</string>
|
||||
<string name="faq">Ofte stilte spørsmål</string>
|
||||
<string name="menu_edit_filters">Rediger filtre</string>
|
||||
<string name="edit">rediger</string>
|
||||
<string name="cancel">Avbryt</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="pref_language">Programspråk</string>
|
||||
<string name="and_n_others">og %d andre</string>
|
||||
<string name="pref_map_provider">Karttilbyder</string>
|
||||
<string name="twitter">Twitter</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="categories">Kategorier</string>
|
||||
<string name="category_airport">Flyplass</string>
|
||||
<string name="category_hotel">Hotell</string>
|
||||
<string name="category_church">Kirke</string>
|
||||
<string name="filter_favorites">Favoritter</string>
|
||||
<string name="delete">Slett</string>
|
||||
<string name="save_as_profile">Lagre som profil</string>
|
||||
<string name="donation_dialog_title">Takk for at du bruker EVMap</string>
|
||||
<string name="license">Lisens</string>
|
||||
<string name="pref_data_source">Datakilder</string>
|
||||
<string name="required">påkrevd</string>
|
||||
<string name="edit_filter_profile">Rediger «%s»</string>
|
||||
<string name="help">Hjelp</string>
|
||||
<string name="hours">Åpningstider</string>
|
||||
<string name="open_247"><b>Døgnåpen</b></string>
|
||||
<string name="settings_ui">Grensesnitt</string>
|
||||
<string name="title_activity_maps">EVMap</string>
|
||||
<string name="no_browser_app_found">Installer en nettleser først</string>
|
||||
<string name="address">Adresse</string>
|
||||
<string name="network">Nettverk</string>
|
||||
<string name="closed_unfmt">Stengt</string>
|
||||
<string name="cost_detail_charging"><b>%s-lading</b></string>
|
||||
<string name="cost_detail_parking"><b>%s-parkering</b></string>
|
||||
<string name="menu_map">Kart</string>
|
||||
<string name="category_petrol_station">Bensinstasjon</string>
|
||||
<string name="closed_opensat"><b>Stengt</b> · Åpner %s</string>
|
||||
<string name="retry">Prøv igjen</string>
|
||||
<string name="source">Kilde: %s</string>
|
||||
<string name="menu_favs">Favoritter</string>
|
||||
<string name="menu_manage_filter_profiles">Håndter filterprofiler</string>
|
||||
<string name="search">Søk</string>
|
||||
<string name="not_implemented">ikke implementert enda</string>
|
||||
<string name="github_link_title">Kildekode</string>
|
||||
<string name="oss_licenses">Lisenser</string>
|
||||
<string name="copyright">Opphavsrett</string>
|
||||
<string name="coordinates">Koordinater</string>
|
||||
<string name="fault_report">Feilrapport</string>
|
||||
<string name="privacy">Personvern</string>
|
||||
<string name="pref_navigate_use_maps">Umiddelbar navigasjon</string>
|
||||
<string name="charge_cards">Betalingsmetoder</string>
|
||||
<string name="go_to_chargeprice">Sammenlign priser</string>
|
||||
<string name="filter_networks">Nettverk</string>
|
||||
<string name="filter_chargecards">Betalingsmetoder</string>
|
||||
<string name="category_hospital">Sykehus</string>
|
||||
<string name="menu_save_profile">Lagre som profil</string>
|
||||
<string name="pref_chargeprice_currency">Valuta</string>
|
||||
<string name="next">neste</string>
|
||||
<string name="github_sponsors">GitHub-sponsorer</string>
|
||||
<string name="menu_report_new_charger">Ny lader</string>
|
||||
<string name="category_private_charger">Privat lader</string>
|
||||
<string name="category_restaurant">Restaurant</string>
|
||||
<string name="category_museum">Museum</string>
|
||||
<string name="category_swimming_pool">Svømmebasseng</string>
|
||||
<string name="unnamed_filter_profile">Filterprofil uten navn</string>
|
||||
<string name="get_started">Begynn</string>
|
||||
<string name="got_it">Skjønner</string>
|
||||
<string name="settings_data_sources">Datakilder</string>
|
||||
<string name="pref_search_delete_recent">Slett nylige søkeresultater</string>
|
||||
<string name="pref_chargeprice_currency_eur">Euro (EUR)</string>
|
||||
<string name="pref_chargeprice_currency_nok">Norske kroner (NOK)</string>
|
||||
<string name="pref_chargeprice_currency_gbp">Britiske pund (GBP)</string>
|
||||
<string name="pref_chargeprice_currency_sek">Svenske kroner (SEK)</string>
|
||||
<string name="realtime_data_loading">Sjekker sanntidsstatus …</string>
|
||||
<string name="realtime_data_source">Kilde for sanntidsstatus (beta): %s</string>
|
||||
<string name="realtime_data_unavailable">Sanntidsstatus utilgjengelig</string>
|
||||
<string name="other">Andre</string>
|
||||
<string name="cost_detail"><b>Lading:</b> %1$s · <b>Parkering:</b> %2$s</string>
|
||||
<string name="copyright_summary">©2020–2022 Johan von Forstner</string>
|
||||
<string name="pref_navigate_use_maps_on">Navigasjonsnkappen starter ruteveiledning på Google Maps</string>
|
||||
<string name="filter_free_parking">Kun ladere med gratis parkering</string>
|
||||
<string name="filter_min_power">Min. effekt</string>
|
||||
<string name="plug_type_1">Type 1</string>
|
||||
<string name="plug_chademo">CHAdeMO</string>
|
||||
<string name="plug_schuko">Schuko</string>
|
||||
<string name="plug_supercharger">Tesla Supercharger</string>
|
||||
<string name="plug_type_2">Type 2</string>
|
||||
<string name="plug_type_3">Type 3A</string>
|
||||
<string name="all">alle</string>
|
||||
<string name="none">ingen</string>
|
||||
<string name="show_less">færre …</string>
|
||||
<string name="map_type_satellite">Satellitt</string>
|
||||
<string name="map_type_terrain">Terreng</string>
|
||||
<string name="map_type">Karttype</string>
|
||||
<string name="map_details">Kartdetaljer</string>
|
||||
<string name="map_traffic">Trafikk</string>
|
||||
<string name="favorites_empty_state">Lagrede ladere vises her</string>
|
||||
<string name="plug_cee_rot">CEE rød</string>
|
||||
<string name="plug_roadster_hpc">Tesla Roadster (2008) HPC</string>
|
||||
<string name="menu_filters_active">Aktive filtre</string>
|
||||
<string name="fault_report_date">Feilrapport (siste oppdatering: %s)</string>
|
||||
<string name="all_selected">Alle valgt</string>
|
||||
<string name="number_selected">%d valgt</string>
|
||||
<string name="pref_darkmode">Mørk drakt</string>
|
||||
<string name="connection_error">Kunne ikke laste inn ladestasjoner</string>
|
||||
<string name="filter_barrierfree">Kan brukes uten registrering</string>
|
||||
<string name="goingelectric_forum">Forumtråd på GoingElectric.de</string>
|
||||
<string name="category_car_dealership">Bilforhandlere</string>
|
||||
<string name="category_railway_station">Togstasjon</string>
|
||||
<string name="category_public_authorities">Offentlige myndigheter</string>
|
||||
<string name="category_amusement_park">Fornøyelsespark</string>
|
||||
<string name="category_cinema">Kino</string>
|
||||
<string name="category_parking_multi">Parkeringshus</string>
|
||||
<string name="edit_at_datasource">rediger på %s</string>
|
||||
<string name="category_camping">Campingplass</string>
|
||||
<string name="category_service_on_motorway">Rasteplass (på motorvei)</string>
|
||||
<string name="category_shopping_mall">Kjøpesenter</string>
|
||||
<string name="category_holiday_home">Feriehjem</string>
|
||||
<string name="category_parking">Parkeringsplass</string>
|
||||
<string name="category_rest_area">Rasteplass</string>
|
||||
<string name="category_supermarket">Supermarked</string>
|
||||
<string name="menu_apply">Bruk filtre</string>
|
||||
<string name="no_filters">Ingen filtre</string>
|
||||
<string name="category_zoo">Dyrehage</string>
|
||||
<string name="category_caravan_site">Campingplass</string>
|
||||
<string name="category_parking_underground">Parkeringsgarasje under bakken</string>
|
||||
<string name="reorder">endre rekkefølge</string>
|
||||
<string name="save_profile_enter_name">Skriv inn navnet på filterprofilen:</string>
|
||||
<string name="filterprofiles_empty_state">Du har ikke noen lagrede filterprofiler</string>
|
||||
<string name="chargeprice_donation_dialog_title">Du er en sann gjerrigknark.</string>
|
||||
<string name="deleted_filterprofile">Slettet «%s»</string>
|
||||
<string name="charging_barrierfree">Kan brukes uten registrering</string>
|
||||
<string name="welcome_1">Finn kjøretøyladere der du er</string>
|
||||
<string name="welcome_2">Hver laders farge samsvarer med dens høyeste ladeeffekt</string>
|
||||
<string name="welcome_2_detail">Dette er også å finne i «Om» → «O-S-S» i menyen</string>
|
||||
<string name="verified_desc">Lader bekreftet av et medlem av %s-gemenskapen. Dette betyr ikke at den virker nå.</string>
|
||||
<string name="charge_price_format">%2$s%1$.2f</string>
|
||||
<string name="charge_price_average_format">⌀ %2$s%1$.2f/kWt</string>
|
||||
<string name="charge_price_kwh_format">%2$s%1$.2f/kWt</string>
|
||||
<string name="chargeprice_per_kwh">per kWt</string>
|
||||
<string name="percent_format">%.0f%%</string>
|
||||
<string name="chargeprice_per_minute">per min</string>
|
||||
<string name="chargeprice_min_spend">Minimumskostnad: %2$s%1$.2f/måned</string>
|
||||
<string name="settings_chargeprice">Prissammenligning</string>
|
||||
<string name="pref_my_vehicle">Mine kjøretøy</string>
|
||||
<string name="chargeprice_battery_range_to">til</string>
|
||||
<string name="chargeprice_stats">(%1$.0f kWt, omtrentlig. %2$s, ⌀ %3$.0f kW)</string>
|
||||
<string name="chargeprice_select_car_first">Velg bilmodellen din i innstillingene først</string>
|
||||
<string name="chargeprice_battery_range">Lad fra %1$.0f%% til %2$.0f%%</string>
|
||||
<string name="chargeprice_battery_range_from">Lad fra</string>
|
||||
<string name="chargeprice_vehicle">Kjøretøy</string>
|
||||
<string name="close">Lukk</string>
|
||||
<string name="chargeprice_title">Priser</string>
|
||||
<string name="chargeprice_connection_error">Kunne ikke laste inn priser</string>
|
||||
<plurals name="pref_my_tariffs_summary">
|
||||
<item quantity="one">(vil bli framhevet i prissammenligningen)</item>
|
||||
<item quantity="other">(vil bli framhevet i prissammenligningen)</item>
|
||||
</plurals>
|
||||
<string name="settings_charger_data">Ladestasjoner</string>
|
||||
<string name="data_source_goingelectric">GoingElectric.de</string>
|
||||
<string name="data_source_openchargemap">Open Charge Map</string>
|
||||
<string name="crash_report_comment_prompt">Du kan legge til en kommentar nedenfor:</string>
|
||||
<string name="github_sponsors_desc">Støtt EVMap med GitHub-sponsorer</string>
|
||||
<string name="donate_desc">Støtt utviklingen av EVMap med en engangsdonasjon</string>
|
||||
<string name="settings_android_auto">Android Auto</string>
|
||||
<string name="pref_map_rotate_gestures_enabled">Kartrotasjon</string>
|
||||
<string name="deleted_recent_search_results">Nylige søkeresultater slettet</string>
|
||||
<string name="autocomplete_connection_error">Kunne ikke laste inn forslag</string>
|
||||
<string name="pref_language_device_default">Enhetsforvalg</string>
|
||||
<string name="pref_chargeprice_currency_chf">Sveitserfranc (CHF)</string>
|
||||
<string name="pref_chargeprice_currency_czk">Tsjekkiske kroner (CZK)</string>
|
||||
<string name="pref_chargeprice_currency_dkk">Danske kroner (DKK)</string>
|
||||
<string name="pref_chargeprice_currency_hrk">Kroatiske kroner (HRK)</string>
|
||||
<string name="pref_map_rotate_gestures_on">Bruk to fingre for å rotere kartet</string>
|
||||
<string name="pref_map_rotate_gestures_off">Rotasjon avslått (nord er alltid oppover)</string>
|
||||
<string name="refresh_live_data">oppdater sanntidsstatus</string>
|
||||
<string name="pref_chargeprice_currency_isk">Islandske kroner (ISK)</string>
|
||||
<string name="pref_chargeprice_currency_pln">Polske zloty (PLN)</string>
|
||||
<string name="pref_chargeprice_currency_usd">Amerikanske dollar (USD)</string>
|
||||
<string name="filters_deactivated">Filtre deaktivert</string>
|
||||
<string name="pref_navigate_use_maps_off">Navigasjonsknapp åpner kartprogrammet med laderposisjon</string>
|
||||
<string name="show_more">flere …</string>
|
||||
<string name="filters_activated">Filtre aktivert</string>
|
||||
<string name="donate">Doner</string>
|
||||
<string name="donation_successful">Takk ❤️</string>
|
||||
<string name="map_type_normal">Forvalg</string>
|
||||
<string name="donation_failed">Noe gikk galt 😕</string>
|
||||
<string name="filter_custom">Endret filter</string>
|
||||
<string name="welcome_to_evmap">Velkommen til EVMap</string>
|
||||
<string name="rename">Gi nytt navn</string>
|
||||
<string name="undo">Angre</string>
|
||||
<plurals name="charge_cards_compatible_num">
|
||||
<item quantity="one">%d kompatibel betalingsmetode</item>
|
||||
<item quantity="other">%d kompatible betalingsmetoder</item>
|
||||
</plurals>
|
||||
<string name="plug_ccs">CCS</string>
|
||||
<string name="verified">bekreftet</string>
|
||||
<string name="pref_darkmode_always_on">alltid på</string>
|
||||
<string name="pref_darkmode_device_default">Enhetsforvalg</string>
|
||||
<string name="pref_darkmode_always_off">alltid av</string>
|
||||
<string name="filter_exclude_faults">Utelat ladere med rapporterte feil</string>
|
||||
<string name="plug_cee_blau">CEE blå</string>
|
||||
<string name="filter_open_247">Døgnåpent</string>
|
||||
<string name="pref_chargeprice_currency_huf">Ungarske forint (HUF)</string>
|
||||
<string name="donation_dialog_detail">EVMap er fri programvare. Kodebidrag mottas med takk. Overvei å gi din støtte gjennom GitHub-sponsorer for å dekke løpende utgifter.</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load_summary">Tillat enfaselading over 4.5 kW</string>
|
||||
<string name="connectors">Tilkobling</string>
|
||||
<string name="operator">Operatør</string>
|
||||
<string name="amenities">Tilleggstjenester</string>
|
||||
<string name="filter_min_connectors">Min. antall tilkoblinger</string>
|
||||
<string name="filter_connectors">Tilkoblinger</string>
|
||||
<string name="filter_operators">Operatører</string>
|
||||
<string name="category_service_off_motorway">Rasteplass (ikke på motorvei)</string>
|
||||
<string name="welcome_2_title">Effekten til veies tilgjengeliggjøres</string>
|
||||
<string name="chargeprice_donation_dialog_detail">Du bruker prissammenligningen en del.
|
||||
\nOvervei å dekke kostnadene ved å støtte EVMap med en donasjon.</string>
|
||||
<string name="navigate">Navigasjon</string>
|
||||
<string name="chargeprice_session_fee">startgebyr</string>
|
||||
<string name="powered_by_chargeprice">tilbudt av Chargeprice</string>
|
||||
<string name="chargeprice_base_fee">Grunnkostnad: %2$s%1$.2f/måned</string>
|
||||
<string name="chargeprice_no_tariffs_found">Ingen ladeabonnement for denne laderen på Chargeprice.app</string>
|
||||
<string name="pref_chargeprice_no_base_fee">Utelat abonnementer med månedlige gebyr</string>
|
||||
<string name="chargeprice_select_connector">Velg tilkobling</string>
|
||||
<string name="chargeprice_provider_customer_tariff">Kun for kundekoblingssalg</string>
|
||||
<string name="chargeprice_blocking_fee">Blokkeringsgebyr >%s</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs">Inkluder kundekoblingssalg</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs_summary">Strømselskaper tilbyr noen ganger billigere abonnementer til sine kunder</string>
|
||||
<string name="pref_my_tariffs">Mine ladeabonnementer</string>
|
||||
<string name="chargeprice_no_compatible_connectors">Ingen kompatible ladere på denne ladestasjonen</string>
|
||||
<string name="data_sources_description">Velg en datakilde for ladestasjoner. (Kan endres senere i programinnstillingene.)</string>
|
||||
<string name="unknown_operator">Ukjent operatør</string>
|
||||
<plurals name="chargeprice_some_tariffs_selected">
|
||||
<item quantity="one">%d plan valgt</item>
|
||||
<item quantity="other">%d planer valgt</item>
|
||||
</plurals>
|
||||
<string name="data_source_goingelectric_desc">Storartet i tyskspråklige land. Beskrivelser på tysk. Gemenskapsdrevet.</string>
|
||||
<string name="powered_by_mapbox">tilbudt av Mapbox</string>
|
||||
<string name="pref_search_provider_info">Data for søk er dyre å hente, spesielt fra Google Maps. Overvei å donere gjennom «Om» → «Doner».</string>
|
||||
<string name="data_source_openchargemap_desc">Verdensomspennende, med varierende kvalitet. Beskrivelser på engelsk eller det lokale språket. Gemenskapsdrevet og åpen myndighetsdata i noen land (f.eks. Nord-Amerika, Storbritannia, Frankrike, og Norge.)</string>
|
||||
<string name="lets_go">Begynn</string>
|
||||
<string name="crash_report_text">EVMap krasjet. Send en rapport til utvikleren.</string>
|
||||
<string name="chargeprice_all_tariffs_selected">alle planer valgt</string>
|
||||
<string name="pref_search_provider">Søketilbyder</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load">Tillat skjev last</string>
|
||||
<string name="edit_on_goingelectric_info">Logg inn på GoingElectric.de hvis denne siden er tom</string>
|
||||
<string name="pref_provider_google_maps">Google Maps</string>
|
||||
<string name="pref_provider_osm_mapbox">OpenStreetMap (Mapbox)</string>
|
||||
<string name="charging_free">Gratis</string>
|
||||
<string name="parking_free">Gratis</string>
|
||||
<string name="charging_paid">Betalt</string>
|
||||
<string name="parking_paid">Betalt</string>
|
||||
<string name="privacy_link">https://evmap.vonforst.net/en/privacy.html</string>
|
||||
<string name="faq_link">https://evmap.vonforst.net/en/faq.html</string>
|
||||
<string name="chargeprice_faq_link">https://evmap.vonforst.net/en/chargeprice_faq.html</string>
|
||||
<string name="about_contributors">Bidragsytere</string>
|
||||
<string name="about_contributors_text">Takk til alle som har kodet og oversatt EVMap:</string>
|
||||
</resources>
|
||||
@@ -1,40 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="pref_language_names">
|
||||
<item>Device default</item>
|
||||
<item>English</item>
|
||||
<item>German</item>
|
||||
<item>@string/pref_language_device_default</item>
|
||||
<item>@string/pref_language_en</item>
|
||||
<item>@string/pref_language_de</item>
|
||||
<item>@string/pref_language_fr</item>
|
||||
<item>@string/pref_language_nb_rNO</item>
|
||||
</string-array>
|
||||
<string-array name="pref_language_values" tranlatable="false">
|
||||
<string-array name="pref_language_values" translatable="false">
|
||||
<item>default</item>
|
||||
<item>en</item>
|
||||
<item>de</item>
|
||||
<item>fr</item>
|
||||
<item>nb-NO</item>
|
||||
</string-array>
|
||||
<string-array name="pref_darkmode_names">
|
||||
<item>Device default</item>
|
||||
<item>always on</item>
|
||||
<item>always off</item>
|
||||
<item>@string/pref_darkmode_device_default</item>
|
||||
<item>@string/pref_darkmode_always_on</item>
|
||||
<item>@string/pref_darkmode_always_off</item>
|
||||
</string-array>
|
||||
<string-array name="pref_darkmode_values" tranlatable="false">
|
||||
<string-array name="pref_darkmode_values" translatable="false">
|
||||
<item>default</item>
|
||||
<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>
|
||||
<item>@string/pref_chargeprice_currency_chf</item>
|
||||
<item>@string/pref_chargeprice_currency_czk</item>
|
||||
<item>@string/pref_chargeprice_currency_dkk</item>
|
||||
<item>@string/pref_chargeprice_currency_eur</item>
|
||||
<item>@string/pref_chargeprice_currency_gbp</item>
|
||||
<item>@string/pref_chargeprice_currency_hrk</item>
|
||||
<item>@string/pref_chargeprice_currency_huf</item>
|
||||
<item>@string/pref_chargeprice_currency_isk</item>
|
||||
<item>@string/pref_chargeprice_currency_nok</item>
|
||||
<item>@string/pref_chargeprice_currency_pln</item>
|
||||
<item>@string/pref_chargeprice_currency_sek</item>
|
||||
<item>@string/pref_chargeprice_currency_usd</item>
|
||||
</string-array>
|
||||
<string-array name="pref_chargeprice_currency_values" donottranslate="true">
|
||||
<string-array name="pref_chargeprice_currency_values" translatable="false">
|
||||
<item>CHF</item>
|
||||
<item>CZK</item>
|
||||
<item>DKK</item>
|
||||
@@ -49,8 +53,8 @@
|
||||
<item>USD</item>
|
||||
</string-array>
|
||||
<string-array name="pref_data_source_names">
|
||||
<item>GoingElectric.de</item>
|
||||
<item>Open Charge Map</item>
|
||||
<item>@string/data_source_goingelectric</item>
|
||||
<item>@string/data_source_openchargemap</item>
|
||||
</string-array>
|
||||
<string-array name="pref_data_source_values" tranlatable="false">
|
||||
<item>goingelectric</item>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<color name="colorSecondaryDark">#00b249</color>
|
||||
<color name="colorSecondaryContainer">#b5f4c7</color>
|
||||
<color name="colorOnSecondaryContainer">#007c00</color>
|
||||
<color name="charger_100kw">#ffeb3b</color>
|
||||
<color name="charger_100kw">#FDD835</color>
|
||||
<color name="charger_43kw">#ff9800</color>
|
||||
<color name="charger_20kw">#03a9f4</color>
|
||||
<color name="charger_11kw">#9e9e9e</color>
|
||||
|
||||
@@ -7,4 +7,10 @@
|
||||
<string name="twitter_url">https://twitter.com/ev_map</string>
|
||||
<string name="goingelectric_forum_url"><![CDATA[https://www.goingelectric.de/forum/viewtopic.php?f=5&t=56342]]></string>
|
||||
<string name="github_sponsors_link">https://github.com/sponsors/johan12345/</string>
|
||||
<string name="chargeprice_api_url">https://api.chargeprice.app/v1/</string>
|
||||
<string name="pref_language_en">English</string>
|
||||
<string name="pref_language_de">Deutsch</string>
|
||||
<string name="pref_language_fr">Français</string>
|
||||
<string name="pref_language_nb_rNO">Norsk Bokmål</string>
|
||||
<string name="about_contributors_list">Danilo Bargen\nAltonss\nAllan Nordhøy\nLicaon_Kter\npt2121\nnautilusx</string>
|
||||
</resources>
|
||||
@@ -2,8 +2,8 @@
|
||||
<string name="app_name">EVMap</string>
|
||||
<string name="title_activity_maps">EVMap</string>
|
||||
<string name="connectors">Connectors</string>
|
||||
<string name="no_maps_app_found">No navigation app found</string>
|
||||
<string name="no_browser_app_found">No web browser found</string>
|
||||
<string name="no_maps_app_found">Install a navigation app first</string>
|
||||
<string name="no_browser_app_found">Install a web browser first</string>
|
||||
<string name="address">Address</string>
|
||||
<string name="operator">Operator</string>
|
||||
<string name="network">Network</string>
|
||||
@@ -18,10 +18,12 @@
|
||||
<string name="cost_detail"><![CDATA[<b>Charging:</b> %1$s · <b>Parking:</b> %2$s]]></string>
|
||||
<string name="cost_detail_charging"><![CDATA[<b>%s charging</b>]]></string>
|
||||
<string name="cost_detail_parking"><![CDATA[<b>%s parking</b>]]></string>
|
||||
<string name="free">Free</string>
|
||||
<string name="paid">Paid</string>
|
||||
<string name="charging_free">Free</string>
|
||||
<string name="charging_paid">Paid</string>
|
||||
<string name="parking_free">Free</string>
|
||||
<string name="parking_paid">Paid</string>
|
||||
<string name="amenities">Amenities</string>
|
||||
<string name="general_info">General information</string>
|
||||
<string name="general_info">General info</string>
|
||||
<string name="realtime_data_unavailable">Real-time status unavailable</string>
|
||||
<string name="realtime_data_loading">Checking real-time status…</string>
|
||||
<string name="realtime_data_source">Real-time status source (beta): %s</string>
|
||||
@@ -31,32 +33,32 @@
|
||||
<string name="menu_favs">Favorites</string>
|
||||
<string name="menu_filter">Filter</string>
|
||||
<string name="not_implemented">not implemented yet</string>
|
||||
<string name="about">About EVMap</string>
|
||||
<string name="about">About</string>
|
||||
<string name="version">Version</string>
|
||||
<string name="github_link_title">Source code</string>
|
||||
<string name="oss_licenses">Open Source Licenses</string>
|
||||
<string name="oss_licenses">Licenses</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="settings_ui">User Interface</string>
|
||||
<string name="settings_ui">Interface</string>
|
||||
<string name="settings_map">Map</string>
|
||||
<string name="copyright">Copyright</string>
|
||||
<string name="copyright_summary">©2020–2022 Johan von Forstner</string>
|
||||
<string name="other">Other</string>
|
||||
<string name="privacy">Privacy Notice</string>
|
||||
<string name="fav_add">Add to favorites</string>
|
||||
<string name="privacy">Privacy</string>
|
||||
<string name="fav_add">Save as favorite</string>
|
||||
<string name="fav_remove">Remove from favorites</string>
|
||||
<string name="pref_navigate_use_maps">Start navigation immediately</string>
|
||||
<string name="pref_navigate_use_maps_on">Navigation button starts Google Maps navigation immediately</string>
|
||||
<string name="pref_navigate_use_maps_off">Navigation button launches maps app with charger location</string>
|
||||
<string name="pref_navigate_use_maps">Immediate navigation</string>
|
||||
<string name="pref_navigate_use_maps_on">Navigation button starts route guidance with Google Maps</string>
|
||||
<string name="pref_navigate_use_maps_off">Navigation button opens the maps app with charger location</string>
|
||||
<string name="coordinates">Coordinates</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="filter_free">Only free chargers</string>
|
||||
<string name="filter_min_power">Minimum power</string>
|
||||
<string name="filter_min_power">Min power</string>
|
||||
<string name="filter_free_parking">Only chargers with free parking</string>
|
||||
<string name="filter_min_connectors">Minimum number of connectors</string>
|
||||
<string name="filter_min_connectors">Min number of connectors</string>
|
||||
<string name="filter_connectors">Connectors</string>
|
||||
<string name="plug_type_1">Type 1</string>
|
||||
<string name="plug_type_2">Type 2</string>
|
||||
<string name="plug_type_3">Type 3a</string>
|
||||
<string name="plug_type_3">Type 3A</string>
|
||||
<string name="plug_ccs">CCS</string>
|
||||
<string name="plug_schuko">Schuko</string>
|
||||
<string name="plug_chademo">CHAdeMO</string>
|
||||
@@ -68,18 +70,17 @@
|
||||
<string name="none">none</string>
|
||||
<string name="show_more">more…</string>
|
||||
<string name="show_less">less…</string>
|
||||
<string name="favorites_empty_state">If you add chargers as favorites, they will show up here.</string>
|
||||
<string name="favorites_empty_state">Saved chargers show up here</string>
|
||||
<string name="donate">Donate</string>
|
||||
<string name="donation_successful">Thank you! ❤️</string>
|
||||
<string name="donation_failed">Something went wrong. 😕</string>
|
||||
<string name="donation_successful">Thank you ❤️</string>
|
||||
<string name="donation_failed">Something went wrong 😕</string>
|
||||
<string name="map_type_normal">Default</string>
|
||||
<string name="map_type_satellite">Satellite</string>
|
||||
<string name="map_type_terrain">Terrain</string>
|
||||
<string name="map_type">Map type</string>
|
||||
<string name="map_details">Map details</string>
|
||||
<string name="map_traffic">Traffic</string>
|
||||
<string name="faq">FAQ</string>
|
||||
<string name="faq_desc">Frequently asked questions</string>
|
||||
<string name="faq">Frequently asked questions</string>
|
||||
<string name="menu_filters_active">Filters active</string>
|
||||
<string name="filters_activated">Filters activated</string>
|
||||
<string name="filters_deactivated">Filters deactivated</string>
|
||||
@@ -96,10 +97,8 @@
|
||||
<string name="edit">edit</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="pref_language">Language</string>
|
||||
<string name="pref_language_summary">Change the app language</string>
|
||||
<string name="pref_language">App language</string>
|
||||
<string name="pref_darkmode">Dark mode</string>
|
||||
<string name="pref_darkmode_summary">Set when dark mode is activated</string>
|
||||
<string name="connection_error">Could not load charging stations</string>
|
||||
<string name="retry">Retry</string>
|
||||
<string name="filter_open_247">Available 24/7</string>
|
||||
@@ -111,7 +110,7 @@
|
||||
<string name="twitter">Twitter</string>
|
||||
<string name="goingelectric_forum">Forum thread at GoingElectric.de</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="menu_report_new_charger">Report new charger</string>
|
||||
<string name="menu_report_new_charger">New charger</string>
|
||||
<string name="edit_at_datasource">edit at %s</string>
|
||||
<string name="categories">Categories</string>
|
||||
<string name="category_car_dealership">Car Dealership</string>
|
||||
@@ -129,7 +128,7 @@
|
||||
<string name="category_church">Church</string>
|
||||
<string name="category_hospital">Hospital</string>
|
||||
<string name="category_museum">Museum</string>
|
||||
<string name="category_parking_multi">Multi-storey car park</string>
|
||||
<string name="category_parking_multi">Parking garage</string>
|
||||
<string name="category_parking">Car park</string>
|
||||
<string name="category_private_charger">Private charger</string>
|
||||
<string name="category_rest_area">Rest area</string>
|
||||
@@ -149,16 +148,16 @@
|
||||
<string name="delete">Delete</string>
|
||||
<string name="save_as_profile">Save as profile</string>
|
||||
<string name="save_profile_enter_name">Enter the name of the filter profile:</string>
|
||||
<string name="filterprofiles_empty_state">You have not yet saved any filter profiles.</string>
|
||||
<string name="filterprofiles_empty_state">You have no filter profiles saved</string>
|
||||
<string name="welcome_to_evmap">Welcome to EVMap</string>
|
||||
<string name="welcome_1">Find electric vehicle chargers around you.</string>
|
||||
<string name="welcome_1">Find electric vehicle chargers around you</string>
|
||||
<string name="welcome_2_title">You\'ve got the power</string>
|
||||
<string name="welcome_2">The color of a charger on the map shows you its maximum charging power.</string>
|
||||
<string name="welcome_2_detail">(You can check the colors again under “About EVMap → FAQ” in the menu)</string>
|
||||
<string name="donation_dialog_title">Thank you for using EVMap!</string>
|
||||
<string name="donation_dialog_detail">EVMap is free and Open Source software that I develop in my spare time. Coding contributions on GitHub are very much appreciated. However, due to increasing popularity of the app, I also need to cover some running costs, e.g. for access to the data sources. Therefore, please consider supporting the app through a donation or via GitHub Sponsors.</string>
|
||||
<string name="welcome_2">Each charger\'s color corresponds to its max charging power</string>
|
||||
<string name="welcome_2_detail">This can also be seen in “About” → “Frequently Asked Questions”</string>
|
||||
<string name="donation_dialog_title">Thank you for using EVMap</string>
|
||||
<string name="donation_dialog_detail">EVMap is libre and free of charge. Code contributions on GitHub are much appreciated. To help cover the running costs for data access, please consider donating an amount of your choice to the developer.</string>
|
||||
<string name="chargeprice_donation_dialog_title">You\'re a real bargain hunter!</string>
|
||||
<string name="chargeprice_donation_dialog_detail">It seems like you like the price comparison feature a lot. To access the pricing data, the developer of EVMap needs to pay a monthly fee to the data provider Chargeprice.app. Therefore, please consider supporting EVMap through a donation.</string>
|
||||
<string name="chargeprice_donation_dialog_detail">You make great use of the price comparison feature. Please help cover the costs for this data by supporting EVMap with a donation.</string>
|
||||
<string name="deleted_filterprofile">Deleted “%s”</string>
|
||||
<string name="undo">Undo</string>
|
||||
<string name="rename">Rename</string>
|
||||
@@ -169,40 +168,43 @@
|
||||
</plurals>
|
||||
<string name="navigate">Navigate</string>
|
||||
<string name="verified">verified</string>
|
||||
<string name="verified_desc">Charger verified by a member at the %s community — not necessarily working right now.</string>
|
||||
<string name="verified_desc">Charger has once been confirmed to work by a member of the %s community</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="chargeprice_provider_customer_tariff">Only for tie-in customers</string>
|
||||
<string name="edit_on_goingelectric_info">Please log in at GoingElectric.de if this page is empty</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="chargeprice_no_tariffs_found">No charging plans for this charger on Chargeprice.app</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 vehicles</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="pref_chargeprice_no_base_fee">Exclude plans with monthly fees</string>
|
||||
<string name="pref_chargeprice_show_provider_customer_tariffs">Include tie-in plans</string>
|
||||
<string name="chargeprice_select_car_first">Please select your car model in the settings first</string>
|
||||
<string name="chargeprice_battery_range">Charge from %1$.0f%% to %2$.0f%%</string>
|
||||
<string name="chargeprice_battery_range_from">Charge from</string>
|
||||
<string name="chargeprice_battery_range_to">to</string>
|
||||
<string name="chargeprice_stats">(%1$.0f kWh, approx. %2$s, ⌀ %3$.0f kW)</string>
|
||||
<string name="chargeprice_vehicle">Vehicle</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="pref_chargeprice_show_provider_customer_tariffs_summary">Utility companies sometimes offer special plans for their customers</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="chargeprice_no_compatible_connectors">No compatible connectors at this charging station</string>
|
||||
<string name="pref_chargeprice_currency">Currency</string>
|
||||
<string name="pref_my_tariffs">My charging plans</string>
|
||||
<string name="pref_my_tariffs_summary">(will be highlighted in price comparison)</string>
|
||||
<plurals name="pref_my_tariffs_summary">
|
||||
<item quantity="one">(will be highlighted in the price comparison)</item>
|
||||
<item quantity="other">(will be highlighted in the price comparison)</item>
|
||||
</plurals>
|
||||
<string name="chargeprice_all_tariffs_selected">all plans selected</string>
|
||||
<string name="license">License</string>
|
||||
<string name="settings_charger_data">Charging stations</string>
|
||||
@@ -212,20 +214,20 @@
|
||||
<item quantity="other">%d plans selected</item>
|
||||
</plurals>
|
||||
<string name="unknown_operator">Unknown operator</string>
|
||||
<string name="data_sources_description">EVMap supports multiple data sources for charging stations. Please select the one you would like to use. You can always change it later in the app\'s settings.</string>
|
||||
<string name="data_sources_description">Please pick a data source for charging stations. It can later be changed in the app settings.</string>
|
||||
<string name="data_source_goingelectric">GoingElectric.de</string>
|
||||
<string name="data_source_openchargemap">Open Charge Map</string>
|
||||
<string name="data_source_goingelectric_desc">Very good coverage in Germany, Austria and Switzerland and many neighboring countries. Descriptions in German. Community-maintained.</string>
|
||||
<string name="data_source_openchargemap_desc"><![CDATA[Worldwide coverage with varying quality. Descriptions in English or local language. Community-maintained & government open data in some countries (e.g. North America, UK, France, Norway).]]></string>
|
||||
<string name="data_source_goingelectric_desc">Great in the German-speaking countries. Descriptions in German. Community-maintained.</string>
|
||||
<string name="data_source_openchargemap_desc"><![CDATA[Worldwide, with varying quality. Descriptions in English or the local language. Community-maintained and open government data in some countries (e.g. North America, UK, France, Norway).]]></string>
|
||||
<string name="next">next</string>
|
||||
<string name="get_started">Get started</string>
|
||||
<string name="got_it">Got it</string>
|
||||
<string name="lets_go">Let\'s go</string>
|
||||
<string name="crash_report_text">Sorry, it seems that EVMap has crashed. Please send a crash report to the developer.</string>
|
||||
<string name="crash_report_text">EVMap crashed. Please send a crash report to the developer.</string>
|
||||
<string name="crash_report_comment_prompt">You can add a comment below:</string>
|
||||
<string name="powered_by_mapbox">powered by Mapbox</string>
|
||||
<string name="pref_search_provider">Place search provider</string>
|
||||
<string name="pref_search_provider_info"><![CDATA[Data for place search, especially from Google Maps, is relatively expensive. If you use this feature often, please consider making a donation through \"About EVMap -> Donate\".]]></string>
|
||||
<string name="pref_search_provider">Search provider</string>
|
||||
<string name="pref_search_provider_info"><![CDATA[Data for searches is expensive to fetch, especially from Google Maps. Please consider donating through “About” → “Donate”.]]></string>
|
||||
<string name="github_sponsors">GitHub Sponsors</string>
|
||||
<string name="donate_desc">Support EVMap\'s development with a one-time donation</string>
|
||||
<string name="github_sponsors_desc">Support EVMap on GitHub Sponsors</string>
|
||||
@@ -240,11 +242,31 @@
|
||||
<string name="settings_data_sources">Data sources</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="settings_android_auto">Android Auto</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load">Enable unbalanced load</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load_summary"><![CDATA[Allow charging with >4.5 kW at AC stations for cars with single-phase charger]]></string>
|
||||
<string name="pref_map_rotate_gestures_enabled">Enable map rotation</string>
|
||||
<string name="pref_map_rotate_gestures_on">Map can be rotated with two-finger gesture</string>
|
||||
<string name="pref_map_rotate_gestures_off">Map will be fixed to north-up</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load">Allow unbalanced load</string>
|
||||
<string name="pref_chargeprice_allow_unbalanced_load_summary">Allow single-phase AC charging with more than 4.5 kW</string>
|
||||
<string name="pref_map_rotate_gestures_enabled">Map rotation</string>
|
||||
<string name="pref_map_rotate_gestures_on">Use two fingers to rotate the map</string>
|
||||
<string name="pref_map_rotate_gestures_off">Rotation off (north always up)</string>
|
||||
<string name="refresh_live_data">refresh real-time status</string>
|
||||
<string name="autocomplete_connection_error">Suggestions could not be loaded</string>
|
||||
<string name="autocomplete_connection_error">Could not load suggestions</string>
|
||||
<string name="pref_language_device_default">Device default</string>
|
||||
<string name="pref_darkmode_device_default">Device default</string>
|
||||
<string name="pref_darkmode_always_on">always on</string>
|
||||
<string name="pref_darkmode_always_off">always off</string>
|
||||
<string name="pref_chargeprice_currency_chf">Swiss franc (CHF)</string>
|
||||
<string name="pref_chargeprice_currency_czk">Czech koruna (CZK)</string>
|
||||
<string name="pref_chargeprice_currency_dkk">Danish krone (DKK)</string>
|
||||
<string name="pref_chargeprice_currency_eur">Euro (EUR)</string>
|
||||
<string name="pref_chargeprice_currency_gbp">Pound sterling (GBP)</string>
|
||||
<string name="pref_chargeprice_currency_hrk">Croatian kuna (HRK)</string>
|
||||
<string name="pref_chargeprice_currency_huf">Hungarian forint (HUF)</string>
|
||||
<string name="pref_chargeprice_currency_isk">Icelandic króna (ISK)</string>
|
||||
<string name="pref_chargeprice_currency_nok">Norwegian krone (NOK)</string>
|
||||
<string name="pref_chargeprice_currency_pln">Polish złoty (PLN)</string>
|
||||
<string name="pref_chargeprice_currency_sek">Swedish krona (SEK)</string>
|
||||
<string name="pref_chargeprice_currency_usd">US dollar (USD)</string>
|
||||
<string name="pref_provider_google_maps">Google Maps</string>
|
||||
<string name="pref_provider_osm_mapbox">OpenStreetMap (Mapbox)</string>
|
||||
<string name="about_contributors">Contributors</string>
|
||||
<string name="about_contributors_text">Thanks to all contributors for their coding and translation contributions to EVMap:</string>
|
||||
</resources>
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<item name="colorOnSecondaryContainer">@color/colorSecondaryDark</item>
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="preferenceTheme">@style/AppTheme.Preference</item>
|
||||
<item name="alertDialogTheme">@style/AppTheme.AlertDialog</item>
|
||||
<item name="materialAlertDialogTheme">@style/AppTheme.AlertDialog</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Preference" parent="@style/PreferenceThemeOverlay">
|
||||
@@ -67,4 +69,10 @@
|
||||
<item name="iconTint">?android:textColorSecondary</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AlertDialog" parent="ThemeOverlay.Material3.MaterialAlertDialog">
|
||||
<!-- this is necessary to make sure the dialog gets "pushed up" when the keyboard appears -->
|
||||
<item name="android:windowTranslucentStatus">false</item>
|
||||
<item name="dialogCornerRadius">28dp</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -14,10 +14,13 @@
|
||||
android:title="@string/copyright"
|
||||
android:summary="@string/copyright_summary" />
|
||||
|
||||
<Preference
|
||||
android:key="contributors"
|
||||
android:title="@string/about_contributors" />
|
||||
|
||||
<Preference
|
||||
android:key="faq"
|
||||
android:title="@string/faq"
|
||||
android:summary="@string/faq_desc" />
|
||||
android:title="@string/faq" />
|
||||
|
||||
<Preference
|
||||
android:key="donate"
|
||||
|
||||
7
app/src/main/res/xml/locales_config.xml
Normal file
7
app/src/main/res/xml/locales_config.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en" />
|
||||
<locale android:name="de" />
|
||||
<locale android:name="fr" />
|
||||
<locale android:name="nb-NO" />
|
||||
</locale-config>
|
||||
@@ -8,8 +8,7 @@
|
||||
app:defaultToAll="false" />
|
||||
<net.vonforst.evmap.ui.MultiSelectDialogPreference
|
||||
android:key="chargeprice_my_tariffs"
|
||||
android:title="@string/pref_my_tariffs"
|
||||
android:summary="@string/pref_my_tariffs_summary" />
|
||||
android:title="@string/pref_my_tariffs" />
|
||||
<ListPreference
|
||||
android:key="chargeprice_currency"
|
||||
android:title="@string/pref_chargeprice_currency"
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ListPreference
|
||||
android:key="language"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_language"
|
||||
android:entries="@array/pref_language_names"
|
||||
android:entryValues="@array/pref_language_values"
|
||||
android:defaultValue="default"
|
||||
android:summary="@string/pref_language_summary" />
|
||||
android:summary="%s" />
|
||||
|
||||
<ListPreference
|
||||
android:key="darkmode"
|
||||
@@ -14,7 +15,7 @@
|
||||
android:entries="@array/pref_darkmode_names"
|
||||
android:entryValues="@array/pref_darkmode_values"
|
||||
android:defaultValue="default"
|
||||
android:summary="@string/pref_darkmode_summary" />
|
||||
android:summary="%s" />
|
||||
<CheckBoxPreference
|
||||
android:key="map_rotate_gestures_enabled"
|
||||
android:title="@string/pref_map_rotate_gestures_enabled"
|
||||
|
||||
@@ -63,14 +63,14 @@ class ChargepriceApiTest {
|
||||
|
||||
runBlocking {
|
||||
val result = chargeprice.getChargePrices(
|
||||
ChargepriceRequest().apply {
|
||||
dataAdapter = "going_electric"
|
||||
ChargepriceRequest(
|
||||
dataAdapter = "going_electric",
|
||||
station =
|
||||
ChargepriceStation.fromEvmap(charger, listOf("Typ2", "Schuko"))
|
||||
ChargepriceStation.fromEvmap(charger, listOf("Typ2", "Schuko")),
|
||||
options = ChargepriceOptions(energy = 22.0, duration = 60)
|
||||
}, "en"
|
||||
), "en"
|
||||
)
|
||||
assertEquals(25, result.size)
|
||||
assertEquals(25, result.data!!.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
ext.about_libs_version = '8.9.4'
|
||||
ext.nav_version = '2.5.1'
|
||||
ext.nav_version = '2.5.2'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libs_version"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
@@ -26,7 +26,6 @@ allprojects {
|
||||
google()
|
||||
mavenCentral()
|
||||
//noinspection JcenterRepositoryObsolete
|
||||
jcenter() // still required for https://github.com/kamikat/moshi-jsonapi
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,9 @@ Not all API keys are strictly required if you only want to work on certain parts
|
||||
example, you can choose only one of the map providers and one of the charging station databases. The
|
||||
Chargeprice API key is also only required if you want to test the price comparison feature.
|
||||
|
||||
All API keys are available for free. Some APIs require payment above a certain limit, but the free
|
||||
tier should be plenty for local testing and development.
|
||||
All APIs can be used for free, at least for testing. Some APIs require payment above a certain usage
|
||||
limit or to get access to the full dataset, but the free tiers should be plenty for local testing
|
||||
and development.
|
||||
|
||||
Below you find a list of all the services and how to obtain the API keys.
|
||||
|
||||
@@ -152,14 +153,19 @@ Pricing providers
|
||||
<details>
|
||||
<summary>How to obtain an API key</summary>
|
||||
|
||||
1. Check the
|
||||
[Pricing page](https://github.com/chargeprice/chargeprice-api-docs/blob/master/plans.md)
|
||||
for information on the current plans at Chargeprice. There should be a free tier up to a certain
|
||||
limit of API calls per month.
|
||||
2. Contact [contact@chargeprice.net](mailto:contact@chargeprice.net), stating that you would like to
|
||||
contribute to the development the open source EVMap app and therefore need access to the
|
||||
Chargeprice API for testing.
|
||||
3. When your access to the API is approved, you will receive an API key via email.
|
||||
Since February 2022, the Chargeprice API is no longer available for free to new customers. However,
|
||||
you can use their
|
||||
[staging API](https://github.com/chargeprice/chargeprice-api-docs/blob/master/test_the_api.md)
|
||||
for free to test the Chargeprice features. This is already
|
||||
[configured](https://github.com/johan12345/EVMap/blob/master/app/src/debug/res/values/donottranslate.xml)
|
||||
by default for the debug version of the app, so you can leave the `chargeprice_key` field in your
|
||||
new `app/src/main/res/values/apikeys.xml` file blank. Note that the staging API contains only a
|
||||
limited dataset, so it only outputs prices for certain charge point operators and payment plans (see
|
||||
[here](https://docs.google.com/document/d/14zlFr5IEhhR3uGXO5QePKjNUQANVwA-Ba-cZbOCiOBk/edit) for
|
||||
details).
|
||||
|
||||
In case you want to pay for access to the full Chargeprice API, check out their
|
||||
[API docs](https://github.com/chargeprice/chargeprice-api-docs) on GitHub and contact them at
|
||||
[sales@chargeprice.net](mailto:sales@chargeprice.net).
|
||||
</details>
|
||||
|
||||
|
||||
12
fastlane/metadata/android/de-DE/changelogs/102.txt
Normal file
12
fastlane/metadata/android/de-DE/changelogs/102.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
Unter https://hosted.weblate.org/engage/evmap/ können Sie jetzt dazu beitragen,
|
||||
EVMap in andere Sprachen zu übersetzen!
|
||||
|
||||
Verbesserungen:
|
||||
- Neues Design für Dialoge
|
||||
- Karte kann bei geöffnetem Filtermenü verschoben werden
|
||||
- Vorbereitungen für Übersetzung der App in andere Sprachen
|
||||
- Android 11 und niedriger: Ortung verbessert, wenn GPS aktiviert aber nicht verfügbar (z.B. in Gebäuden)
|
||||
|
||||
Fehler behoben:
|
||||
- Absturz im Preisvergleich behoben
|
||||
- Verbesserungen für weitere kleine Darstellungsfehler
|
||||
9
fastlane/metadata/android/de-DE/changelogs/104.txt
Normal file
9
fastlane/metadata/android/de-DE/changelogs/104.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
Verbesserungen:
|
||||
- Übersetzung auf Norwegisch und Französisch (Danke an die Beitragenden!)
|
||||
- Kontrast der Marker auf der Karte (v.a. gelb) erhöht
|
||||
- Android Automotive OS: Filter "Störung ausschließen" verfügbar
|
||||
|
||||
Fehler behoben:
|
||||
- "Meine Tarife" wurden nicht mehr oben angeordnet und hervorgehoben
|
||||
- Dark Mode: Weißes Aufblitzen bei Wechsel zur Karte teilweise reduziert
|
||||
- Android Auto: Abstürze behoben
|
||||
2
fastlane/metadata/android/de-DE/changelogs/106.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/106.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Fehler behoben:
|
||||
- Wechseln der Datenquelle nicht möglich
|
||||
8
fastlane/metadata/android/de-DE/changelogs/116.txt
Normal file
8
fastlane/metadata/android/de-DE/changelogs/116.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Verbesserungen:
|
||||
- Einige Texte vereinfacht
|
||||
- Unterstützung für Sprachauswahl pro App von Android 13
|
||||
|
||||
Fehler behoben:
|
||||
- Filtermenü ließ sich nicht öffnen
|
||||
- Abstürze / Inkonsistenzen nach Wechsel der Datenquelle
|
||||
- Abstürze unter Android Auto
|
||||
8
fastlane/metadata/android/de-DE/changelogs/120.txt
Normal file
8
fastlane/metadata/android/de-DE/changelogs/120.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Verbesserungen:
|
||||
- Weitere unterstützte Länder für Preisvergleich mit Chargeprice.app
|
||||
- Android Auto: Suchbutton nun auf dem Hauptbildschirm
|
||||
- Android Auto: Emoji durch Icons ersetzt
|
||||
|
||||
Fehler behoben:
|
||||
- Abstürze / Inkonsistenzen nach Wechsel der Datenquelle
|
||||
- Probleme beim Laden der Echtzeitdaten
|
||||
2
fastlane/metadata/android/de-DE/changelogs/124.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/124.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Fehler behoben:
|
||||
- verschiedene filterabhängige Anzeigen waren seit 1.3.11 nicht mehr korrekt
|
||||
1
fastlane/metadata/android/de-DE/changelogs/126.txt
Normal file
1
fastlane/metadata/android/de-DE/changelogs/126.txt
Normal file
@@ -0,0 +1 @@
|
||||
Reverted Android Auto changes in 1.3.13, which caused crashes
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user