Compare commits

...

29 Commits
1.0.0 ... 1.1.0

Author SHA1 Message Date
johan12345
9966b44a76 Release 1.1.0 2021-10-17 21:29:53 +02:00
johan12345
d44b2206d2 update build status badge 2021-10-17 15:49:56 +02:00
johan12345
f61082f491 fix incorrect formatting in api_keys.md 2021-10-17 15:27:01 +02:00
johan12345
f58d96c939 fix links in README 2021-10-17 15:26:08 +02:00
Johan von Forstner
29aedfa3d9 Merge pull request #140 from johan12345/contributing-docs
Improve docs for first-time contributors
2021-10-17 15:25:15 +02:00
johan12345
8331f92f10 Improve docs for first-time contributors
adds two docs pages with info on API keys and Android Auto testing.
fixes #112
2021-10-17 15:24:50 +02:00
johan12345
123680d3e8 replace Mapbox android-plugin-places with just the Java SDK
(the UI has been replaced by our own one in #120)
2021-10-17 12:43:16 +02:00
johan12345
0f6b45d745 upgrade navigation component to 2.4.0-alpha10
(necessary for Android 12 compatibility of deep links)
2021-10-17 12:11:28 +02:00
Johan von Forstner
69faa94f18 Merge pull request #139 from pt2121/pt/safeArgs
refactor to use Android Navigation Component's SafeArgs
2021-10-17 12:10:16 +02:00
prat t
70805b7960 refactor to use Navigation Component's SafeArgs 2021-10-16 15:16:56 -07:00
johan12345
56453b0658 remove obsolete TODO 2021-10-16 15:51:36 +02:00
johan12345
975d95e37e refactor app settings into separate sub-screens
for better overview
2021-10-16 15:49:00 +02:00
johan12345
ba34cd016a fix static splashscreen icon size
after upgrade to core-splashscreen 1.0.0-alpha02 in e2bcf8d1
2021-10-16 12:36:29 +02:00
johan12345
590b16aa49 make debug build distinguishable from release build
with grayscale app icon + different label
fixes #113
2021-10-16 12:25:26 +02:00
johan12345
5fe8d0cab4 replace Google Maps v3 beta with Play Services version 17.0.1
fixes #124
fixes #30
2021-10-16 12:16:27 +02:00
johan12345
9d7b181410 remove focus from search when selecting a charger 2021-10-10 17:55:16 +02:00
johan12345
128532aac6 open autocomplete list more quickly 2021-10-10 17:51:54 +02:00
johan12345
486854f56c Android Auto: use slightly darker color for >100 kW chargers for better contrast 2021-10-09 19:16:21 +02:00
johan12345
1e30db5cd1 GoingElectricApi: map plug types correctly to names 2021-10-09 13:55:25 +02:00
johan12345
aad386ab04 MultiSelectDialog: put common choices on top even when selected 2021-10-09 13:44:17 +02:00
johan12345
e2bcf8d1cd upgrade dependencies 2021-10-08 22:02:04 +02:00
Johan von Forstner
f56fad1282 Merge pull request #134 from johan12345/filter-by-favorites
add possibility to show only the favorites on a map
2021-10-08 21:51:46 +02:00
johan12345
adb4d938cc add possibility to show only the favorites on a map (fixes #119) 2021-10-08 21:51:13 +02:00
Johan von Forstner
b773f65912 Update screenshot URLs 2021-10-06 08:39:13 +02:00
johan12345
de335b18d8 PlaceAutocompleteAdapter: do not modify resultList on background thread 2021-10-05 21:43:47 +02:00
johan12345
6c8380b8ce revisit Android Auto location service connection
to possibly fix IllegalArgumentException: Service not registered
based on https://stackoverflow.com/questions/22079909/android-java-lang-illegalargumentexception-service-not-registered
2021-10-05 21:12:03 +02:00
Johan von Forstner
81afdca19d Merge pull request #130 from johan12345/highlight_favorites
highlight favorites on map
2021-10-03 13:33:12 +02:00
johan12345
14e03ba6dd highlight favorites on map
fixes #118
2021-10-03 13:31:58 +02:00
Johan von Forstner
abe12b45c3 disable git LFS for screenshots
(not supported on F-Droid
2021-10-03 12:55:11 +02:00
94 changed files with 965 additions and 389 deletions

View File

@@ -1,4 +1,4 @@
EVMap [![Build Status](https://travis-ci.org/johan12345/EVMap.svg?branch=master)](https://travis-ci.org/johan12345/EVMap)
EVMap [![Build Status](https://app.travis-ci.com/johan12345/EVMap.svg?branch=master)](https://app.travis-ci.com/johan12345/EVMap)
=====
<img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/feature_graphic.svg" width=700 alt="Logo"/>
@@ -28,38 +28,22 @@ Features
Screenshots
-----------
<img src="https://media.githubusercontent.com/media/johan12345/EVMap/master/_img/screenshots/phone/en/mapbox/01_map.png" width=250 alt="Screenshot 1"/><img src="https://media.githubusercontent.com/media/johan12345/EVMap/master/_img/screenshots/phone/en/mapbox/02_detail.png" width=250 alt="Screenshot 2"/>
<img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/screenshots/phone/en/mapbox/01_map.png" width=250 alt="Screenshot 1"/><img src="https://raw.githubusercontent.com/johan12345/EVMap/master/_img/screenshots/phone/en/mapbox/02_detail.png" width=250 alt="Screenshot 2"/>
Development setup
-----------------
The App is developed using Android Studio.
The App is developed using Android Studio and should pretty much work out-of-the-box when you clone
the Git repository and open the project with Android Studio.
For testing the app, you need to obtain free API Keys for the
[GoingElectric API](https://www.goingelectric.de/stromtankstellen/api/),
the [Chargeprice API](https://github.com/chargeprice/chargeprice-api-docs),
the [OpenChargeMap API](https://openchargemap.org/site/profile/appedit),
as well as for [Google APIs](https://console.developers.google.com/)
("Maps SDK for Android" and "Places API" need to be activated) and/or [Mapbox](https://www.mapbox.com/). These API keys need to be put into the
app in the form of a resource file called `apikeys.xml` under `app/src/main/res/values`, with the
following content:
The only exception is that you need to obtain some free API keys for the different data sources that
EVMap uses and put them into the app in the form of a resource file called `apikeys.xml` under
`app/src/main/res/values`. You can find more information on which API keys are necessary for which
features and how they can be obtained in our [documentation page](doc/api_keys.md).
```xml
<resources>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
insert your Google Maps key here
</string>
<string name="mapbox_key" translatable="false">
insert your Mapbox key here
</string>
<string name="goingelectric_key" translatable="false">
insert your GoingElectric key here
</string>
<string name="chargeprice_key" translatable="false">
insert your Chargeprice key here
</string>
<string name="openchargemap_key" translatable="false">
insert your OpenChargeMap key here
</string>
</resources>
```
There are two different build flavors, `google` and `foss`, where only the `google` variant uses
Google Maps data and provides the Android Auto integration. The `foss` variant only uses Mapbox data
and should run on devices without Google Play Services.
We also have a special [documentation page](doc/android_auto.md) on how to test the Android Auto
app.

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 194 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 94 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 88 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 45 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 242 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 90 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 88 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 42 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 972 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 352 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 81 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 113 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 875 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 837 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 341 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 94 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 134 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 B

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 972 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 343 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 83 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 106 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 864 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 837 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 330 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 95 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 122 KiB

View File

@@ -13,8 +13,8 @@ android {
applicationId "net.vonforst.evmap"
minSdkVersion 21
targetSdkVersion 31
versionCode 63
versionName "1.0.0"
versionCode 64
versionName "1.1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@@ -106,17 +106,16 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha01'
implementation 'androidx.core:core-splashscreen:1.0.0-alpha02'
implementation "androidx.activity:activity-ktx:1.3.1"
implementation "androidx.fragment:fragment-ktx:1.3.6"
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.browser:browser:1.3.0'
implementation 'com.github.johan12345:CustomBottomSheetBehavior:f69f532660'
@@ -144,32 +143,17 @@ dependencies {
googleImplementation 'androidx.car.app:app-projected:1.1.0-beta01'
// AnyMaps
def anyMapsVersion = '95ddd6c083'
def anyMapsVersion = '8c0d46e7a6'
implementation "com.github.johan12345.AnyMaps:anymaps-base:$anyMapsVersion"
googleImplementation "com.github.johan12345.AnyMaps:anymaps-google:$anyMapsVersion"
implementation "com.github.johan12345.AnyMaps:anymaps-mapbox:$anyMapsVersion"
// Google Maps v3 Beta
googleImplementation 'com.google.android.libraries.maps:maps:3.1.0-beta'
googleImplementation name:'places-maps-sdk-3.1.0-beta', ext:'aar'
googleImplementation 'com.android.volley:volley:1.2.0'
googleImplementation 'com.google.android.gms:play-services-base:17.5.0'
googleImplementation 'com.google.android.gms:play-services-basement:17.5.0'
googleImplementation 'com.google.android.gms:play-services-gcm:17.0.0'
googleImplementation 'com.google.android.gms:play-services-location:17.1.0'
googleImplementation 'com.google.android.gms:play-services-tasks:17.2.0'
googleImplementation 'com.google.auto.value:auto-value-annotations:1.6.3'
googleImplementation 'com.google.code.gson:gson:2.8.6'
googleImplementation 'com.google.android.datatransport:transport-runtime:2.2.5'
googleImplementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
// Google Places
implementation 'com.google.android.libraries.places:places:2.5.0'
googleImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'
// Mapbox places (autocomplete)
// forked this library and included through JitPack to fix https://github.com/mapbox/mapbox-plugins-android/issues/1011
implementation('com.github.johan12345.mapbox-plugins-android:mapbox-android-plugin-places-v9:922bf877f6') {
exclude group: 'com.mapbox.mapboxsdk', module: 'mapbox-android-accounts'
exclude group: 'com.mapbox.mapboxsdk', module: 'mapbox-android-telemetry'
}
// Mapbox Geocoding
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:5.5.0'
// navigation library
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"

View File

Binary file not shown.

View File

@@ -0,0 +1,107 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="198.3471"
android:viewportHeight="198.3471">
<group
android:translateX="3.1735537"
android:translateY="3.1735537">
<group>
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
<path
android:pathData="M106.2,74.3h-7"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#212121"
android:fillAlpha="0.2" />
</group>
<group>
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
<path
android:pathData="M106.2,60.3c0,0 -17.5,0 -17.5,0"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#212121"
android:fillAlpha="0.2" />
</group>
<group>
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
<path
android:pathData="M93.9,79.5L88.7,79.5"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.2" />
</group>
<group>
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
<path
android:strokeWidth="1"
android:pathData="M94,79v16.2"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#212121"
android:fillAlpha="0.2"
android:strokeLineCap="round" />
</group>
<group>
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
<path
android:strokeWidth="1"
android:pathData="M106.2,60.3L99.2,74.3"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.2"
android:strokeLineCap="round" />
</group>
<group>
<clip-path android:pathData="M122.5,57.1c-5.8,-7.7 -15.2,-12.4 -24.7,-12.9H94c-8.1,0.5 -15.9,3.9 -21.7,9.6c-4.3,4.3 -7.4,9.7 -8.6,15.7c-1.5,7.5 -0.1,15.4 3.5,22.1c2.3,4.5 5.2,8.6 8.1,12.8c4.9,6.8 9.8,13.7 13.2,21.3c3.1,6.5 4.9,13.6 5.6,20.8c0.4,0.6 1.1,1 1.7,1.5c2.6,-1 2,-4 2.5,-6.2c1.9,-12.8 8.7,-24.2 16.1,-34.6c6.6,-9.2 14.1,-19 14.4,-30.8V74C128.5,67.9 126.3,61.9 122.5,57.1zM106.2,74.3l-12.2,21V79.5h-5.2V60.3h17.5l-7,14H106.2z" />
<path
android:strokeWidth="1"
android:pathData="M106.2,74.3L93.9,95.2"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillAlpha="0.2"
android:strokeLineCap="round" />
</group>
<path
android:pathData="M67.6,120.6L65.7,104l-2.9,0.3l1.9,16.6L67.6,120.6zM77.9,119.4l-1.9,-16.6l-2.9,0.3l1.9,16.6L77.9,119.4z"
android:fillColor="#808080" />
<path
android:pathData="M83.3,142c-0.9,1.1 -1.6,1.8 -1.7,1.9c-2.6,2.1 -4.7,2.7 -6.4,1.9c-3,-1.5 -2.8,-7.1 -2.7,-7.7l2.1,0.1c-0.1,1.6 0.2,5 1.6,5.7c0.8,0.4 2.2,-0.1 4,-1.6l0,0c0,0 5.8,-5.8 4.6,-10.4c-1.4,-5.5 5,-13.4 7.1,-16.1l0.3,-0.3l1.7,1.3l-0.3,0.4c-6.5,8 -7.2,12.1 -6.7,14.2C87.9,135.4 85.2,139.7 83.3,142z"
android:fillColor="#9e9e9e" />
<path
android:pathData="M61.2,120.4l0.8,6.8l6.3,4.2l8.5,-0.9l5.2,-5.5l-0.8,-6.8L61.2,120.4z"
android:fillColor="#9e9e9e" />
<path
android:pathData="M76.7,130.5l-8.5,0.9l1.8,7.5l6.7,-0.8L76.7,130.5L76.7,130.5zM82.8,112.5l0.7,6.2l-24.4,2.8l-0.7,-6.2L82.8,112.5z"
android:fillColor="#666666" />
<path
android:pathData="M101.9,44.1c-17.5,0 -31.7,14.2 -31.7,31.7c0,23.9 26.7,36.4 29.9,70.5c0.1,1 0.9,1.7 1.9,1.7s1.8,-0.7 1.9,-1.7c3.2,-34.1 29.9,-46.6 29.9,-70.5C133.6,58.2 119.4,44.1 101.9,44.1z"
android:fillColor="#737373" />
<path
android:pathData="M101.9,44.8c17.4,0 31.5,14 31.7,31.3c0,-0.1 0,-0.2 0,-0.3c0,-17.5 -14.2,-31.7 -31.7,-31.7S70.2,58.2 70.2,75.8c0,0.1 0,0.2 0,0.3C70.4,58.8 84.5,44.8 101.9,44.8L101.9,44.8z"
android:fillColor="#FFFFFF"
android:fillAlpha="0.2" />
<path
android:pathData="M103.8,145.5c-0.1,1 -0.9,1.7 -1.9,1.7s-1.8,-0.7 -1.9,-1.7c-3.1,-34 -29.6,-46.5 -29.8,-70.1c0,0.2 0,0.3 0,0.5c0,23.9 26.7,36.4 29.9,70.5c0.1,1 0.9,1.7 1.9,1.7s1.8,-0.7 1.9,-1.7c3.2,-34.1 29.9,-46.6 29.9,-70.5c0,-0.2 0,-0.3 0,-0.5C133.4,99 106.9,111.5 103.8,145.5L103.8,145.5z"
android:fillColor="#303030"
android:fillAlpha="0.2" />
<path
android:fillColor="#FF000000"
android:pathData="M94.6,60.3v19.2h5.2v15.7l12.2,-21h-7l7,-14C112.1,60.3 94.6,60.3 94.6,60.3z"
android:strokeAlpha="0.45"
android:fillAlpha="0.45" />
</group>
</vector>

View File

@@ -62,6 +62,7 @@ class EVMapSession(val cas: CarAppService) : Session(), LifecycleObserver {
locationService = null
}
}
private var serviceBound = false
init {
lifecycle.addObserver(this)
@@ -91,13 +92,9 @@ class EVMapSession(val cas: CarAppService) : Session(), LifecycleObserver {
private fun onCarHardwareLocationReceived(loc: CarHardwareLocation) {
updateLocation(loc.location.value)
locationService?.let { service ->
// we successfully received a location from the car hardware,
// so we don't need the smartphone location anymore.
service.removeLocationUpdates()
cas.unbindService(serviceConnection)
locationService = null
}
// we successfully received a location from the car hardware,
// so we don't need the smartphone location anymore.
unbindLocationService()
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
@@ -111,7 +108,7 @@ class EVMapSession(val cas: CarAppService) : Session(), LifecycleObserver {
::onCarHardwareLocationReceived
)
}
cas.bindService(
serviceBound = cas.bindService(
Intent(cas, CarLocationService::class.java),
serviceConnection,
Context.BIND_AUTO_CREATE
@@ -119,13 +116,18 @@ class EVMapSession(val cas: CarAppService) : Session(), LifecycleObserver {
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun unbindLocationService() {
private fun onStop() {
if (supportsCarApiLevel3(carContext)) {
hardwareMan.carSensors.removeCarHardwareLocationListener(::onCarHardwareLocationReceived)
}
locationService?.let { service ->
service.removeLocationUpdates()
unbindLocationService()
}
private fun unbindLocationService() {
locationService?.removeLocationUpdates()
if (serviceBound) {
cas.unbindService(serviceConnection)
serviceBound = false
}
}

View File

@@ -7,7 +7,9 @@ import androidx.car.app.model.*
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.LiveData
import net.vonforst.evmap.R
import net.vonforst.evmap.model.FILTERS_CUSTOM
import net.vonforst.evmap.model.FILTERS_DISABLED
import net.vonforst.evmap.model.FILTERS_FAVORITES
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.FilterProfile
import net.vonforst.evmap.storage.PreferenceDataSource
@@ -40,9 +42,12 @@ class FilterScreen(ctx: CarContext) : Screen(ctx) {
}
override fun onGetTemplate(): Template {
val filterStatus =
prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM || it == FILTERS_FAVORITES }
?: FILTERS_DISABLED
return ListTemplate.Builder().apply {
filterProfiles.value?.let {
setSingleList(buildFilterProfilesList(it.take(maxRows), prefs.filterStatus))
setSingleList(buildFilterProfilesList(it.take(maxRows), filterStatus))
} ?: setLoading(true)
setTitle(carContext.getString(R.string.menu_filter))
setHeaderAction(Action.BACK)

View File

@@ -22,6 +22,7 @@ import net.vonforst.evmap.api.stringProvider
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.FILTERS_CUSTOM
import net.vonforst.evmap.model.FILTERS_DISABLED
import net.vonforst.evmap.model.FILTERS_FAVORITES
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.availabilityText
@@ -69,7 +70,8 @@ class MapScreen(ctx: CarContext, val session: EVMapSession, val favorites: Boole
private val referenceData = api.getReferenceData(lifecycleScope, carContext)
private val filterStatus = MutableLiveData<Long>().apply {
value = prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM } ?: FILTERS_DISABLED
value = prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM || it == FILTERS_FAVORITES }
?: FILTERS_DISABLED
}
private val filterValues = db.filterValueDao().getFilterValues(filterStatus, prefs.dataSource)
private val filters = api.getFilters(referenceData, carContext.stringProvider())
@@ -139,7 +141,9 @@ class MapScreen(ctx: CarContext, val session: EVMapSession, val favorites: Boole
screenManager.pushForResult(FilterScreen(carContext)) {
chargers = null
numUpdates = 0
filterStatus.value = prefs.filterStatus
filterStatus.value =
prefs.filterStatus.takeUnless { it == FILTERS_CUSTOM || it == FILTERS_FAVORITES }
?: FILTERS_DISABLED
}
session.mapScreen = null
}
@@ -151,7 +155,12 @@ class MapScreen(ctx: CarContext, val session: EVMapSession, val favorites: Boole
}
private fun formatCharger(charger: ChargeLocation, showCity: Boolean): Row {
val color = ContextCompat.getColor(carContext, getMarkerTint(charger))
val markerTint = if (charger.maxPower > 100) {
R.color.charger_100kw_dark // slightly darker color for better contrast
} else {
getMarkerTint(charger)
}
val color = ContextCompat.getColor(carContext, markerTint)
val place =
Place.Builder(CarLocation.create(charger.coordinates.lat, charger.coordinates.lng))
.setMarker(

View File

@@ -7,9 +7,9 @@ import android.text.style.StyleSpan
import com.car2go.maps.google.adapter.AnyMapAdapter
import com.car2go.maps.util.SphericalUtil
import com.google.android.gms.common.api.ApiException
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.gms.tasks.Tasks.await
import com.google.android.libraries.maps.model.LatLng
import com.google.android.libraries.maps.model.LatLngBounds
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.AutocompleteSessionToken
import com.google.android.libraries.places.api.model.Place

View File

@@ -3,4 +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>
</resources>

View File

@@ -32,7 +32,7 @@
<activity
android:name=".MapsActivity"
android:label="@string/title_activity_maps"
android:label="@string/app_name"
android:theme="@style/AppTheme.LaunchScreen"
android:exported="true">
<intent-filter>

View File

@@ -19,11 +19,15 @@ import androidx.core.view.WindowInsetsCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.FragmentNavigator
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupWithNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.car2go.maps.model.LatLng
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import net.vonforst.evmap.fragment.MapFragment
import net.vonforst.evmap.fragment.MapFragmentArgs
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.utils.LocaleContextWrapper
@@ -35,7 +39,8 @@ const val EXTRA_CHARGER_ID = "chargerId"
const val EXTRA_LAT = "lat"
const val EXTRA_LON = "lon"
class MapsActivity : AppCompatActivity() {
class MapsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
interface FragmentCallback {
fun getRootView(): View
}
@@ -102,11 +107,11 @@ class MapsActivity : AppCompatActivity() {
}
})
}
navGraph.startDestination = R.id.onboarding
navGraph.setStartDestination(R.id.onboarding)
navController.graph = navGraph
return
} else {
navGraph.startDestination = R.id.map
navGraph.setStartDestination(R.id.map)
navController.graph = navGraph
}
@@ -121,14 +126,14 @@ class MapsActivity : AppCompatActivity() {
val deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragment.showLocation(lat, lon))
.setArguments(MapFragmentArgs(latLng = LatLng(lat, lon)).toBundle())
.createPendingIntent()
deepLink.send()
} else if (query != null && query.isNotEmpty()) {
val deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragment.showLocationByName(query))
.setArguments(MapFragmentArgs(locationName = query).toBundle())
.createPendingIntent()
deepLink.send()
}
@@ -138,7 +143,7 @@ class MapsActivity : AppCompatActivity() {
val deepLink = navController.createDeepLink()
.setGraph(R.navigation.nav_graph)
.setDestination(R.id.map)
.setArguments(MapFragment.showChargerById(id))
.setArguments(MapFragmentArgs(chargerId = id).toBundle())
.createPendingIntent()
deepLink.send()
}
@@ -146,11 +151,13 @@ class MapsActivity : AppCompatActivity() {
navController.createDeepLink()
.setDestination(R.id.map)
.setArguments(
MapFragment.showCharger(
intent.getLongExtra(EXTRA_CHARGER_ID, 0),
intent.getDoubleExtra(EXTRA_LAT, 0.0),
intent.getDoubleExtra(EXTRA_LON, 0.0)
)
MapFragmentArgs(
chargerId = intent.getLongExtra(EXTRA_CHARGER_ID, 0),
latLng = LatLng(
intent.getDoubleExtra(EXTRA_LAT, 0.0),
intent.getDoubleExtra(EXTRA_LON, 0.0)
)
).toBundle()
)
.createPendingIntent()
.send()
@@ -213,4 +220,15 @@ class MapsActivity : AppCompatActivity() {
}
startActivity(intent)
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Identify the Navigation Destination
val navDestination = navController.graph
.find { target -> target is FragmentNavigator.Destination && pref.fragment == target.className }
navDestination?.let { target -> navController.navigate(target.id) }
return true
}
}

View File

@@ -98,11 +98,12 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
init {
if (PreferenceDataSource(context).searchProvider == "mapbox") {
// set delay to 500 ms to reduce paid Mapbox API requests
this.setDelayer { 500L }
this.setDelayer { q -> if (isShortQuery(q)) 0L else 500L }
}
}
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
resultList = results?.values as? List<AutocompletePlace>?
if (results != null && results.count > 0) {
notifyDataSetChanged()
} else {
@@ -112,6 +113,7 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
override fun performFiltering(constraint: CharSequence?): FilterResults {
val query = constraint.toString()
var resultList: List<AutocompletePlace>? = null
if (constraint != null) {
for (provider in providers) {
try {
@@ -132,14 +134,13 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
}
// if we already have enough results or the query is short, stop here
if (query.length < 3 || recentResults.size >= maxItems) break
if (isShortQuery(query) || recentResults.size >= maxItems) break
// then search online
val recentIds = recentPlaces.map { it.id }
resultList =
(recentPlaces.map { it.asAutocompletePlace(location.value) } +
provider.autocomplete(query, location.value)
.filter { !recentIds.contains(it.id) }).take(maxItems)
(resultList!! + provider.autocomplete(query, location.value)
.filter { !recentIds.contains(it.id) }).take(maxItems)
break
} catch (e: ApiUnavailableException) {
e.printStackTrace()
@@ -150,7 +151,7 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
if (currentProvider is MapboxAutocompleteProvider && !delaySet) {
// set delay to 500 ms to reduce paid Mapbox API requests
this.setDelayer { 500L }
this.setDelayer { q -> if (isShortQuery(q)) 0L else 500L }
}
return resultList.asFilterResults()
@@ -167,6 +168,8 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
}
}
private fun isShortQuery(query: CharSequence) = query.length < 3
suspend fun getDetails(id: String): PlaceWithBounds {
val provider = currentProvider!!
val result = resultList!!.find { it.id == id }!!

View File

@@ -402,7 +402,7 @@ class GoingElectricApiWrapper(
val chargeCards = referenceData.chargecards
val plugMap = plugs.map { plug ->
plug to nameForPlugType(sp, plug)
plug to nameForPlugType(sp, GEChargepoint.convertTypeFromGE(plug))
}.toMap()
val networkMap = networks.map { it to it }.toMap()
val chargecardMap = chargeCards.map { it.id.toString() to it.name }.toMap()
@@ -448,11 +448,11 @@ class GoingElectricApiWrapper(
MultipleChoiceFilter(
sp.getString(R.string.filter_connectors), "connectors",
plugMap,
commonChoices = setOf(
commonChoices = listOf(
Chargepoint.TYPE_2_UNKNOWN,
Chargepoint.CCS_UNKNOWN,
Chargepoint.CHADEMO
),
).map { GEChargepoint.convertTypeToGE(it)!! }.toSet(),
manyChoices = true
),
SliderFilter(

View File

@@ -11,6 +11,7 @@ import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.navigation.ui.setupWithNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@@ -20,15 +21,10 @@ 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.ChargepointApi
import net.vonforst.evmap.api.chargeprice.ChargepriceCar
import net.vonforst.evmap.api.equivalentPlugTypes
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
import net.vonforst.evmap.databinding.FragmentChargepriceBinding
import net.vonforst.evmap.model.ChargeLocation
import net.vonforst.evmap.model.Chargepoint
import net.vonforst.evmap.model.ReferenceData
import net.vonforst.evmap.viewmodel.ChargepriceViewModel
import net.vonforst.evmap.viewmodel.Status
import net.vonforst.evmap.viewmodel.viewModelFactory
@@ -84,8 +80,9 @@ class ChargepriceFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val charger = requireArguments().getParcelable<ChargeLocation>(ARG_CHARGER)!!
val dataSource = requireArguments().getString(ARG_DATASOURCE)!!
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) {
@@ -216,28 +213,4 @@ class ChargepriceFragment : DialogFragment() {
)
}
companion object {
const val ARG_CHARGER = "charger"
const val ARG_DATASOURCE = "datasource"
fun showCharger(
charger: ChargeLocation,
dataSource: Class<ChargepointApi<ReferenceData>>
): Bundle {
return Bundle().apply {
putParcelable(
ARG_CHARGER,
charger
)
putString(
ARG_DATASOURCE,
when (dataSource) {
GoingElectricApiWrapper::class.java -> "going_electric"
OpenChargeMapApiWrapper::class.java -> "open_charge_map"
else -> throw IllegalArgumentException("unsupported data source")
}
)
}
}
}
}

View File

@@ -77,7 +77,10 @@ class FavoritesFragment : Fragment(), LostApiClient.ConnectionCallbacks {
onClickListener = {
findNavController().navigate(
R.id.action_favs_to_map,
MapFragment.showCharger(it.charger)
MapFragmentArgs(
chargerId = it.charger.id,
latLng = LatLng(it.charger.coordinates.lat, it.charger.coordinates.lng)
).toBundle()
)
}
}

View File

@@ -33,6 +33,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.navigation.ui.setupWithNavController
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
@@ -72,6 +73,7 @@ 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.databinding.FragmentMapBinding
@@ -89,11 +91,6 @@ import net.vonforst.evmap.viewmodel.*
import java.io.IOException
const val ARG_CHARGER_ID = "chargerId"
const val ARG_LAT = "lat"
const val ARG_LON = "lon"
const val ARG_LOCATION_NAME = "locationName"
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback,
LostApiClient.ConnectionCallbacks, LocationListener {
private lateinit var binding: FragmentMapBinding
@@ -309,9 +306,14 @@ 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")
}
findNavController().navigate(
R.id.action_map_to_chargepriceFragment,
ChargepriceFragment.showCharger(charger, vm.apiType)
ChargepriceFragmentArgs(charger, dataSource).toBundle()
)
}
binding.detailView.topPart.setOnClickListener {
@@ -442,11 +444,21 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
private fun toggleFavorite() {
val favs = vm.favorites.value ?: return
val charger = vm.chargerSparse.value ?: return
if (favs.find { it.id == charger.id } != null) {
val isFav = favs.find { it.id == charger.id } != null
if (isFav) {
vm.deleteFavorite(charger)
} else {
vm.insertFavorite(charger)
}
markers.inverse[charger]?.setIcon(
chargerIconGenerator.getBitmapDescriptor(
getMarkerTint(charger, vm.filteredConnectors.value),
highlight = true,
fault = charger.faultReport != null,
multi = charger.isMulti(vm.filteredConnectors.value),
fav = !isFav
)
)
}
private fun setupObservers() {
@@ -475,6 +487,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
if (vm.bottomSheetState.value != BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT) {
bottomSheetBehavior.state = STATE_COLLAPSED
}
removeSearchFocus()
binding.fabDirections.show()
detailAppBarBehavior.setToolbarTitle(it.name)
updateFavoriteToggle()
@@ -573,7 +586,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
getMarkerTint(c, vm.filteredConnectors.value),
highlight = false,
fault = c.faultReport != null,
multi = c.isMulti(vm.filteredConnectors.value)
multi = c.isMulti(vm.filteredConnectors.value),
fav = c.id in vm.favorites.value?.map { it.id } ?: emptyList()
)
)
}
@@ -587,7 +601,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
getMarkerTint(charger, vm.filteredConnectors.value),
highlight = true,
fault = charger.faultReport != null,
multi = charger.isMulti(vm.filteredConnectors.value)
multi = charger.isMulti(vm.filteredConnectors.value),
fav = charger.id in vm.favorites.value?.map { it.id } ?: emptyList()
)
)
animator.animateMarkerBounce(marker)
@@ -600,7 +615,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
getMarkerTint(c, vm.filteredConnectors.value),
highlight = false,
fault = c.faultReport != null,
multi = c.isMulti(vm.filteredConnectors.value)
multi = c.isMulti(vm.filteredConnectors.value),
fav = c.id in vm.favorites.value?.map { it.id } ?: emptyList()
)
)
}
@@ -802,10 +818,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
val position = vm.mapPosition.value
val lat = arguments?.optDouble(ARG_LAT)
val lon = arguments?.optDouble(ARG_LON)
val chargerId = arguments?.optLong(ARG_CHARGER_ID)
val locationName = arguments?.getString(ARG_LOCATION_NAME)
val fragmentArgs: MapFragmentArgs by navArgs()
val locationName = fragmentArgs.locationName
val chargerId = fragmentArgs.chargerId
val latLng = fragmentArgs.latLng
var positionSet = false
@@ -814,7 +830,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
map.cameraUpdateFactory.newLatLngZoom(position.bounds.center, position.zoom)
map.moveCamera(cameraUpdate)
positionSet = true
} else if (chargerId != null && (lat == null || lon == null)) {
} else if (chargerId != 0L && latLng == null) {
// show given charger ID
vm.loadChargerById(chargerId)
vm.chargerSparse.observe(
@@ -832,13 +848,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
})
positionSet = true
} else if (lat != null && lon != null) {
} else if (latLng != null) {
// show given position
val latLng = LatLng(lat, lon)
val cameraUpdate = map.cameraUpdateFactory.newLatLngZoom(latLng, 16f)
map.moveCamera(cameraUpdate)
if (chargerId != null) {
if (chargerId != 0L) {
// show charger detail after chargers were loaded
vm.chargepoints.observe(
viewLifecycleOwner,
@@ -943,7 +958,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
getMarkerTint(charger, vm.filteredConnectors.value),
highlight = charger == vm.chargerSparse.value,
fault = charger.faultReport != null,
multi = charger.isMulti(vm.filteredConnectors.value)
multi = charger.isMulti(vm.filteredConnectors.value),
fav = charger.id in vm.favorites.value?.map { it.id } ?: emptyList()
)
)
}
@@ -961,7 +977,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
val highlight = charger == vm.chargerSparse.value
val fault = charger.faultReport != null
val multi = charger.isMulti(vm.filteredConnectors.value)
animator.animateMarkerDisappear(marker, tint, highlight, fault, multi)
val fav = charger.id in vm.favorites.value?.map { it.id } ?: emptyList()
animator.animateMarkerDisappear(marker, tint, highlight, fault, multi, fav)
} else {
animator.deleteMarker(marker)
}
@@ -976,6 +993,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
val highlight = charger == vm.chargerSparse.value
val fault = charger.faultReport != null
val multi = charger.isMulti(vm.filteredConnectors.value)
val fav = charger.id in vm.favorites.value?.map { it.id } ?: emptyList()
val marker = map.addMarker(
MarkerOptions()
.position(LatLng(charger.coordinates.lat, charger.coordinates.lng))
@@ -986,12 +1004,13 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
255,
highlight,
fault,
multi
multi,
fav
)
)
.anchor(0.5f, 1f)
)
animator.animateMarkerAppear(marker, tint, highlight, fault, multi)
animator.animateMarkerAppear(marker, tint, highlight, fault, multi, fav)
markers[marker] = charger
}
}
@@ -1083,6 +1102,11 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
R.id.menu_group_filter_profiles,
Menu.NONE, Menu.NONE, R.string.no_filters
)
val favoritesItem = popup.menu.add(
R.id.menu_group_filter_profiles,
Menu.NONE,
Menu.NONE, R.string.filter_favorites
)
profiles.forEach { profile ->
val item = popup.menu.add(
R.id.menu_group_filter_profiles,
@@ -1099,11 +1123,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
profilesMap[FILTERS_DISABLED] = noFiltersItem
profilesMap[FILTERS_CUSTOM] = customItem
profilesMap[FILTERS_FAVORITES] = favoritesItem
popup.menu.setGroupCheckable(R.id.menu_group_filter_profiles, true, true);
val manageFiltersItem = popup.menu.findItem(R.id.menu_manage_filter_profiles)
manageFiltersItem.isVisible = !profiles.isEmpty()
manageFiltersItem.isVisible = profiles.isNotEmpty()
vm.filterStatus.observe(viewLifecycleOwner, Observer { id ->
when (id) {
@@ -1115,6 +1140,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
customItem.isVisible = true
customItem.isChecked = true
}
FILTERS_FAVORITES -> {
customItem.isVisible = false
favoritesItem.isChecked = true
}
else -> {
customItem.isVisible = false
val item = profilesMap[id]
@@ -1153,43 +1182,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
return binding.root
}
companion object {
fun showCharger(charger: ChargeLocation): Bundle {
return Bundle().apply {
putLong(ARG_CHARGER_ID, charger.id)
putDouble(ARG_LAT, charger.coordinates.lat)
putDouble(ARG_LON, charger.coordinates.lng)
}
}
fun showLocation(lat: Double, lon: Double): Bundle {
return Bundle().apply {
putDouble(ARG_LAT, lat)
putDouble(ARG_LON, lon)
}
}
fun showChargerById(id: Long): Bundle {
return Bundle().apply {
putLong(ARG_CHARGER_ID, id)
}
}
fun showCharger(id: Long, lat: Double, lon: Double): Bundle {
return Bundle().apply {
putLong(ARG_CHARGER_ID, id)
putDouble(ARG_LAT, lat)
putDouble(ARG_LON, lon)
}
}
fun showLocationByName(query: String): Bundle {
return Bundle().apply {
putString(ARG_LOCATION_NAME, query)
}
}
}
override fun onConnected() {
val map = this.map ?: return
val context = this.context ?: return

View File

@@ -81,9 +81,10 @@ class MultiSelectDialog : AppCompatDialogFragment() {
.sortedBy { it.value.toLowerCase(Locale.getDefault()) }
.sortedBy {
when {
selected.contains(it.key) -> 0
commonChoices?.contains(it.key) == true -> 1
else -> 2
selected.contains(it.key) && commonChoices?.contains(it.key) == true -> 0
selected.contains(it.key) -> 1
commonChoices?.contains(it.key) == true -> 2
else -> 3
}
}
.map { MultiSelectItem(it.key, it.value, it.key in selected) }

View File

@@ -1,4 +1,4 @@
package net.vonforst.evmap.fragment
package net.vonforst.evmap.fragment.preference
import android.os.Bundle
import androidx.appcompat.widget.Toolbar

View File

@@ -0,0 +1,40 @@
package net.vonforst.evmap.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.appcompat.widget.Toolbar
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
import androidx.preference.PreferenceFragmentCompat
import net.vonforst.evmap.MapsActivity
import net.vonforst.evmap.R
import net.vonforst.evmap.storage.PreferenceDataSource
abstract class BaseSettingsFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
protected lateinit var prefs: PreferenceDataSource
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
prefs = PreferenceDataSource(requireContext())
}
override fun onResume() {
super.onResume()
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
val navController = findNavController()
val toolbar = requireView().findViewById(R.id.toolbar) as Toolbar
toolbar.setupWithNavController(
navController,
(requireActivity() as MapsActivity).appBarConfiguration
)
}
override fun onPause() {
preferenceManager.sharedPreferences
.unregisterOnSharedPreferenceChangeListener(this)
super.onPause()
}
}

View File

@@ -1,28 +1,15 @@
package net.vonforst.evmap.fragment
package net.vonforst.evmap.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import net.vonforst.evmap.MapsActivity
import net.vonforst.evmap.R
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.ui.updateNightMode
import net.vonforst.evmap.viewmodel.SettingsViewModel
import net.vonforst.evmap.viewmodel.viewModelFactory
class SettingsFragment : PreferenceFragmentCompat(),
SharedPreferences.OnSharedPreferenceChangeListener {
private lateinit var prefs: PreferenceDataSource
class ChargepriceSettingsFragment : BaseSettingsFragment() {
private val vm: SettingsViewModel by viewModels(factoryProducer = {
viewModelFactory {
SettingsViewModel(
@@ -37,7 +24,6 @@ class SettingsFragment : PreferenceFragmentCompat(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
prefs = PreferenceDataSource(requireContext())
myVehiclePreference = findPreference("chargeprice_my_vehicle")!!
myVehiclePreference.isEnabled = false
@@ -92,68 +78,21 @@ class SettingsFragment : PreferenceFragmentCompat(),
"${it.brand} ${it.name}"
}.joinToString(", ")
myVehiclePreference.summary = summary
// TODO: prefs.chargepriceMyVehicleDcChargeports = it.dcChargePorts
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey)
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
"search_delete_recent" -> {
Toast.makeText(context, R.string.deleted_recent_search_results, Toast.LENGTH_LONG)
.show()
vm.deleteRecentSearchResults()
true
}
else -> super.onPreferenceTreeClick(preference)
}
setPreferencesFromResource(R.xml.settings_chargeprice, rootKey)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
"language" -> {
activity?.let {
it.finish();
it.startActivity(it.intent);
}
}
"darkmode" -> {
updateNightMode(prefs)
}
"chargeprice_my_vehicle" -> {
updateMyVehiclesSummary()
}
"chargeprice_my_tariffs" -> {
updateMyTariffsSummary()
}
"search_provider" -> {
if (prefs.searchProvider == "google") {
Toast.makeText(context, R.string.pref_search_provider_info, Toast.LENGTH_LONG)
.show()
}
}
}
}
override fun onResume() {
super.onResume()
preferenceManager.sharedPreferences.registerOnSharedPreferenceChangeListener(this)
val navController = findNavController()
val toolbar = requireView().findViewById(R.id.toolbar) as Toolbar
toolbar.setupWithNavController(
navController,
(requireActivity() as MapsActivity).appBarConfiguration
)
}
override fun onPause() {
preferenceManager.sharedPreferences
.unregisterOnSharedPreferenceChangeListener(this)
super.onPause()
}
}

View File

@@ -0,0 +1,63 @@
package net.vonforst.evmap.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import android.widget.TextView
import androidx.fragment.app.viewModels
import androidx.preference.Preference
import com.google.android.material.snackbar.Snackbar
import net.vonforst.evmap.R
import net.vonforst.evmap.viewmodel.SettingsViewModel
import net.vonforst.evmap.viewmodel.viewModelFactory
class DataSettingsFragment : BaseSettingsFragment() {
private val vm: SettingsViewModel by viewModels(factoryProducer = {
viewModelFactory {
SettingsViewModel(
requireActivity().application,
getString(R.string.chargeprice_key)
)
}
})
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_data, rootKey)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
"search_provider" -> {
if (prefs.searchProvider == "google") {
Snackbar.make(
requireView(),
R.string.pref_search_provider_info,
Snackbar.LENGTH_INDEFINITE
).apply {
setAction(R.string.ok) {}
this.view.findViewById<TextView>(com.google.android.material.R.id.snackbar_text)
?.apply {
maxLines = 6
}
}
.show()
}
}
}
}
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
"search_delete_recent" -> {
Snackbar.make(
requireView(),
R.string.deleted_recent_search_results,
Snackbar.LENGTH_LONG
)
.show()
vm.deleteRecentSearchResults()
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
}

View File

@@ -0,0 +1,22 @@
package net.vonforst.evmap.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import net.vonforst.evmap.R
class SettingsFragment : BaseSettingsFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings, rootKey)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
}
}

View File

@@ -0,0 +1,26 @@
package net.vonforst.evmap.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import net.vonforst.evmap.R
import net.vonforst.evmap.ui.updateNightMode
class UiSettingsFragment : BaseSettingsFragment() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_ui, rootKey)
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
when (key) {
"language" -> {
activity?.let {
it.finish();
it.startActivity(it.intent);
}
}
"darkmode" -> {
updateNightMode(prefs)
}
}
}
}

View File

@@ -129,4 +129,5 @@ fun FilterValues.getMultipleChoiceValue(key: String) =
this.find { it.value.key == key }?.value as MultipleChoiceFilterValue?
const val FILTERS_DISABLED = -2L
const val FILTERS_CUSTOM = -1L
const val FILTERS_CUSTOM = -1L
const val FILTERS_FAVORITES = -3L

View File

@@ -23,4 +23,12 @@ interface ChargeLocationsDao {
@Query("SELECT * FROM chargelocation")
fun getAllChargeLocationsBlocking(): List<ChargeLocation>
@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>
}

View File

@@ -54,7 +54,7 @@ abstract class FilterValueDao {
)
open fun getFilterValues(filterStatus: Long, dataSource: String): LiveData<List<FilterValue>> =
if (filterStatus == FILTERS_DISABLED) {
if (filterStatus == FILTERS_DISABLED || filterStatus == FILTERS_FAVORITES) {
MutableLiveData(emptyList())
} else {
MediatorLiveData<List<FilterValue>>().apply {

View File

@@ -55,7 +55,8 @@ class ChargerIconGenerator(
val alpha: Int,
val highlight: Boolean,
val fault: Boolean,
val multi: Boolean
val multi: Boolean,
val fav: Boolean
)
// 230 items: (21 sizes, 5 colors, multi on/off) + highlight + fault (only with scale = 1)
@@ -66,6 +67,7 @@ class ChargerIconGenerator(
private val highlightIcon = R.drawable.ic_map_marker_highlight
private val highlightIconMulti = R.drawable.ic_map_marker_charging_highlight_multiple
private val faultIcon = R.drawable.ic_map_marker_fault
private val favIcon = R.drawable.ic_map_marker_fav
fun preloadCache() {
// pre-generates images for scale from 0 to 255 for all possible tint colors
@@ -79,12 +81,14 @@ class ChargerIconGenerator(
for (fault in listOf(false, true)) {
for (highlight in listOf(false, true)) {
for (multi in listOf(false, true)) {
for (tint in tints) {
for (scale in 0..scaleResolution) {
getBitmapDescriptor(
tint, scale.toFloat() / scaleResolution,
255, highlight, fault, multi
)
for (fav in listOf(false, true)) {
for (tint in tints) {
for (scale in 0..scaleResolution) {
getBitmapDescriptor(
tint, scale.toFloat() / scaleResolution,
255, highlight, fault, multi, fav
)
}
}
}
}
@@ -98,14 +102,16 @@ class ChargerIconGenerator(
alpha: Int = 255,
highlight: Boolean = false,
fault: Boolean = false,
multi: Boolean = false
multi: Boolean = false,
fav: Boolean = false
): BitmapDescriptor? {
val data = BitmapData(
tint, (scale * scaleResolution).roundToInt(),
alpha,
if (scale == 1f) highlight else false,
if (scale == 1f) fault else false,
multi
multi,
if (scale == 1f) fav else false
)
val cachedImg = cache[data]
return if (cachedImg != null) {
@@ -124,14 +130,16 @@ class ChargerIconGenerator(
alpha: Int = 255,
highlight: Boolean = false,
fault: Boolean = false,
multi: Boolean = false
multi: Boolean = false,
fav: Boolean = false
): Bitmap {
val data = BitmapData(
tint, (scale * scaleResolution).roundToInt(),
alpha,
if (scale == 1f) highlight else false,
if (scale == 1f) fault else false,
multi
multi,
if (scale == 1f) fav else false,
)
return generateBitmap(data)
}
@@ -200,6 +208,22 @@ class ChargerIconGenerator(
faultDrawable.draw(canvas)
}
if (data.fav) {
val favDrawable = ContextCompat.getDrawable(context, favIcon)!!
val favSize = 0.75
val favShiftY = 0.25
val favShiftX = if (data.fault) -0.5 else 0.25
val base = width
favDrawable.setBounds(
(leftPadding.toInt() + base * (1 - favSize + favShiftX)).toInt(),
(topPadding.toInt() - base * favShiftY).toInt(),
(leftPadding.toInt() + base * (1 + favShiftX)).toInt(),
(topPadding.toInt() + base * (favSize - favShiftY)).toInt()
)
favDrawable.alpha = data.alpha
favDrawable.draw(canvas)
}
return bm
}
}

View File

@@ -29,7 +29,8 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
tint: Int,
highlight: Boolean,
fault: Boolean,
multi: Boolean
multi: Boolean,
fav: Boolean
) {
animatingMarkers[marker]?.let {
it.cancel()
@@ -47,7 +48,8 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
scale = scale,
highlight = highlight,
fault = fault,
multi = multi
multi = multi,
fav = fav
)
)
}
@@ -66,7 +68,8 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
tint: Int,
highlight: Boolean,
fault: Boolean,
multi: Boolean
multi: Boolean,
fav: Boolean
) {
animatingMarkers[marker]?.let {
it.cancel()
@@ -84,7 +87,8 @@ class MarkerAnimator(val gen: ChargerIconGenerator) {
scale = scale,
highlight = highlight,
fault = fault,
multi = multi
multi = multi,
fav = fav
)
)
}

View File

@@ -42,7 +42,7 @@ class FilterViewModel(application: Application) : AndroidViewModel(application)
MediatorLiveData<FilterProfile>().apply {
addSource(filterStatus) { id ->
when (id) {
FILTERS_CUSTOM, FILTERS_DISABLED -> value = null
FILTERS_CUSTOM, FILTERS_DISABLED, FILTERS_FAVORITES -> value = null
else -> viewModelScope.launch {
value = db.filterProfileDao().getProfileById(id, prefs.dataSource)
}

View File

@@ -23,6 +23,7 @@ import net.vonforst.evmap.model.*
import net.vonforst.evmap.storage.AppDatabase
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
@@ -304,6 +305,25 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
val filters = data.second
val api = api
val refData = data.third
if (filterStatus.value == FILTERS_FAVORITES) {
// load favorites from local DB
val b = mapPosition.bounds
var chargers = db.chargeLocationsDao().getChargeLocationsInBoundsAsync(
b.southwest.latitude,
b.northeast.latitude,
b.southwest.longitude,
b.northeast.longitude
) as List<ChargepointListItem>
val clusterDistance = getClusterDistance(mapPosition.zoom)
clusterDistance?.let {
chargers = cluster(chargers, mapPosition.zoom, clusterDistance)
}
chargepoints.value = Resource.success(chargers)
return@throttleLatest
}
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

View File

@@ -1,4 +1,5 @@
<vector android:height="15.811624dp" android:viewportHeight="131.5"
android:tint="?attr/colorControlNormal"
android:viewportWidth="199.6" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M197.544,65.685l-9.2,-4.8l8,-4.2c2.7,-1.4 2.7,-3.8 0,-5.2l-8.6,-4.5l8.6,-4.5c2.7,-1.4 2.7,-3.8 0,-5.2l-68.9,-36.1c-2.7,-1.4 -7.2,-1.4 -9.9,0l-115.5,59.7c-2.7,1.4 -2.7,3.7 0,5.1l8.8,4.5l-8.8,4.6c-2.7,1.4 -2.7,3.7 0,5.1l9.4,4.8l-8.2,4.3c-2.7,1.4 -2.7,3.7 0,5.1l70.4,36.2c2.7,1.4 7.2,1.4 9.9,0l114,-59.6C200.344,69.385 200.344,67.085 197.544,65.685L197.544,65.685zM123.144,18.785L105.844,38.685c-0.9,1 -0.6,2.3 0.6,2.9l13.7,7.1c1.2,0.6 1.2,1.6 0,2.2l-43.1,22.3c-1.2,0.6 -1.4,0.3 -0.6,-0.7l17.3,-19.9c0.9,-1 0.6,-2.3 -0.6,-2.9l-13.7,-7.1c-1.2,-0.6 -1.2,-1.6 0,-2.2l43.1,-22.3C123.744,17.485 123.944,17.785 123.144,18.785L123.144,18.785z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF4081"
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" />
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="#000"
android:pathData="M12,3C7.58,3 4,4.79 4,7C4,9.21 7.58,11 12,11C12.5,11 13,10.97 13.5,10.92V9.5H16.39L15.39,8.5L18.9,5C17.5,3.8 14.94,3 12,3M18.92,7.08L17.5,8.5L20,11H15V13H20L17.5,15.5L18.92,16.92L23.84,12M4,9V12C4,14.21 7.58,16 12,16C13.17,16 14.26,15.85 15.25,15.63L16.38,14.5H13.5V12.92C13,12.97 12.5,13 12,13C7.58,13 4,11.21 4,9M4,14V17C4,19.21 7.58,21 12,21C14.94,21 17.5,20.2 18.9,19L17,17.1C15.61,17.66 13.9,18 12,18C7.58,18 4,16.21 4,14Z" />
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M7,14c-1.66,0 -3,1.34 -3,3 0,1.31 -1.16,2 -2,2 0.92,1.22 2.49,2 4,2 2.21,0 4,-1.79 4,-4 0,-1.66 -1.34,-3 -3,-3zM20.71,4.63l-1.34,-1.34c-0.39,-0.39 -1.02,-0.39 -1.41,0L9,12.25 11.75,15l8.96,-8.96c0.39,-0.39 0.39,-1.02 0,-1.41z" />
</vector>

View File

@@ -1,13 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="160dp"
android:height="160dp"
android:viewportWidth="120"
android:viewportHeight="120">
android:width="192dp"
android:height="192dp"
android:viewportWidth="192"
android:viewportHeight="192">
<group android:name="_R_G">
<group
android:name="_R_G_L_2_G_N_1_T_0"
android:translateX="58"
android:translateY="60">
android:translateX="94"
android:translateY="96">
<group
android:name="_R_G_L_2_G"
android:pivotX="53.625"
@@ -43,8 +43,8 @@
</group>
<group
android:name="_R_G_L_1_G_N_1_T_0"
android:translateX="58"
android:translateY="60">
android:translateX="94"
android:translateY="96">
<group
android:name="_R_G_L_1_G"
android:translateX="-26.049"
@@ -77,8 +77,8 @@
</group>
<group
android:name="_R_G_L_0_G_N_1_T_0"
android:translateX="58"
android:translateY="60">
android:translateX="94"
android:translateY="96">
<group
android:name="_R_G_L_0_G"
android:translateX="-1.3999999999999995"

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/chip_background" />
<item android:drawable="?selectableItemBackground" />
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/chip_background" />
<item android:drawable="?selectableItemBackground" />
</layer-list>

View File

@@ -33,10 +33,24 @@
<action
android:id="@+id/action_map_to_opensource_donations"
app:destination="@id/opensource_donations" />
<argument
android:name="locationName"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="chargerId"
android:defaultValue="0L"
app:argType="long" />
<argument
android:name="latLng"
android:defaultValue="@null"
app:argType="com.car2go.maps.model.LatLng"
app:nullable="true" />
</fragment>
<fragment
android:id="@+id/about"
android:name="net.vonforst.evmap.fragment.AboutFragment"
android:name="net.vonforst.evmap.fragment.preference.AboutFragment"
android:label="@string/about"
tools:layout="@layout/fragment_preference">
<action
@@ -48,9 +62,24 @@
</fragment>
<fragment
android:id="@+id/settings"
android:name="net.vonforst.evmap.fragment.SettingsFragment"
android:name="net.vonforst.evmap.fragment.preference.SettingsFragment"
android:label="@string/settings"
tools:layout="@layout/fragment_preference" />
<fragment
android:id="@+id/settings_ui"
android:name="net.vonforst.evmap.fragment.preference.UiSettingsFragment"
android:label="@string/settings_ui"
tools:layout="@layout/fragment_preference" />
<fragment
android:id="@+id/settings_data"
android:name="net.vonforst.evmap.fragment.preference.DataSettingsFragment"
android:label="@string/settings_data_sources"
tools:layout="@layout/fragment_preference" />
<fragment
android:id="@+id/settings_chargeprice"
android:name="net.vonforst.evmap.fragment.preference.ChargepriceSettingsFragment"
android:label="@string/settings_chargeprice"
tools:layout="@layout/fragment_preference" />
<fragment
android:id="@+id/favs"
android:name="net.vonforst.evmap.fragment.FavoritesFragment"
@@ -82,6 +111,12 @@
app:enterAnim="@animator/nav_default_enter_anim"
app:popEnterAnim="@animator/nav_default_enter_anim"
app:popExitAnim="@animator/nav_default_exit_anim" />
<argument
android:name="charger"
app:argType="net.vonforst.evmap.model.ChargeLocation" />
<argument
android:name="dataSource"
app:argType="string" />
</dialog>
<fragment
android:id="@+id/donate"

View File

@@ -143,6 +143,7 @@
<string name="menu_save_profile">Als Profil speichern</string>
<string name="no_filters">Keine Filter</string>
<string name="filter_custom">Verändertes Filterprofil</string>
<string name="filter_favorites">Favoriten</string>
<string name="reorder">Reihenfolge ändern</string>
<string name="delete">Löschen</string>
<string name="save_as_profile">Als Profil speichern</string>
@@ -244,4 +245,5 @@
<string name="edit_filter_profile">„%s“ bearbeiten</string>
<string name="pref_search_delete_recent">Suchverlauf löschen</string>
<string name="deleted_recent_search_results">Suchverlauf wurde gelöscht</string>
<string name="settings_data_sources">Datenquellen</string>
</resources>

View File

@@ -142,6 +142,7 @@
<string name="menu_save_profile">Save as profile</string>
<string name="no_filters">No filters</string>
<string name="filter_custom">Modified filter</string>
<string name="filter_favorites">Favorites</string>
<string name="reorder">reorder</string>
<string name="delete">Delete</string>
<string name="save_as_profile">Save as profile</string>
@@ -229,4 +230,5 @@
<string name="edit_filter_profile">Edit “%s”</string>
<string name="pref_search_delete_recent">Delete recent search results</string>
<string name="deleted_recent_search_results">Recent search results have been deleted</string>
<string name="settings_data_sources">Data sources</string>
</resources>

View File

@@ -1,91 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/settings_ui">
<ListPreference
android:key="language"
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" />
<ListPreference
android:key="darkmode"
android:title="@string/pref_darkmode"
android:entries="@array/pref_darkmode_names"
android:entryValues="@array/pref_darkmode_values"
android:defaultValue="default"
android:summary="@string/pref_darkmode_summary" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_charger_data">
<net.vonforst.evmap.ui.DataSourceSelectDialogPreference
android:key="data_source"
android:title="@string/pref_data_source"
android:entries="@array/pref_data_source_names"
android:entryValues="@array/pref_data_source_values"
android:defaultValue="goingelectric"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_map">
<ListPreference
android:key="map_provider"
android:title="@string/pref_map_provider"
android:entries="@array/pref_map_provider_names"
android:entryValues="@array/pref_map_provider_values"
android:defaultValue="@string/pref_map_provider_default"
android:summary="%s" />
<ListPreference
android:key="search_provider"
android:title="@string/pref_search_provider"
android:entries="@array/pref_search_provider_names"
android:entryValues="@array/pref_search_provider_values"
android:defaultValue="@string/pref_search_provider_default"
android:summary="%s" />
<Preference
android:key="search_delete_recent"
android:title="@string/pref_search_delete_recent" />
<CheckBoxPreference
android:key="navigate_use_maps"
android:title="@string/pref_navigate_use_maps"
android:summaryOn="@string/pref_navigate_use_maps_on"
android:summaryOff="@string/pref_navigate_use_maps_off"
android:defaultValue="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_chargeprice">
<net.vonforst.evmap.ui.MultiSelectDialogPreference
android:key="chargeprice_my_vehicle"
android:title="@string/pref_my_vehicle"
app:showAllButton="false"
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" />
<ListPreference
android:key="chargeprice_currency"
android:title="@string/pref_chargeprice_currency"
android:entries="@array/pref_chargeprice_currency_names"
android:entryValues="@array/pref_chargeprice_currency_values"
android:defaultValue="EUR"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:key="chargeprice_no_base_fee"
android:title="@string/pref_chargeprice_no_base_fee"
android:defaultValue="false"
app:singleLineTitle="false" />
<CheckBoxPreference
android:key="chargeprice_show_provider_customer_tariffs"
android:title="@string/pref_chargeprice_show_provider_customer_tariffs"
android:summary="@string/pref_chargeprice_show_provider_customer_tariffs_summary"
android:defaultValue="false"
app:singleLineTitle="false" />
</PreferenceCategory>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<Preference
android:fragment="net.vonforst.evmap.fragment.preference.UiSettingsFragment"
android:title="@string/settings_ui"
android:icon="@drawable/ic_settings_ui" />
<Preference
android:fragment="net.vonforst.evmap.fragment.preference.DataSettingsFragment"
android:title="@string/settings_data_sources"
android:icon="@drawable/ic_settings_data_source" />
<Preference
android:fragment="net.vonforst.evmap.fragment.preference.ChargepriceSettingsFragment"
android:title="@string/settings_chargeprice"
android:icon="@drawable/ic_chargeprice" />
</PreferenceScreen>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<net.vonforst.evmap.ui.MultiSelectDialogPreference
android:key="chargeprice_my_vehicle"
android:title="@string/pref_my_vehicle"
app:showAllButton="false"
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" />
<ListPreference
android:key="chargeprice_currency"
android:title="@string/pref_chargeprice_currency"
android:entries="@array/pref_chargeprice_currency_names"
android:entryValues="@array/pref_chargeprice_currency_values"
android:defaultValue="EUR"
app:useSimpleSummaryProvider="true" />
<CheckBoxPreference
android:key="chargeprice_no_base_fee"
android:title="@string/pref_chargeprice_no_base_fee"
android:defaultValue="false"
app:singleLineTitle="false" />
<CheckBoxPreference
android:key="chargeprice_show_provider_customer_tariffs"
android:title="@string/pref_chargeprice_show_provider_customer_tariffs"
android:summary="@string/pref_chargeprice_show_provider_customer_tariffs_summary"
android:defaultValue="false"
app:singleLineTitle="false" />
</PreferenceScreen>

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceCategory android:title="@string/settings_charger_data">
<net.vonforst.evmap.ui.DataSourceSelectDialogPreference
android:key="data_source"
android:title="@string/pref_data_source"
android:entries="@array/pref_data_source_names"
android:entryValues="@array/pref_data_source_values"
android:defaultValue="goingelectric"
app:useSimpleSummaryProvider="true" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/settings_map">
<ListPreference
android:key="map_provider"
android:title="@string/pref_map_provider"
android:entries="@array/pref_map_provider_names"
android:entryValues="@array/pref_map_provider_values"
android:defaultValue="@string/pref_map_provider_default"
android:summary="%s" />
<ListPreference
android:key="search_provider"
android:title="@string/pref_search_provider"
android:entries="@array/pref_search_provider_names"
android:entryValues="@array/pref_search_provider_values"
android:defaultValue="@string/pref_search_provider_default"
android:summary="%s" />
<Preference
android:key="search_delete_recent"
android:title="@string/pref_search_delete_recent" />
</PreferenceCategory>
</PreferenceScreen>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<ListPreference
android:key="language"
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" />
<ListPreference
android:key="darkmode"
android:title="@string/pref_darkmode"
android:entries="@array/pref_darkmode_names"
android:entryValues="@array/pref_darkmode_values"
android:defaultValue="default"
android:summary="@string/pref_darkmode_summary" />
<CheckBoxPreference
android:key="navigate_use_maps"
android:title="@string/pref_navigate_use_maps"
android:summaryOn="@string/pref_navigate_use_maps_on"
android:summaryOff="@string/pref_navigate_use_maps_off"
android:defaultValue="true" />
</PreferenceScreen>

View File

@@ -1,9 +1,9 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.5.20'
ext.kotlin_version = '1.5.31'
ext.about_libs_version = '8.8.5'
ext.nav_version = '2.3.5'
ext.nav_version = '2.4.0-alpha10'
repositories {
google()
mavenCentral()

75
doc/android_auto.md Normal file
View File

@@ -0,0 +1,75 @@
Testing EVMap on Android Auto
=============================
In addition to the Android app on the phone, EVMap is also available as an Android Auto app built
using the [Android for Cars App Library](https://developer.android.com/training/cars/apps). The
Android Auto app is only available in the `google` build flavor of the app, and thus its code is
located in the `app/src/google/java` directory under the `net.vonforst.evmap.auto` package.
This page contains instructions on how to test the Android Auto app using the Desktop Head Unit
(DHU).
Further information about testing Android Auto apps is also available on the
[Android Developers site](https://developer.android.com/training/cars/testing).
Install the Desktop Head Unit
-----------------------------
Refer to the instructions on the
[Android Developers site](https://developer.android.com/training/cars/testing#install)
to install the DHU 2.0 using the SDK manager.
Install Android Auto
--------------------
If you haven't already, install the
[Android Auto](https://play.google.com/store/apps/details?id=com.google.android.projection.gearhead)
and
[Android Auto for phone screens](https://play.google.com/store/apps/details?id=com.google.android.projection.gearhead.phonescreen)
apps on your test device from the Google Play Store.
If you are using the Android Emulator, the Play Store may show the Android Auto app as incompatible.
In that case, download the APK for the newest version from a site like
[APKMirror](https://www.apkmirror.com/apk/google-inc/android-auto/)
(choosing the correct architecture for your emulator - x86_64, x86 or ARM)
and drag it onto the running emulator window to install.
Starting the DHU
----------------
(see also the corresponding section on
the [Android Developers site](https://developer.android.com/training/cars/testing#running-dhu))
1. Start the Android Auto for phone screens app, tap the menu icon on the top left to go to settings
2. Scroll all the way down to the app version, tap it 10 times
3. Click *OK* in the dialog that appears to enable developer mode
4. In the menu on the top left, tap *Start head unit server*
5. On your computer, run the following command to set up the required port forwarding:
```shell
adb forward tcp:5277 tcp:5277
```
6. Start the DHU by running the command `desktop-head-unit.exe` (on Windows) or
`./desktop-head-unit` (on macOS or Linux) in a console window from the
`SDK_LOCATION/extras/google/auto/` directory.
The desktop head unit should appear and show the Android Auto interface. If this is the first time
the Android device is connected to the DHU, you may need to open the Android Auto app again on the
phone to accept some permissions before the connection can succeed.
Testing EVMap on the DHU
------------------------
Make sure that you have selected the `googleDebug` variant in the *Build Variants* tool window in
Android Studio (the `foss` variants do not contain the Android Auto app). Then, install the app on
your phone - if the DHU is connected, the app should also automatically appear in the apps menu on
Android Auto.
For testing features that require car sensors, you need to start the DHU with the option
`-c config/default_sensors.ini` to select a configuration file that enables these sensors. From the
console, you can then type certain commands to update the data of these sensors, such as:
```shell
location 54.0 9.0 # latitude, longitude
fuel 50 # percentage
range 100 # in kilometers
speed 28 # in m/s
```

165
doc/api_keys.md Normal file
View File

@@ -0,0 +1,165 @@
API keys required for testing EVMap
===================================
EVMap uses multiple different data sources, most of which require an API key. These API keys need to
be put into the app in the form of a resource file called `apikeys.xml` under
`app/src/main/res/values`, with the following content:
<details>
<summary>apikeys.xml content</summary>
```xml
<resources>
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">
insert your Google Maps key here
</string>
<string name="mapbox_key" translatable="false">
insert your Mapbox key here
</string>
<string name="goingelectric_key" translatable="false">
insert your GoingElectric key here
</string>
<string name="chargeprice_key" translatable="false">
insert your Chargeprice key here
</string>
<string name="openchargemap_key" translatable="false">
insert your OpenChargeMap key here
</string>
</resources>
```
</details>
Not all API keys are strictly required if you only want to work on certain parts of the app. For
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.
Below you find a list of all the services and how to obtain the API keys.
Map providers
-------------
The different Map SDKs are wrapped by our [fork](https://github.com/johan12345/AnyMaps) of the
[AnyMaps](https://github.com/sharenowTech/AnyMaps) library to provide a common API. The `google`
build flavor of the app includes both Google Maps and Mapbox and allows the user to switch between
the two, while the `foss` flavor only includes the Mapbox SDK.
> ⚠️ When testing the app using the Android Emulator, we recommend using Google Maps and not Mapbox, as the latter has
[issues displaying the markers](https://github.com/mapbox/mapbox-gl-native/issues/10829). It works fine on real Android devices.
### Google Maps
[Maps SDK for Android](https://developers.google.com/maps/documentation/android-sdk/overview),
[Places API](https://developers.google.com/maps/documentation/places/android-sdk/overview)
<details>
<summary>How to obtain an API key</summary>
1. Log in to the [Google API console](https://console.developers.google.com/) with your Google
account
2. Create a new project, or select an existing one that you want to use
3. Under *APIs & Services → Library*, enable
the [Maps SDK for Android](https://console.cloud.google.com/apis/library/maps-android-backend.googleapis.com)
and [Places API](https://console.cloud.google.com/apis/library/places-backend.googleapis.com).
4. Under *APIs & Services → Credentials*, click on *Create credentials → API Key*
5. Copy the displayed key to your `apikeys.xml` file.
</details>
### Mapbox
[Maps SDK for Android](https://docs.mapbox.com/android/maps)
<details>
<summary>How to obtain an API key</summary>
1. [Sign up](https://account.mapbox.com/auth/signup) for a Mapbox account
2. Under [Access Tokens](https://account.mapbox.com/access-tokens/), create a new access token
3. Set a name for the scope and enable only the preselected public scopes. Do not restrict the token
to a specific URL (this setting is not compatible with Android apps)
</details>
Charging station databases
--------------------------
### **GoingElectric.de**
GoingElectric.de provides an [API](https://www.goingelectric.de/stromtankstellen/api/) for their
community-maintained directory of charging stations. The website and data are mostly only available
in German.
<details>
<summary>How to obtain an API key</summary>
1. [Sign up](https://www.goingelectric.de/forum/ucp.php?mode=register) for an account in the
GoingElectric.de forum. The registration page can be switched to English using the dropdown menu
under "Sprache". Then, agree to the registration terms.
2. Fill in your desired username, password and email address and submit the registration form. You
do not need to fill the information under *GoingElectric Usermap*.
3. Verify your account by clicking on the link in the email you received
4. [Log in](https://www.goingelectric.de/forum/ucp.php?mode=login) to the GoingElectric forum
5. Go to [this link](https://www.goingelectric.de/stromtankstellen/api/new/) to request access to
the API. This page is only available in German. You need to fill in the following data:
- name / company (*Name / Firma*)
- street address (*Straße, Nr.*)
- postal code, town (*Postleitzahl, Ort*)
- country (*Land*)
- email address (*E-Mail Adresse*)
- website (*Webseite*, optional)
- phone number (*Telefonnummer*, optional)
- name of the app (*Name der App*): EVMap
- app website (*Webseite der App*): https://github.com/johan12345/EVMap
- description (*kurze Beschreibung der App*): please explain that you would like to contribute to
the development of EVMap and therefore need access to the GoingElectric.de API.
- Referrer (*Herkunft*): leave this field blank!
6. When your access to the API is approved, you can access the
[API console](https://www.goingelectric.de/stromtankstellen/api/ucp/) to retrieve your API key.
</details>
### **OpenChargeMap**
[API documentation](https://openchargemap.org/site/develop/api)
<details>
<summary>How to obtain an API key</summary>
1. [Sign up](https://openchargemap.org/site/loginprovider/register) for an account at OpenChargeMap
2. Go to the [My Apps](https://openchargemap.org/site/profile/applications) page and click
*Register an application*
3. Enter the name of the app (EVMap) and website (https://github.com/johan12345/EVMap), and in the
description field describe that you would like to contribute to the development of EVMap and
therefore need access to the OpenChargeMap API. Do not tick the *List App in Public Showcase*
box. Then, click *save*.
4. Your API key will appear on the
[My Apps](https://openchargemap.org/site/profile/applications) page.
</details>
Pricing providers
-----------------
### Chargeprice.app
[API documentation](https://github.com/chargeprice/chargeprice-api-docs)
<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.
</details>

View File

@@ -1,3 +1,3 @@
Verbesserungen:
- Kleinere Verbesserungen für die Chargeprice.app-Integration
Verbesserungen:
- Kleinere Verbesserungen für die Chargeprice.app-Integration
- Verschiedene Abstürze behoben

View File

@@ -1,4 +1,4 @@
Verbesserungen für Chargeprice.app-Integration:
- Währung kann in den Einstellungen gewählt werden
- Ausgewählter Ladebereich wird gespeichert
Verbesserungen für Chargeprice.app-Integration:
- Währung kann in den Einstellungen gewählt werden
- Ausgewählter Ladebereich wird gespeichert
- Eigene Tarife können in den Einstellungen ausgewählt werden

View File

@@ -0,0 +1,11 @@
Neue Funktionen:
- Favoriten werden auf der Karte hervorgehoben
- Filtermöglichkeit um nur Favoriten auf der Karte anzuzeigen
Verbesserungen:
- Liste mit vorherigen Suchergebnissen wird schneller geöffnet
- Verbesserungen der Markerdarstellung in Google Maps-Karten
- Umstrukturierung der Einstellungen
- Android Auto: Gelbe Marker leicht verdunkelt für besseren Kontrast
- GoingElectric: Filter nach Anschlüssen besser sortiert
- Verschiedene Abstürze behoben

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 875 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 837 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 341 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 94 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -1,3 +1,3 @@
Improvements:
- Minor improvements for the Chargeprice.app integration
Improvements:
- Minor improvements for the Chargeprice.app integration
- Fixed various crashes

View File

@@ -1,4 +1,4 @@
Improvements for Chargeprice.app integration:
- Currency selection in settings
- Save selected charging range
Improvements for Chargeprice.app integration:
- Currency selection in settings
- Save selected charging range
- Own charging plans can be selected in settings

View File

@@ -0,0 +1,11 @@
New features:
- Favorites are highlighted on the map
- Filter option to show only favorites
Improvements:
- List of previous search results opens more quickly
- Improved incorrect marker display on Google Maps
- Restructured settings menu
- Android Auto: Slightly darkened yellow markers for better contrast
- GoingElectric: Improved sorting of options in connectors filter
- Fixed various crashes

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 24 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 864 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 837 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 330 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

After

Width:  |  Height:  |  Size: 95 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 B

After

Width:  |  Height:  |  Size: 122 KiB