Compare commits
9 Commits
1.9.1
...
screenshot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e1e22a44b | ||
|
|
d638a88f4d | ||
|
|
b9c08a8e75 | ||
|
|
ab2a77d25a | ||
|
|
e0ddc9f734 | ||
|
|
168fa244d3 | ||
|
|
76388ccc6e | ||
|
|
76aac7dae4 | ||
|
|
ee8b586079 |
2
.github/workflows/apikeys-ci.xml
vendored
@@ -1,8 +1,6 @@
|
||||
<resources>
|
||||
<string name="google_maps_key" templateMergeStrategy="preserve" translatable="false">ci</string>
|
||||
<string name="mapbox_key" translatable="false">ci</string>
|
||||
<string name="jawg_key" translatable="false">ci</string>
|
||||
<string name="arcgis_key" translatable="false">ci</string>
|
||||
<string name="goingelectric_key" translatable="false">ci</string>
|
||||
<string name="chargeprice_key" translatable="false">ci</string>
|
||||
<string name="openchargemap_key" translatable="false">ci</string>
|
||||
|
||||
2
.github/workflows/release.yml
vendored
@@ -30,8 +30,6 @@ jobs:
|
||||
OPENCHARGEMAP_API_KEY: ${{ secrets.OPENCHARGEMAP_API_KEY }}
|
||||
CHARGEPRICE_API_KEY: ${{ secrets.CHARGEPRICE_API_KEY }}
|
||||
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
|
||||
JAWG_API_KEY: ${{ secrets.JAWG_API_KEY }}
|
||||
ARCGIS_API_KEY: ${{ secrets.ARCGIS_API_KEY }}
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
FRONYX_API_KEY: ${{ secrets.FRONYX_API_KEY }}
|
||||
ACRA_CRASHREPORT_CREDENTIALS: ${{ secrets.ACRA_CRASHREPORT_CREDENTIALS }}
|
||||
|
||||
115
.github/workflows/screenshots.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
|
||||
name: Generate Screenshots
|
||||
|
||||
jobs:
|
||||
|
||||
screenshot:
|
||||
name: Generate screenshots
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
device:
|
||||
- profile: pixel_6
|
||||
api-level: 34
|
||||
display_size: 1080x2336 # subtracted the 64px bottom navigation bar
|
||||
type: phone
|
||||
- profile: pixel_tablet
|
||||
api-level: 34
|
||||
display_size: 2560x1488 # subtracted the 64px navigation bar and 48px status bar
|
||||
type: tenInch
|
||||
env:
|
||||
ANDROID_USER_HOME: /home/runner/.android
|
||||
steps:
|
||||
- name: Enable KVM group perms
|
||||
run: |
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Retrieve debug keystore
|
||||
env:
|
||||
DEBUG_KEYSTORE_BASE64: ${{ secrets.DEBUG_KEYSTORE_BASE64 }}
|
||||
run: |
|
||||
mkdir ~/.config/.android
|
||||
echo $DEBUG_KEYSTORE_BASE64 | base64 --decode > ~/.config/.android/debug.keystore
|
||||
|
||||
- name: Set up Java environment
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'zulu'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Setup Android SDK
|
||||
run: |
|
||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install "cmdline-tools;latest"
|
||||
rm -r $ANDROID_HOME/cmdline-tools/latest
|
||||
mv $ANDROID_HOME/cmdline-tools/latest-2 $ANDROID_HOME/cmdline-tools/latest
|
||||
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --version
|
||||
|
||||
- name: AVD cache
|
||||
uses: actions/cache@v3
|
||||
id: avd-cache
|
||||
with:
|
||||
path: |
|
||||
~/.android/avd/*
|
||||
~/.android/adb*
|
||||
key: ${{ runner.os }}-avd-api${{ matrix.device.api-level }}-${{ matrix.device.profile }}
|
||||
|
||||
- name: create AVD and generate snapshot for caching
|
||||
if: steps.avd-cache.outputs.cache-hit != 'true'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.device.api-level }}
|
||||
target: google_atd
|
||||
arch: x86_64
|
||||
profile: ${{ matrix.device.profile }}
|
||||
force-avd-creation: false
|
||||
ram-size: 2048M
|
||||
disk-size: 4096M
|
||||
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics
|
||||
disable-animations: true
|
||||
script: echo "Generated AVD snapshot for caching."
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assembleGoogleNormalDebug assembleGoogleNormalAndroidTest
|
||||
env:
|
||||
GOINGELECTRIC_API_KEY: ${{ secrets.GOINGELECTRIC_API_KEY }}
|
||||
OPENCHARGEMAP_API_KEY: ${{ secrets.OPENCHARGEMAP_API_KEY }}
|
||||
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
FRONYX_API_KEY: ${{ secrets.FRONYX_API_KEY }}
|
||||
ACRA_CRASHREPORT_CREDENTIALS: ${{ secrets.ACRA_CRASHREPORT_CREDENTIALS }}
|
||||
|
||||
- name: Run emulator and generate screenshots
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
with:
|
||||
api-level: ${{ matrix.device.api-level }}
|
||||
target: google_atd
|
||||
arch: x86_64
|
||||
profile: ${{ matrix.device.profile }}
|
||||
force-avd-creation: false
|
||||
ram-size: 2048M
|
||||
disk-size: 4096M
|
||||
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -no-metrics
|
||||
disable-animations: true
|
||||
script: |
|
||||
adb shell pm clear net.vonforst.evmap.debug || true
|
||||
adb shell wm size ${{ matrix.device.display_size }}
|
||||
adb logcat -c
|
||||
|
||||
adb logcat *:E &
|
||||
fastlane screengrab --app_apk_path app/build/outputs/apk/googleNormal/debug/app-google-normal-debug.apk --test_apk_path app/build/outputs/apk/androidTest/googleNormal/debug/app-google-normal-debug-androidTest.apk --tests_package_name=net.vonforst.evmap.debug.test --app_package_name net.vonforst.evmap.debug -p net.vonforst.evmap.screenshot --use_timestamp_suffix false --clear_previous_screenshots true --device_type=${{ matrix.device.type }} -q en-US,de-DE,fr-FR,nb-rNO,nl-NL,pt-PT,ro-RO
|
||||
|
||||
- name: Upload screenshots as artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: screenshots-${{ matrix.device.profile }}-${{ matrix.device.api-level }}
|
||||
path: fastlane/metadata/android
|
||||
2
.github/workflows/tests.yml
vendored
@@ -34,5 +34,3 @@ jobs:
|
||||
run: ./gradlew test${{ matrix.buildvariant }}DebugUnitTest --no-daemon
|
||||
- name: Run Android Lint
|
||||
run: ./gradlew lint${{ matrix.buildvariant }}Debug --no-daemon
|
||||
- name: Check licenses
|
||||
run: ./gradlew exportLibraryDefinitions --no-daemon
|
||||
|
||||
3
.gitignore
vendored
@@ -12,5 +12,4 @@ apikeys.xml
|
||||
/app/**/*.apk
|
||||
/_img/connectors/*.ai
|
||||
api-7125266970515251116-798419-8e2dda660c80.json
|
||||
output-metadata.json
|
||||
licenses_*.csv
|
||||
output-metadata.json
|
||||
22
README.md
@@ -24,8 +24,7 @@ Features
|
||||
- Android Auto & Android Automotive OS integration
|
||||
- No ads, fully open source
|
||||
- Compatible with Android 5.0 and above
|
||||
- Can use Google Maps or OpenStreetMap as map backends - the version available on F-Droid only uses
|
||||
OSM.
|
||||
- Can use Google Maps or Mapbox (OpenStreetMap) as map backends - the version available on F-Droid only uses Mapbox.
|
||||
|
||||
Screenshots
|
||||
-----------
|
||||
@@ -43,14 +42,12 @@ EVMap uses and put them into the app in the form of a resource file called `apik
|
||||
`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).
|
||||
|
||||
There are four different build flavors, `googleNormal`, `fossNormal`, `googleAutomotive`, and
|
||||
`fossAutomotive`.
|
||||
|
||||
- The `foss` variants only use OSM data and should run on most Android devices, even without
|
||||
There are three different build flavors, `googleNormal`, `fossNormal` and `googleAutomotive`.
|
||||
- The `foss` variants only use Mapbox data and should run on most Android devices, even without
|
||||
Google Play Services.
|
||||
- `fossNormal` is intended to run on smartphones and tablets, and also includes the Android
|
||||
Auto app for use on the car display (however Android Auto may not work if the app is not
|
||||
installed from Google Play, see https://github.com/ev-map/EVMap/issues/319).
|
||||
Auto app for use on the car display (however for that to work, the Android Auto app is
|
||||
necessary, which in turn does require Google Play Services).
|
||||
- `fossAutomotive` can be installed directly on
|
||||
[Android Automotive OS (AAOS)](https://source.android.com/docs/automotive/start/what_automotive)
|
||||
headunits without Google services.
|
||||
@@ -78,12 +75,5 @@ You can use our [Weblate page](https://hosted.weblate.org/projects/evmap/) to he
|
||||
into new languages.
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/evmap/">
|
||||
<img src="https://hosted.weblate.org/widgets/evmap/-/open-graph.png" width="400" alt="Translation status" />
|
||||
<img src="https://hosted.weblate.org/widgets/evmap/-/open-graph.png" width="500" alt="Translation status" />
|
||||
</a>
|
||||
|
||||
Sponsors
|
||||
--------
|
||||
|
||||
<a href="https://www.jawg.io"><img src="https://www.jawg.io/static/Blue@10x-9cdc4596e4e59acbd9ead55e9c28613e.png" alt="JawgMaps" height="58"/></a><br>
|
||||
Since mid 2024, **JawgMaps** provides their OpenStreetMap vector map tiles service to EVMap for
|
||||
free, i.e. the background map displayed in the app if OpenStreetMap is selected as the data source.
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #00e676;
|
||||
}
|
||||
|
||||
.cls-1, .cls-2, .cls-3, .cls-4, .cls-5, .cls-6, .cls-7, .cls-8 {
|
||||
stroke-width: 0px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: rgba(255, 255, 255, .2);
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: #ffb300;
|
||||
}
|
||||
|
||||
.cls-4 {
|
||||
fill: #000;
|
||||
isolation: isolate;
|
||||
opacity: .45;
|
||||
}
|
||||
|
||||
.cls-5 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.cls-6 {
|
||||
fill: #90a4ae;
|
||||
}
|
||||
|
||||
.cls-7 {
|
||||
fill: #546e7a;
|
||||
}
|
||||
|
||||
.cls-8 {
|
||||
fill: rgba(62, 39, 35, .2);
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_1" data-name="Layer 1">
|
||||
<g>
|
||||
<rect class="cls-5" width="512" height="512" />
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-3"
|
||||
d="M159.42,338.98l-6.43-56.15-9.81,1.01,6.43,56.15,9.81-1.01ZM194.26,334.92l-6.43-56.15-9.81,1.01,6.43,56.15,9.81-1.01Z" />
|
||||
<path class="cls-6"
|
||||
d="M212.53,411.37c-3.04,3.72-5.41,6.09-5.75,6.43-8.79,7.1-15.9,9.13-21.65,6.43-10.15-5.07-9.47-24.02-9.13-26.05l7.1.34c-.34,5.41.68,16.91,5.41,19.28,2.71,1.35,7.44-.34,13.53-5.41h0s19.62-19.62,15.56-35.18c-4.74-18.6,16.91-45.33,24.02-54.46l1.01-1.01,5.75,4.4-1.01,1.35c-21.99,27.06-24.35,40.93-22.66,48.03,3.38,13.53-5.75,28.08-12.18,35.85Z" />
|
||||
<path class="cls-6"
|
||||
d="M137.78,338.3l2.71,23,21.31,14.21,28.75-3.04,17.59-18.6-2.71-23-67.65,7.44Z" />
|
||||
<path class="cls-7"
|
||||
d="M190.21,372.47l-28.75,3.04,6.09,25.37,22.66-2.71v-25.71h0ZM210.84,311.58l2.37,20.97-82.53,9.47-2.37-20.97,82.53-9.47Z" />
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="cls-1"
|
||||
d="M275.45,80.22c-59.19,0-107.23,48.03-107.23,107.23,0,80.84,90.31,123.12,101.14,238.47.34,3.38,3.04,5.75,6.43,5.75s6.09-2.37,6.43-5.75c10.82-115.34,101.14-157.63,101.14-238.47-.68-59.53-48.71-107.23-107.9-107.23Z" />
|
||||
<path class="cls-2"
|
||||
d="M275.45,82.58c58.86,0,106.55,47.36,107.23,105.87v-1.01c0-59.19-48.03-107.23-107.23-107.23s-107.23,47.69-107.23,107.23v1.01c.68-58.52,48.37-105.87,107.23-105.87h0Z" />
|
||||
<path class="cls-8"
|
||||
d="M281.87,423.21c-.34,3.38-3.04,5.75-6.43,5.75s-6.09-2.37-6.43-5.75c-10.49-115.01-100.12-157.29-100.8-237.12v1.69c0,80.84,90.31,123.12,101.14,238.47.34,3.38,3.04,5.75,6.43,5.75s6.09-2.37,6.43-5.75c10.82-115.34,101.14-157.63,101.14-238.47v-1.69c-1.35,79.83-90.99,122.11-101.48,237.12h0Z" />
|
||||
</g>
|
||||
<path class="cls-4"
|
||||
d="M250.75,135.01v64.94h17.59v53.11l41.27-71.03h-23.68l23.68-47.36c.34.34-58.86.34-58.86.34Z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,23 +0,0 @@
|
||||
import subprocess
|
||||
import json
|
||||
|
||||
build_types = ["fossNormalRelease", "fossAutomotiveRelease"]
|
||||
|
||||
for build_type in build_types:
|
||||
result = subprocess.run(["gradlew.bat", f"generateLibraryDefinitions{build_type.capitalize()}"],
|
||||
capture_output=True)
|
||||
|
||||
data = json.load(
|
||||
open(f"app/build/generated/aboutLibraries/{build_type}/res/raw/aboutlibraries.json"))
|
||||
|
||||
with open(f"licenses_{build_type}.csv", "w") as f:
|
||||
f.write("component_name;license_title;license_url;public_repository;copyrights\n")
|
||||
for lib in data["libraries"]:
|
||||
license = data["licenses"][lib["licenses"][0]] if len(lib["licenses"]) > 0 else None
|
||||
license_name = license["name"] if license is not None else " "
|
||||
license_url = license["url"] if license is not None else " "
|
||||
copyrights = ", ".join([dev["name"] for dev in lib["developers"] if "name" in dev])
|
||||
if copyrights == "":
|
||||
copyrights = " "
|
||||
repo_url = lib['scm']['url'] if 'scm' in lib else ''
|
||||
f.write(f"{lib['name']};{license_name};{license_url};\"{copyrights}\";{repo_url}\n")
|
||||
@@ -12,16 +12,14 @@ plugins {
|
||||
|
||||
|
||||
android {
|
||||
useLibrary("android.car")
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "net.vonforst.evmap"
|
||||
compileSdk = 34
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
|
||||
versionCode = 220
|
||||
versionName = "1.9.1"
|
||||
versionCode = 212
|
||||
versionName = "1.8.2"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -48,21 +46,12 @@ android {
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
}
|
||||
create("releaseAutomotivePackageName") {
|
||||
// Faurecia Aptoide requires the automotive variant to use a separate package name
|
||||
initWith(getByName("release"))
|
||||
applicationIdSuffix = ".automotive"
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
isDebuggable = true
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("releaseAutomotivePackageName").setRoot("src/release")
|
||||
}
|
||||
|
||||
flavorDimensions += listOf("dependencies", "automotive")
|
||||
productFlavors {
|
||||
create("foss") {
|
||||
@@ -157,28 +146,6 @@ android {
|
||||
if (mapboxKey != null) {
|
||||
resValue("string", "mapbox_key", mapboxKey)
|
||||
}
|
||||
var jawgKey =
|
||||
System.getenv("JAWG_API_KEY") ?: project.findProperty("JAWG_API_KEY")?.toString()
|
||||
if (jawgKey == null && project.hasProperty("JAWG_API_KEY_ENCRYPTED")) {
|
||||
jawgKey = decode(
|
||||
project.findProperty("JAWG_API_KEY_ENCRYPTED").toString(),
|
||||
"FmK.d,-f*p+rD+WK!eds"
|
||||
)
|
||||
}
|
||||
if (jawgKey != null) {
|
||||
resValue("string", "jawg_key", jawgKey)
|
||||
}
|
||||
var arcgisKey =
|
||||
System.getenv("ARCGIS_API_KEY") ?: project.findProperty("ARCGIS_API_KEY")?.toString()
|
||||
if (arcgisKey == null && project.hasProperty("ARCGIS_API_KEY_ENCRYPTED")) {
|
||||
arcgisKey = decode(
|
||||
project.findProperty("ARCGIS_API_KEY_ENCRYPTED").toString(),
|
||||
"FmK.d,-f*p+rD+WK!eds"
|
||||
)
|
||||
}
|
||||
if (arcgisKey != null) {
|
||||
resValue("string", "arcgis_key", jawgKey)
|
||||
}
|
||||
var chargepriceKey =
|
||||
System.getenv("CHARGEPRICE_API_KEY") ?: project.findProperty("CHARGEPRICE_API_KEY")
|
||||
?.toString()
|
||||
@@ -229,22 +196,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents {
|
||||
beforeVariants { variantBuilder ->
|
||||
if (variantBuilder.buildType == "releaseAutomotivePackageName"
|
||||
&& !variantBuilder.productFlavors.containsAll(
|
||||
listOf(
|
||||
"automotive" to "automotive",
|
||||
"dependencies" to "foss"
|
||||
)
|
||||
)
|
||||
) {
|
||||
// releaseAutomotivePackageName type is only needed for fossAutomotive
|
||||
variantBuilder.enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
create("googleNormalImplementation") {}
|
||||
create("googleAutomotiveImplementation") {}
|
||||
@@ -252,14 +203,10 @@ configurations {
|
||||
|
||||
aboutLibraries {
|
||||
allowedLicenses = arrayOf(
|
||||
"Apache-2.0", "mit", "BSD-2-Clause", "BSD-3-Clause", "EPL-1.0",
|
||||
"Apache-2.0", "mit", "BSD-2-Clause",
|
||||
"asdkl", // Android SDK
|
||||
"Dual OpenSSL and SSLeay License", // Android NDK OpenSSL
|
||||
"Google Maps Platform Terms of Service", // Google Maps SDK
|
||||
"provided without support or warranty", // org.json
|
||||
"Unicode/ICU License", // icu4j
|
||||
"Bouncy Castle Licence", // bcprov
|
||||
"CDDL + GPLv2 with classpath exception", // javax.annotation-api
|
||||
"Google Maps Platform Terms of Service" // Google Maps SDK
|
||||
)
|
||||
strictMode = com.mikepenz.aboutlibraries.plugin.StrictMode.FAIL
|
||||
}
|
||||
@@ -314,13 +261,27 @@ dependencies {
|
||||
automotiveImplementation("androidx.car.app:app-automotive:$carAppVersion")
|
||||
|
||||
// AnyMaps
|
||||
val anyMapsVersion = "a5b9abca40"
|
||||
val anyMapsVersion = "4854581f72"
|
||||
implementation("com.github.ev-map.AnyMaps:anymaps-base:$anyMapsVersion")
|
||||
googleImplementation("com.github.ev-map.AnyMaps:anymaps-google:$anyMapsVersion")
|
||||
googleImplementation("com.google.android.gms:play-services-maps:18.2.0")
|
||||
implementation("com.github.ev-map.AnyMaps:anymaps-maplibre:$anyMapsVersion") {
|
||||
// duplicates classes from mapbox-sdk-services
|
||||
exclude("org.maplibre.gl", "android-sdk-geojson")
|
||||
implementation("com.github.ev-map.AnyMaps:anymaps-mapbox:$anyMapsVersion") {
|
||||
exclude(group = "com.mapbox.mapboxsdk", module = "mapbox-android-accounts")
|
||||
exclude(group = "com.mapbox.mapboxsdk", module = "mapbox-android-telemetry")
|
||||
exclude(group = "com.google.android.gms", module = "play-services-location")
|
||||
exclude(group = "com.mapbox.mapboxsdk", module = "mapbox-android-core")
|
||||
}
|
||||
// original version of mapbox-android-core
|
||||
googleImplementation("com.mapbox.mapboxsdk:mapbox-android-core:2.0.1")
|
||||
// patched version that removes build-time dependency on GMS (-> no Google location services)
|
||||
fossImplementation("com.github.ev-map:mapbox-events-android:a21c324501")
|
||||
|
||||
implementation("com.mapbox.mapboxsdk:mapbox-android-sdk") {
|
||||
exclude(group = "com.mapbox.mapboxsdk", module = "mapbox-android-accounts")
|
||||
exclude(group = "com.mapbox.mapboxsdk", module = "mapbox-android-telemetry")
|
||||
version {
|
||||
strictly("9.1.0-SNAPSHOT")
|
||||
}
|
||||
}
|
||||
|
||||
// Google Places
|
||||
@@ -364,6 +325,7 @@ dependencies {
|
||||
debugImplementation("com.facebook.flipper:flipper:0.238.0")
|
||||
debugImplementation("com.facebook.soloader:soloader:0.10.5")
|
||||
debugImplementation("com.facebook.flipper:flipper-network-plugin:0.238.0")
|
||||
debugImplementation("androidx.test.espresso:espresso-idling-resource:3.5.1")
|
||||
|
||||
// testing
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
@@ -374,11 +336,14 @@ dependencies {
|
||||
testImplementation("androidx.test:core:1.5.0")
|
||||
testImplementation("androidx.arch.core:core-testing:2.2.0")
|
||||
testImplementation("androidx.car.app:app-testing:$carAppVersion")
|
||||
testImplementation("androidx.test:core:1.5.0")
|
||||
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-contrib:3.5.1")
|
||||
androidTestImplementation("androidx.test:rules:1.5.0")
|
||||
androidTestImplementation("androidx.arch.core:core-testing:2.2.0")
|
||||
androidTestImplementation("tools.fastlane:screengrab:2.1.1")
|
||||
|
||||
kapt("com.squareup.moshi:moshi-kotlin-codegen:1.15.0")
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.vonforst.evmap.screenshot
|
||||
|
||||
import android.view.View
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.databinding.ViewDataBinding
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.IdlingResource
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* An espresso idling resource implementation that reports idle status for all data binding
|
||||
* layouts. Data Binding uses a mechanism to post messages which Espresso doesn't track yet.
|
||||
*
|
||||
* Since this application only uses fragments, the resource only checks the fragments and their
|
||||
* children instead of the whole view tree.
|
||||
*
|
||||
* Tracking bug: https://github.com/android/android-test/issues/317
|
||||
*/
|
||||
class DataBindingIdlingResource(
|
||||
activityScenarioRule: ActivityScenarioRule<out FragmentActivity>
|
||||
) : IdlingResource {
|
||||
// list of registered callbacks
|
||||
private val idlingCallbacks = mutableListOf<IdlingResource.ResourceCallback>()
|
||||
|
||||
// give it a unique id to workaround an espresso bug where you cannot register/unregister
|
||||
// an idling resource w/ the same name.
|
||||
private val id = UUID.randomUUID().toString()
|
||||
|
||||
// holds whether isIdle is called and the result was false. We track this to avoid calling
|
||||
// onTransitionToIdle callbacks if Espresso never thought we were idle in the first place.
|
||||
private var wasNotIdle = false
|
||||
|
||||
lateinit var activity: FragmentActivity
|
||||
|
||||
override fun getName() = "DataBinding $id"
|
||||
|
||||
init {
|
||||
monitorActivity(activityScenarioRule.scenario)
|
||||
}
|
||||
|
||||
override fun isIdleNow(): Boolean {
|
||||
val idle = !getBindings().any { it.hasPendingBindings() }
|
||||
@Suppress("LiftReturnOrAssignment")
|
||||
if (idle) {
|
||||
if (wasNotIdle) {
|
||||
// notify observers to avoid espresso race detector
|
||||
idlingCallbacks.forEach { it.onTransitionToIdle() }
|
||||
}
|
||||
wasNotIdle = false
|
||||
} else {
|
||||
wasNotIdle = true
|
||||
// check next frame
|
||||
activity.findViewById<View>(android.R.id.content).postDelayed({
|
||||
isIdleNow
|
||||
}, 16)
|
||||
}
|
||||
return idle
|
||||
}
|
||||
|
||||
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
|
||||
idlingCallbacks.add(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the activity from an [ActivityScenario] to be used from [DataBindingIdlingResource].
|
||||
*/
|
||||
private fun monitorActivity(
|
||||
activityScenario: ActivityScenario<out FragmentActivity>
|
||||
) {
|
||||
activityScenario.onActivity {
|
||||
this.activity = it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all binding classes in all currently available fragments.
|
||||
*/
|
||||
private fun getBindings(): List<ViewDataBinding> {
|
||||
val fragments = (activity as? FragmentActivity)
|
||||
?.supportFragmentManager
|
||||
?.fragments
|
||||
|
||||
val bindings =
|
||||
fragments?.mapNotNull {
|
||||
it.view?.getBinding()
|
||||
} ?: emptyList()
|
||||
val childrenBindings = fragments?.flatMap { it.childFragmentManager.fragments }
|
||||
?.mapNotNull { it.view?.getBinding() } ?: emptyList()
|
||||
|
||||
return bindings + childrenBindings
|
||||
}
|
||||
}
|
||||
|
||||
private fun View.getBinding(): ViewDataBinding? = DataBindingUtil.getBinding(this)
|
||||
@@ -0,0 +1,155 @@
|
||||
package net.vonforst.evmap.screenshot
|
||||
|
||||
import android.Manifest.permission.ACCESS_FINE_LOCATION
|
||||
import android.content.Intent
|
||||
import androidx.test.espresso.Espresso.onView
|
||||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.action.ViewActions.click
|
||||
import androidx.test.espresso.action.ViewActions.pressBack
|
||||
import androidx.test.espresso.contrib.DrawerActions
|
||||
import androidx.test.espresso.contrib.NavigationViewActions
|
||||
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||
import androidx.test.ext.junit.rules.ActivityScenarioRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import androidx.test.rule.GrantPermissionRule.grant
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import net.vonforst.evmap.EXTRA_CHARGER_ID
|
||||
import net.vonforst.evmap.EXTRA_LAT
|
||||
import net.vonforst.evmap.EXTRA_LON
|
||||
import net.vonforst.evmap.EspressoIdlingResource
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.goingelectric.GEReferenceData
|
||||
import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
|
||||
import net.vonforst.evmap.model.Favorite
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import org.junit.Before
|
||||
import org.junit.BeforeClass
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import tools.fastlane.screengrab.Screengrab
|
||||
import tools.fastlane.screengrab.locale.LocaleTestRule
|
||||
import java.time.Instant
|
||||
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ScreenshotTest {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@BeforeClass
|
||||
fun beforeAll() {
|
||||
IdlingRegistry.getInstance().register(EspressoIdlingResource.countingIdlingResource)
|
||||
|
||||
Screengrab.setDefaultScreenshotStrategy { screenshotName, screenshotCallback ->
|
||||
screenshotCallback.screenshotCaptured(
|
||||
screenshotName,
|
||||
androidx.test.core.app.takeScreenshot()
|
||||
)
|
||||
}
|
||||
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val prefs = PreferenceDataSource(context)
|
||||
prefs.dataSourceSet = true
|
||||
prefs.welcomeDialogShown = true
|
||||
prefs.privacyAccepted = true
|
||||
prefs.opensourceDonationsDialogLastShown = Instant.now()
|
||||
prefs.chargepriceMyVehicles = setOf("b58bc94d-d929-ad71-d95b-08b877bf76ba")
|
||||
prefs.appStartCounter = 0
|
||||
prefs.mapProvider = "google"
|
||||
|
||||
// insert favorites
|
||||
val db = AppDatabase.getInstance(context)
|
||||
val api = GoingElectricApiWrapper(
|
||||
context.getString(R.string.goingelectric_key),
|
||||
context = context
|
||||
)
|
||||
val ids = listOf(70774L to true, 40315L to true, 65330L to true, 62489L to false)
|
||||
runBlocking {
|
||||
val refData = api.getReferenceData().data as GEReferenceData
|
||||
ids.forEachIndexed { i, (id, favorite) ->
|
||||
val detail = api.getChargepointDetail(refData, id).data!!
|
||||
db.chargeLocationsDao().insert(detail)
|
||||
if (db.favoritesDao().findFavorite(id, "goingelectric") == null && favorite) {
|
||||
db.favoritesDao().insert(
|
||||
Favorite(
|
||||
chargerId = id,
|
||||
chargerDataSource = "goingelectric"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@get:Rule
|
||||
val localeTestRule = LocaleTestRule()
|
||||
|
||||
@get:Rule
|
||||
val activityRule: ActivityScenarioRule<MapsActivity> = ActivityScenarioRule(
|
||||
Intent(
|
||||
InstrumentationRegistry.getInstrumentation().targetContext,
|
||||
MapsActivity::class.java
|
||||
).apply {
|
||||
putExtra(EXTRA_CHARGER_ID, 62489L)
|
||||
putExtra(EXTRA_LAT, 53.099512)
|
||||
putExtra(EXTRA_LON, 9.981884)
|
||||
})
|
||||
|
||||
@get:Rule
|
||||
val permissionRule: GrantPermissionRule = grant(ACCESS_FINE_LOCATION)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
IdlingRegistry.getInstance().register(DataBindingIdlingResource(activityRule))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testTakeScreenshot() {
|
||||
Thread.sleep(15000L)
|
||||
Screengrab.screenshot("01_map_google")
|
||||
|
||||
onView(withId(R.id.topPart)).perform(click())
|
||||
|
||||
Thread.sleep(1000L)
|
||||
Screengrab.screenshot("02_detail")
|
||||
|
||||
onView(withId(R.id.btnChargeprice)).perform(click())
|
||||
Thread.sleep(5000L)
|
||||
Screengrab.screenshot("03_prices")
|
||||
|
||||
onView(isRoot()).perform(pressBack())
|
||||
Thread.sleep(500L)
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
Thread.sleep(2000L)
|
||||
|
||||
onView(withId(R.id.menu_filter)).perform(click())
|
||||
Thread.sleep(1000L)
|
||||
onView(withText(R.string.menu_edit_filters)).perform(click())
|
||||
|
||||
Thread.sleep(1000L)
|
||||
|
||||
Screengrab.screenshot("05_filters")
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
onView(withId(R.id.drawer_layout)).perform(DrawerActions.open())
|
||||
onView(withId(R.id.nav_view)).perform(NavigationViewActions.navigateTo(R.id.favs))
|
||||
|
||||
Thread.sleep(10000L)
|
||||
Screengrab.screenshot("04_favorites")
|
||||
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
PreferenceDataSource(context).mapProvider = "mapbox"
|
||||
onView(isRoot()).perform(pressBack())
|
||||
|
||||
Thread.sleep(5000L)
|
||||
Screengrab.screenshot("01_map_mapbox")
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.johan.evmap.storage
|
||||
package net.vonforst.evmap.storage
|
||||
|
||||
import android.content.Context
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
@@ -1,171 +0,0 @@
|
||||
import android.car.Car
|
||||
import android.car.VehiclePropertyIds
|
||||
import android.car.VehicleUnit
|
||||
import android.car.hardware.CarPropertyValue
|
||||
import android.car.hardware.property.CarPropertyManager
|
||||
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.annotations.ExperimentalCarApi
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.common.CarUnit
|
||||
import androidx.car.app.hardware.common.CarValue
|
||||
import androidx.car.app.hardware.common.OnCarDataAvailableListener
|
||||
import androidx.car.app.hardware.info.CarInfo
|
||||
import androidx.car.app.hardware.info.EnergyLevel
|
||||
import androidx.car.app.hardware.info.EnergyProfile
|
||||
import androidx.car.app.hardware.info.EvStatus
|
||||
import androidx.car.app.hardware.info.Mileage
|
||||
import androidx.car.app.hardware.info.Model
|
||||
import androidx.car.app.hardware.info.Speed
|
||||
import androidx.car.app.hardware.info.TollCard
|
||||
import java.util.concurrent.Executor
|
||||
|
||||
|
||||
val CarContext.patchedCarInfo: CarInfo
|
||||
get() = CarInfoWrapper(this)
|
||||
|
||||
class CarInfoWrapper(ctx: CarContext) : CarInfo {
|
||||
private val wrapped =
|
||||
(ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager).carInfo
|
||||
private val carPropertyManager = try {
|
||||
val car = Car.createCar(ctx)
|
||||
car.getCarManager(Car.PROPERTY_SERVICE) as CarPropertyManager
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
null
|
||||
}
|
||||
private val callbacks = mutableMapOf<OnCarDataAvailableListener<*>, CarPropertyEventCallback>()
|
||||
|
||||
override fun fetchModel(executor: Executor, listener: OnCarDataAvailableListener<Model>) =
|
||||
wrapped.fetchModel(executor, listener)
|
||||
|
||||
override fun fetchEnergyProfile(
|
||||
executor: Executor,
|
||||
listener: OnCarDataAvailableListener<EnergyProfile>
|
||||
) = wrapped.fetchEnergyProfile(executor, listener)
|
||||
|
||||
override fun addTollListener(
|
||||
executor: Executor,
|
||||
listener: OnCarDataAvailableListener<TollCard>
|
||||
) = wrapped.addTollListener(executor, listener)
|
||||
|
||||
override fun removeTollListener(listener: OnCarDataAvailableListener<TollCard>) =
|
||||
wrapped.removeTollListener(listener)
|
||||
|
||||
override fun addEnergyLevelListener(
|
||||
executor: Executor,
|
||||
listener: OnCarDataAvailableListener<EnergyLevel>
|
||||
) = wrapped.addEnergyLevelListener(executor, listener)
|
||||
|
||||
override fun removeEnergyLevelListener(listener: OnCarDataAvailableListener<EnergyLevel>) =
|
||||
wrapped.removeEnergyLevelListener(listener)
|
||||
|
||||
override fun addSpeedListener(executor: Executor, listener: OnCarDataAvailableListener<Speed>) {
|
||||
// TODO: This is a emporary workaround until Car App Library 1.7.0 is released - previous versions would crash if the car reported an invalid speed display unit
|
||||
carPropertyManager ?: return
|
||||
val callback = object : CarPropertyEventCallback {
|
||||
private var speedRaw: CarPropertyValue<Float>? = null
|
||||
private var speedDisplay: CarPropertyValue<Float>? = null
|
||||
private var speedUnit: CarPropertyValue<Int>? = null
|
||||
|
||||
override fun onChangeEvent(value: CarPropertyValue<*>?) {
|
||||
when (value?.propertyId) {
|
||||
VehiclePropertyIds.PERF_VEHICLE_SPEED -> speedRaw =
|
||||
value as CarPropertyValue<Float>?
|
||||
|
||||
VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY -> speedDisplay =
|
||||
value as CarPropertyValue<Float>?
|
||||
|
||||
VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS -> speedUnit =
|
||||
value as CarPropertyValue<Int>?
|
||||
}
|
||||
|
||||
executor.execute {
|
||||
listener.onCarDataAvailable(Speed.Builder().apply {
|
||||
speedRaw?.let {
|
||||
setRawSpeedMetersPerSecond(
|
||||
CarValue(
|
||||
it.value,
|
||||
it.timestamp,
|
||||
if (it.value != null) CarValue.STATUS_SUCCESS else CarValue.STATUS_UNKNOWN
|
||||
)
|
||||
)
|
||||
}
|
||||
speedDisplay?.let {
|
||||
setDisplaySpeedMetersPerSecond(
|
||||
CarValue(
|
||||
it.value,
|
||||
it.timestamp,
|
||||
if (it.value != null) CarValue.STATUS_SUCCESS else CarValue.STATUS_UNKNOWN
|
||||
)
|
||||
)
|
||||
}
|
||||
speedUnit?.let {
|
||||
val unit = when (it.value) {
|
||||
VehicleUnit.METER_PER_SEC -> CarUnit.METERS_PER_SEC
|
||||
VehicleUnit.MILES_PER_HOUR -> CarUnit.MILES_PER_HOUR
|
||||
VehicleUnit.KILOMETERS_PER_HOUR -> CarUnit.KILOMETERS_PER_HOUR
|
||||
else -> null
|
||||
}
|
||||
setSpeedDisplayUnit(
|
||||
CarValue(
|
||||
unit,
|
||||
it.timestamp,
|
||||
if (unit != null) CarValue.STATUS_SUCCESS else CarValue.STATUS_UNKNOWN
|
||||
)
|
||||
)
|
||||
}
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onErrorEvent(propertyId: Int, areaId: Int) {
|
||||
listener.onCarDataAvailable(
|
||||
Speed.Builder()
|
||||
.setRawSpeedMetersPerSecond(CarValue(null, 0, CarValue.STATUS_UNKNOWN))
|
||||
.setDisplaySpeedMetersPerSecond(CarValue(null, 0, CarValue.STATUS_UNKNOWN))
|
||||
.setSpeedDisplayUnit(CarValue(null, 0, CarValue.STATUS_UNKNOWN))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
carPropertyManager.registerCallback(
|
||||
callback,
|
||||
VehiclePropertyIds.PERF_VEHICLE_SPEED,
|
||||
CarPropertyManager.SENSOR_RATE_NORMAL
|
||||
)
|
||||
carPropertyManager.registerCallback(
|
||||
callback,
|
||||
VehiclePropertyIds.PERF_VEHICLE_SPEED_DISPLAY,
|
||||
CarPropertyManager.SENSOR_RATE_NORMAL
|
||||
)
|
||||
carPropertyManager.registerCallback(
|
||||
callback,
|
||||
VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
|
||||
CarPropertyManager.SENSOR_RATE_NORMAL
|
||||
)
|
||||
}
|
||||
|
||||
override fun removeSpeedListener(listener: OnCarDataAvailableListener<Speed>) {
|
||||
val callback = callbacks[listener]
|
||||
carPropertyManager?.unregisterCallback(callback)
|
||||
}
|
||||
|
||||
override fun addMileageListener(
|
||||
executor: Executor,
|
||||
listener: OnCarDataAvailableListener<Mileage>
|
||||
) = wrapped.addMileageListener(executor, listener)
|
||||
|
||||
override fun removeMileageListener(listener: OnCarDataAvailableListener<Mileage>) =
|
||||
wrapped.removeMileageListener(listener)
|
||||
|
||||
@OptIn(ExperimentalCarApi::class)
|
||||
override fun addEvStatusListener(
|
||||
executor: Executor,
|
||||
listener: OnCarDataAvailableListener<EvStatus>
|
||||
) = wrapped.addEvStatusListener(executor, listener)
|
||||
|
||||
@OptIn(ExperimentalCarApi::class)
|
||||
override fun removeEvStatusListener(listener: OnCarDataAvailableListener<EvStatus>) =
|
||||
wrapped.removeEvStatusListener(listener)
|
||||
}
|
||||
@@ -1,5 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Permissions needed for fastlane screengrab -->
|
||||
|
||||
<!-- Allows unlocking your device and activating its screen so UI tests can succeed -->
|
||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<!-- Allows for storing and retrieving screenshots -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<!-- Allows changing locales -->
|
||||
<uses-permission
|
||||
android:name="android.permission.CHANGE_CONFIGURATION"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<!-- Allows changing SystemUI demo mode -->
|
||||
<uses-permission
|
||||
android:name="android.permission.DUMP"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<application>
|
||||
<activity
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.vonforst.evmap
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.test.espresso.IdlingRegistry
|
||||
import androidx.test.espresso.idling.CountingIdlingResource
|
||||
import com.facebook.flipper.android.AndroidFlipperClient
|
||||
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin
|
||||
import com.facebook.flipper.plugins.inspector.DescriptorMapping
|
||||
@@ -34,9 +36,29 @@ fun OkHttpClient.Builder.addDebugInterceptors(): OkHttpClient.Builder {
|
||||
} catch (e: ClassNotFoundException) {
|
||||
isRunningTest = false
|
||||
}
|
||||
|
||||
|
||||
if (!isRunningTest) {
|
||||
this.addNetworkInterceptor(FlipperOkhttpInterceptor(networkFlipperPlugin))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains a static reference to [IdlingResource], only available in the 'debug' build type.
|
||||
*/
|
||||
object EspressoIdlingResource {
|
||||
private const val RESOURCE = "GLOBAL"
|
||||
|
||||
@JvmField
|
||||
val countingIdlingResource = CountingIdlingResource(RESOURCE)
|
||||
|
||||
fun increment() {
|
||||
countingIdlingResource.increment()
|
||||
}
|
||||
|
||||
fun decrement() {
|
||||
if (!countingIdlingResource.isIdleNow) {
|
||||
countingIdlingResource.decrement()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Pomohla vám EVMap? Podpořte její vývoj zasláním finančního daru vývojáři.</string>
|
||||
<string name="donate_paypal">Přispět pomocí PayPalu</string>
|
||||
<string name="data_sources_hint">Mapová data v aplikaci poskytuje služba OpenStreetMap.</string>
|
||||
<string name="data_sources_hint">Mapová data v aplikaci poskytuje služba OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources>
|
||||
<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.</string>
|
||||
<string name="data_sources_hint">Die Kartendaten für die App stammen von OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -1,6 +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.</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>
|
||||
@@ -1,6 +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.</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>
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Vond je EVMap nuttig\? Je kan de ontwikkeling ondersteunen door een donatie te sturen naar de ontwikkelaar.</string>
|
||||
<string name="donate_paypal">Doneer via PayPal</string>
|
||||
<string name="data_sources_hint">De kaartgegevens zijn afkomstig van OpenStreetMap.</string>
|
||||
<string name="data_sources_hint">De kaartgegevens zijn afkomstig van OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="data_sources_hint">Os dados do mapa são fornecidos pelo OpenStreetMap.</string>
|
||||
<string name="data_sources_hint">Os dados do mapa são fornecidos pelo OpenStreetMap (Mapbox).</string>
|
||||
<string name="donate_paypal">Doar com o PayPal</string>
|
||||
<string name="donations_info" formatted="false">Acha que o EVMap é útil\? Apoie a manutenção e desenvolvimento com uma doação para o desenvolvedor da app.</string>
|
||||
</resources>
|
||||
@@ -2,5 +2,5 @@
|
||||
<resources>
|
||||
<string name="donations_info" formatted="false">Crezi ca EVMap este util? Sprijina dezvoltarea printr-o donatie pentru dezvoltator.</string>
|
||||
<string name="donate_paypal">Doneaza cu PayPal</string>
|
||||
<string name="data_sources_hint">Hartile din aplicatie sunt furnizate de OpenStreetMap.</string>
|
||||
<string name="data_sources_hint">Hartile din aplicatie sunt furnizate de OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
<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.</string>
|
||||
<string name="data_sources_hint">Map data in the app is provided by OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -3,5 +3,5 @@
|
||||
<string name="donations_info" formatted="false">Pomohla vám EVMap? Podpořte její vývoj posláním finančního daru vývojáři.
|
||||
\n
|
||||
\nGoogle si z každého daru strhne 15 %.</string>
|
||||
<string name="data_sources_hint">V nastavení můžete také pro mapová data přepínat mezi službami Mapy Google a OpenStreetMap.</string>
|
||||
<string name="data_sources_hint">V nastavení můžete také pro mapová data přepínat mezi službami Mapy Google a OpenStreetMap (Mapbox).</string>
|
||||
</resources>
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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="data_sources_hint">In den Einstellungen kannst du auch zwischen Google Maps und OpenStreetMap für die Kartendaten wechseln.</string>
|
||||
<string name="data_sources_hint">In den Einstellungen kannst du auch zwischen Google Maps und OpenStreetMap (Mapbox) für die Kartendaten wechseln.</string>
|
||||
</resources>
|
||||
@@ -3,5 +3,5 @@
|
||||
<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="data_sources_hint">Dans les paramètres, vous pouvez également choisir entre Google Maps et OpenStreetMap pour les données cartographiques.</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>
|
||||
</resources>
|
||||
@@ -3,5 +3,5 @@
|
||||
<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="data_sources_hint">I innstillingene kan du også bytte mellom Google Maps og OpenStreetMap for kartdata.</string>
|
||||
<string name="data_sources_hint">I innstillingene kan du også bytte mellom Google Maps og OpenStreetMap (Mapbox) for kartdata.</string>
|
||||
</resources>
|
||||
@@ -3,5 +3,5 @@
|
||||
<string name="donations_info" formatted="false">Vind je EVMap nuttig\? Je kan de ontwikkeling steunen via een donatie aan de ontwikkelaar.
|
||||
\n
|
||||
\nGoogle houdt 15% in van elke donatie.</string>
|
||||
<string name="data_sources_hint">In de instellingen kan je ook wisselen tussen Google Maps en OpenStreetMap voor de kaartgegevens.</string>
|
||||
<string name="data_sources_hint">In de instellingen kan je ook wisselen tussen Google Maps en OpenStreetMap (Mapbox) voor de kaartgegevens.</string>
|
||||
</resources>
|
||||
@@ -3,5 +3,5 @@
|
||||
<string name="donations_info" formatted="false">Acha que o EVMap é útil\? Apoie a manutenção e desenvolvimento com uma doação para o desenvolvedor da app.
|
||||
\n
|
||||
\nA Google cobra 15% de cada doação.</string>
|
||||
<string name="data_sources_hint">Também pode mudar entre o Google Maps e OpenStreetMap nas definições da app.</string>
|
||||
<string name="data_sources_hint">Também pode mudar entre o Google Maps e OpenStreetMap (Mapbox) nas definições da app.</string>
|
||||
</resources>
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
<string-array name="pref_map_provider_names">
|
||||
<item>@string/pref_provider_google_maps</item>
|
||||
<item>@string/pref_provider_osm</item>
|
||||
<item>@string/pref_provider_osm_mapbox</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_provider_values" translatable="false">
|
||||
<item>google</item>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?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.\n\nGoogle takes 15% off every donation.</string>
|
||||
<string name="data_sources_hint">In the settings you can also switch between Google Maps and OpenStreetMap for the map data.</string>
|
||||
<string name="data_sources_hint">In the settings you can also switch between Google Maps and OpenStreetMap (Mapbox) for the map data.</string>
|
||||
</resources>
|
||||
@@ -47,14 +47,6 @@
|
||||
android:name="com.mapbox.ACCESS_TOKEN"
|
||||
android:value="@string/mapbox_key" />
|
||||
|
||||
<meta-data
|
||||
android:name="io.jawg.ACCESS_TOKEN"
|
||||
android:value="@string/jawg_key" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.arcgis.ACCESS_TOKEN"
|
||||
android:value="@string/arcgis_key" />
|
||||
|
||||
<activity
|
||||
android:name=".MapsActivity"
|
||||
android:label="@string/app_name"
|
||||
@@ -347,8 +339,9 @@
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="location">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.car.app.CarAppService" />
|
||||
<category android:name="androidx.car.app.category.POI" />
|
||||
<action
|
||||
android:name="androidx.car.app.CarAppService"
|
||||
android:category="androidx.car.app.category.POI" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.app.ActivityOptions
|
||||
import android.app.PendingIntent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
@@ -206,18 +207,15 @@ class MapsActivity : AppCompatActivity(),
|
||||
}
|
||||
}
|
||||
} else if (intent.hasExtra(EXTRA_CHARGER_ID)) {
|
||||
deepLink = navController.createDeepLink()
|
||||
.setDestination(R.id.map)
|
||||
.setArguments(
|
||||
MapFragmentArgs(
|
||||
chargerId = intent.getLongExtra(EXTRA_CHARGER_ID, 0),
|
||||
latLng = LatLng(
|
||||
intent.getDoubleExtra(EXTRA_LAT, 0.0),
|
||||
intent.getDoubleExtra(EXTRA_LON, 0.0)
|
||||
)
|
||||
).toBundle()
|
||||
)
|
||||
.createPendingIntent()
|
||||
navController.navigate(
|
||||
R.id.map, MapFragmentArgs(
|
||||
chargerId = intent.getLongExtra(EXTRA_CHARGER_ID, 0),
|
||||
latLng = LatLng(
|
||||
intent.getDoubleExtra(EXTRA_LAT, 0.0),
|
||||
intent.getDoubleExtra(EXTRA_LON, 0.0)
|
||||
)
|
||||
).toBundle()
|
||||
)
|
||||
} else if (intent.hasExtra(EXTRA_FAVORITES)) {
|
||||
deepLink = navController.createDeepLink()
|
||||
.setGraph(navGraph)
|
||||
|
||||
@@ -14,11 +14,7 @@ import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.hardware.common.CarUnit
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.Distance
|
||||
import androidx.car.app.model.MessageTemplate
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.model.*
|
||||
import androidx.car.app.versioning.CarAppApiLevels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
@@ -30,7 +26,7 @@ import net.vonforst.evmap.getPackageInfoCompat
|
||||
import net.vonforst.evmap.kmPerMile
|
||||
import net.vonforst.evmap.shouldUseImperialUnits
|
||||
import net.vonforst.evmap.ydPerMile
|
||||
import java.util.Locale
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun carAvailabilityColor(status: List<ChargepointStatus>): CarColor {
|
||||
@@ -211,9 +207,7 @@ fun supportsCarApiLevel3(ctx: CarContext): Boolean {
|
||||
val version = getAndroidAutoVersion(ctx)
|
||||
// Android Auto 6.7 is required. 6.6 reports supporting API Level 3,
|
||||
// but crashes when using it. See: https://issuetracker.google.com/issues/199509584
|
||||
val major = version[0].toIntOrNull() ?: return false
|
||||
val minor = version[1].toIntOrNull() ?: return false
|
||||
if (major < 6 || major < 6 && minor < 7) {
|
||||
if (version[0] < "6" || version[0] == "6" && version[1] < "7") {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,9 @@ import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.hardware.info.CarSensors
|
||||
import androidx.car.app.hardware.info.Compass
|
||||
import androidx.car.app.hardware.info.EnergyLevel
|
||||
import androidx.car.app.hardware.info.Model
|
||||
import androidx.car.app.hardware.info.Speed
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.GridItem
|
||||
import androidx.car.app.model.GridTemplate
|
||||
import androidx.car.app.model.ItemList
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.*
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
@@ -27,14 +18,14 @@ import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.ui.CompassNeedle
|
||||
import net.vonforst.evmap.ui.Gauge
|
||||
import net.vonforst.evmap.utils.formatDecimal
|
||||
import patchedCarInfo
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@androidx.car.app.annotations.ExperimentalCarApi
|
||||
class VehicleDataScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx),
|
||||
LocationAwareScreen, DefaultLifecycleObserver {
|
||||
private val carInfo = carContext.patchedCarInfo
|
||||
private val carInfo =
|
||||
(ctx.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager).carInfo
|
||||
private val carSensors = carContext.patchedCarSensors
|
||||
private var model: Model? = null
|
||||
private var energyLevel: EnergyLevel? = null
|
||||
|
||||
@@ -113,7 +113,8 @@ class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
|
||||
|
||||
override fun getAttributionString(): Int = R.string.powered_by_mapbox
|
||||
|
||||
override fun getAttributionImage(dark: Boolean): Int = R.drawable.mapbox_logo
|
||||
override fun getAttributionImage(dark: Boolean): Int =
|
||||
if (dark) com.mapbox.mapboxsdk.R.drawable.mapbox_logo_icon else R.drawable.mapbox_logo
|
||||
}
|
||||
|
||||
private fun BoundingBox.toLatLngBounds(): LatLngBounds {
|
||||
|
||||
@@ -225,12 +225,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
mapFragment = MapFragment()
|
||||
mapFragment!!.priority = arrayOf(
|
||||
when (provider) {
|
||||
"mapbox" -> MapFragment.MAPLIBRE
|
||||
"mapbox" -> MapFragment.MAPBOX
|
||||
"google" -> MapFragment.GOOGLE
|
||||
else -> null
|
||||
},
|
||||
MapFragment.GOOGLE,
|
||||
MapFragment.MAPLIBRE
|
||||
MapFragment.MAPBOX
|
||||
)
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
@@ -275,7 +275,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
// set map padding so that compass is not obstructed by toolbar
|
||||
mapTopPadding = systemWindowInsetTop + (48 * density).toInt() + (16 * density).toInt()
|
||||
// if we actually use map.setPadding here, MapLibre will re-trigger onApplyWindowInsets
|
||||
// if we actually use map.setPadding here, Mapbox will re-trigger onApplyWindowInsets
|
||||
// and cause an infinite loop. So we rely on onMapReady being called later than
|
||||
// onApplyWindowInsets.
|
||||
|
||||
@@ -1052,9 +1052,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val context = this.context ?: return
|
||||
chargerIconGenerator = ChargerIconGenerator(context, map.bitmapDescriptorFactory)
|
||||
|
||||
vm.mapTrafficSupported.value =
|
||||
mapFragment?.let { AnyMap.Feature.TRAFFIC_LAYER in it.supportedFeatures } ?: false
|
||||
|
||||
if (BuildConfig.FLAVOR.contains("google") && mapFragment!!.priority[0] == MapFragment.GOOGLE) {
|
||||
// Google Maps: icons can be generated in background thread
|
||||
lifecycleScope.launch {
|
||||
@@ -1063,7 +1060,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// MapLibre: needs to be run on main thread
|
||||
// Mapbox: needs to be run on main thread
|
||||
chargerIconGenerator.preloadCache()
|
||||
}
|
||||
|
||||
|
||||
@@ -108,11 +108,14 @@ class PreferenceDataSource(val context: Context) {
|
||||
val darkmode: String
|
||||
get() = sp.getString("darkmode", "default")!!
|
||||
|
||||
val mapProvider: String
|
||||
var mapProvider: String
|
||||
get() = sp.getString(
|
||||
"map_provider",
|
||||
context.getString(R.string.pref_map_provider_default)
|
||||
)!!
|
||||
set(value) {
|
||||
sp.edit().putString("map_provider", value).apply()
|
||||
}
|
||||
|
||||
var searchProvider: String
|
||||
get() = sp.getString(
|
||||
|
||||
@@ -8,6 +8,7 @@ import jsonapi.Relationships
|
||||
import jsonapi.ResourceIdentifier
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.EspressoIdlingResource
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
@@ -48,6 +49,7 @@ class ChargepriceViewModel(
|
||||
} else {
|
||||
value = Resource.loading(null)
|
||||
viewModelScope.launch {
|
||||
EspressoIdlingResource.increment()
|
||||
value = try {
|
||||
val result = api.getVehicles()
|
||||
Resource.success(result.filter {
|
||||
@@ -57,6 +59,8 @@ class ChargepriceViewModel(
|
||||
Resource.error(e.message, null)
|
||||
} catch (e: HttpException) {
|
||||
Resource.error(e.message, null)
|
||||
} finally {
|
||||
EspressoIdlingResource.decrement()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,6 +257,7 @@ class ChargepriceViewModel(
|
||||
|
||||
loadPricesJob?.cancel()
|
||||
loadPricesJob = viewModelScope.launch {
|
||||
EspressoIdlingResource.increment()
|
||||
try {
|
||||
val result = api.getChargePrices(
|
||||
ChargepriceRequest(
|
||||
@@ -295,6 +300,8 @@ class ChargepriceViewModel(
|
||||
} catch (e: HttpException) {
|
||||
chargePrices.value = Resource.error(e.message, null)
|
||||
chargePriceMeta.value = Resource.error(e.message, null)
|
||||
} finally {
|
||||
EspressoIdlingResource.decrement()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,16 +3,7 @@ package net.vonforst.evmap.viewmodel
|
||||
import android.app.Application
|
||||
import android.graphics.Point
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.*
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.Projection
|
||||
import com.car2go.maps.model.LatLng
|
||||
@@ -33,17 +24,7 @@ import net.vonforst.evmap.api.openchargemap.OCMConnection
|
||||
import net.vonforst.evmap.api.openchargemap.OCMReferenceData
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.autocomplete.PlaceWithBounds
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Chargepoint
|
||||
import net.vonforst.evmap.model.ChargepointListItem
|
||||
import net.vonforst.evmap.model.FILTERS_DISABLED
|
||||
import net.vonforst.evmap.model.FILTERS_FAVORITES
|
||||
import net.vonforst.evmap.model.Favorite
|
||||
import net.vonforst.evmap.model.FavoriteWithDetail
|
||||
import net.vonforst.evmap.model.FilterValue
|
||||
import net.vonforst.evmap.model.FilterValues
|
||||
import net.vonforst.evmap.model.getMultipleChoiceValue
|
||||
import net.vonforst.evmap.model.getSliderValue
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.ChargeLocationsRepository
|
||||
import net.vonforst.evmap.storage.EncryptedPreferenceDataStore
|
||||
@@ -301,12 +282,6 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
}
|
||||
|
||||
val mapTrafficSupported: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>().apply {
|
||||
value = false
|
||||
}
|
||||
}
|
||||
|
||||
val mapTrafficEnabled: MutableLiveData<Boolean> by lazy {
|
||||
MutableLiveData<Boolean>().apply {
|
||||
value = prefs.mapTrafficEnabled
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_tesla"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_tesla" />
|
||||
|
||||
<Button
|
||||
|
||||
@@ -80,7 +80,6 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/map_details"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
app:goneUnless="@{vm.mapTrafficSupported}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -95,7 +94,6 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/map_traffic"
|
||||
android:checked="@={vm.mapTrafficEnabled}"
|
||||
app:goneUnless="@{vm.mapTrafficSupported}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView23" />
|
||||
|
||||
@@ -271,7 +271,6 @@
|
||||
<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">OpenStreetMap</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>
|
||||
|
||||
@@ -271,7 +271,6 @@
|
||||
<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">OpenStreetMap</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>
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.CarInfo
|
||||
|
||||
val CarContext.patchedCarInfo: CarInfo
|
||||
get() = (this.getCarService(CarContext.HARDWARE_SERVICE) as CarHardwareManager).carInfo
|
||||
@@ -7,4 +7,10 @@ fun addDebugInterceptors(context: Context) {
|
||||
|
||||
}
|
||||
|
||||
fun OkHttpClient.Builder.addDebugInterceptors(): OkHttpClient.Builder = this
|
||||
fun OkHttpClient.Builder.addDebugInterceptors(): OkHttpClient.Builder = this
|
||||
|
||||
object EspressoIdlingResource {
|
||||
fun increment() {}
|
||||
|
||||
fun decrement() {}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ buildscript {
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:8.3.2")
|
||||
classpath("com.android.tools.build:gradle:8.2.2")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
classpath("com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$aboutLibsVersion")
|
||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion")
|
||||
@@ -26,6 +26,9 @@ allprojects {
|
||||
mavenCentral()
|
||||
//noinspection JcenterRepositoryObsolete
|
||||
maven { setUrl("https://jitpack.io") }
|
||||
maven {
|
||||
setUrl("https://raw.githubusercontent.com/ev-map/mapbox-gl-native-android/mvn")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,12 +17,6 @@ be put into the app in the form of a resource file called `apikeys.xml` under
|
||||
<string name="mapbox_key" translatable="false">
|
||||
insert your Mapbox key here
|
||||
</string>
|
||||
<string name="jawg_key" translatable="false">
|
||||
insert your Jawg Maps key here
|
||||
</string>
|
||||
<string name="arcgis_key" translatable="false">
|
||||
insert your ArcGIS Maps key here
|
||||
</string>
|
||||
<string name="goingelectric_key" translatable="false">
|
||||
insert your GoingElectric key here
|
||||
</string>
|
||||
@@ -58,12 +52,10 @@ Map providers
|
||||
|
||||
The different Map SDKs are wrapped by our [fork](https://github.com/ev-map/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 OpenStreetMap (vector tiles from
|
||||
[Jawg Maps](https://www.jawg.io/en/) through [MapLibre](https://maplibre.org/)) and allows the user
|
||||
to switch between the two, while the `foss` flavor only includes OSM.
|
||||
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
|
||||
> OSM/MapLibre, as the latter has
|
||||
> ⚠️ 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
|
||||
@@ -85,39 +77,9 @@ to switch between the two, while the `foss` flavor only includes OSM.
|
||||
|
||||
</details>
|
||||
|
||||
### Jawg Maps
|
||||
|
||||
[Dynamic Maps](https://www.jawg.io/docs/apidocs/maps/)
|
||||
|
||||
<details>
|
||||
<summary>How to obtain an API key</summary>
|
||||
|
||||
1. [Sign up](https://www.jawg.io/lab) for a Jawg account
|
||||
2. Under [Access Tokens](https://www.jawg.io/lab/access-tokens), copy your default access token or
|
||||
create a new one. Do not restrict it to a specific origin (this setting is not compatible with
|
||||
Android apps).
|
||||
|
||||
</details>
|
||||
|
||||
### ArcGIS
|
||||
|
||||
[World Imagery basemap](https://www.arcgis.com/home/item.html?id=10df2279f9684e4a9f6a7f08febac2a9)
|
||||
*We use this for the satellite map, as [Jawg's](https://blog.jawg.io/satellite-imaging/) satellite
|
||||
style does not have global coverage.*
|
||||
|
||||
<details>
|
||||
<summary>How to obtain an API key</summary>
|
||||
|
||||
1. [Sign up](https://developers.arcgis.com/dashboard/) for an ArcGIS developer account
|
||||
2. In the dashboard, copy your default API key or create a new one. It has to have access to the
|
||||
"Basemaps" service.
|
||||
|
||||
</details>
|
||||
|
||||
### Mapbox
|
||||
|
||||
[Geocoding API](https://docs.mapbox.com/api/search/geocoding/)
|
||||
*previously we also used Mapbox's Maps SDK, but this has now been switched to Jawg Maps.*
|
||||
[Maps SDK for Android](https://docs.mapbox.com/android/maps)
|
||||
|
||||
<details>
|
||||
<summary>How to obtain an API key</summary>
|
||||
@@ -129,6 +91,7 @@ style does not have global coverage.*
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
Charging station databases
|
||||
--------------------------
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Funkce:
|
||||
- Zobrazuje všechny nabíjecí stanice z komunitou spravovaných databází GoingElectric.de a Open Charge Map.
|
||||
- Informace o dostupnosti v reálném čase (pouze v Evropě)
|
||||
- Integrované srovnání cen pomocí Chargeprice.app (pouze v Evropě)
|
||||
- Mapové podklady z OpenStreetMap
|
||||
- Mapové podklady z OpenStreetMap (Mapbox)
|
||||
- Vyhledávání míst
|
||||
- Pokročilé možnosti filtrování, včetně uložených profilů filtrů
|
||||
- Seznam oblíbených, také s informacemi o dostupnosti
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
Änderungen:
|
||||
- OpenStreetMap-Karten: Wechsel der Datenquelle von Mapbox zu Jawg Maps
|
||||
|
||||
Fehler behoben:
|
||||
- App-Shortcuts repariert
|
||||
- Anzeigefehler behoben
|
||||
- Abstürze behoben
|
||||
@@ -1,2 +0,0 @@
|
||||
Fehler behoben:
|
||||
- Unterstützung für Geräte mit OpenGL ES 2.0 wiederhergestellt
|
||||
@@ -5,7 +5,7 @@ Funktionen:
|
||||
- Anzeige der Stromtankstellen aus den Stromtankstellenverzeichnissen von GoingElectric.de und Open Charge Map
|
||||
- Echtzeit-Verfügbarkeitsanzeige für viele Ladesäulen (nur in Europa)
|
||||
- Integrierter Preisvergleich für die jeweilige Ladesäule mit Chargeprice.app (nur in Europa)
|
||||
- Kartendaten von OpenStreetMap
|
||||
- Kartendaten von OpenStreetMap (Mapbox)
|
||||
- Suche nach Orten
|
||||
- Erweiterte Filterfunktionen, Filterprofile speichern
|
||||
- Favoritenliste, auch mit Anzeige der Verfügbarkeit
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,7 +0,0 @@
|
||||
Changes:
|
||||
- OpenStreetMap maps: Change data source from Mapbox to Jawg Maps
|
||||
|
||||
Bugfixes:
|
||||
- Fixed app shortcuts
|
||||
- Fixed display errors
|
||||
- Fixed crashes
|
||||
@@ -1,2 +0,0 @@
|
||||
Bugfixes:
|
||||
- Restored support for devices with OpenGL ES 2.0
|
||||
@@ -5,7 +5,7 @@ Features:
|
||||
- Shows all charging stations from the community-maintained GoingElectric.de and Open Charge Map directories
|
||||
- Realtime availability information (only in Europe)
|
||||
- Integrated price comparison using Chargeprice.app (only in Europe)
|
||||
- Map data from OpenStreetMap
|
||||
- Map data from OpenStreetMap (Mapbox)
|
||||
- Search for places
|
||||
- Advanced filtering options, including saved filter profiles
|
||||
- Favorites list, also with availability information
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
@@ -5,7 +5,7 @@ Caractéristiques :
|
||||
- Affiche toutes les stations de recharge des répertoires GoingElectric.de et Open Charge Map gérés par la communauté.
|
||||
- Informations sur la disponibilité en temps réel (uniquement en Europe)
|
||||
- Comparaison des prix intégrée grâce à Chargeprice.app (uniquement en Europe)
|
||||
- Données cartographiques provenant d'OpenStreetMap
|
||||
- Données cartographiques provenant d'OpenStreetMap (Mapbox)
|
||||
- Recherche de lieux
|
||||
- Options de filtrage avancées, y compris les profils de filtrage enregistrés
|
||||
- Liste de favoris, avec également des informations sur la disponibilité
|
||||
|
||||
@@ -5,7 +5,7 @@ Du finner info om ladestasjoner i hele verden og sanntidsinfo for mange av dem s
|
||||
- Materiell design
|
||||
- Sanntidsinfo (kun i Europa)
|
||||
- Integrert sammenligningsinfo ved bruk av Chargeprice.app (kun i Europa)
|
||||
- Kartdata fra OpenStreetMap
|
||||
- Kartdata fra OpenStreetMap (Mapbox)
|
||||
- Søk etter steder
|
||||
- Avanserte filtreringsvalg, inkludert lagrede filterprofiler
|
||||
- Favorittliste, som også har tilgjengelighetsinfo
|
||||
|
||||
@@ -5,7 +5,7 @@ Kenmerken:
|
||||
- Toont alle laadpunten van de GoingElectric.de en Open Charge Map databanken
|
||||
- Real-time status (enkel in Europa)
|
||||
- Geïntegreerde prijsvergelijking via Chargeprice.app (enkel in Europe)
|
||||
- Kaartgegevens van OpenStreetMap
|
||||
- Kaartgegevens van OpenStreetMap (Mapbox)
|
||||
- Zoek naar locaties
|
||||
- Geavanceerde filtermogelijkheden, inclusief bewaarde filterprofielen
|
||||
- Lijst van favorieten, met statusinformatie
|
||||
|
||||
@@ -5,7 +5,7 @@ Destaques:
|
||||
- Mostra todas as estações de carregamento dos diretórios GoingElectric.de e Open Charge Map mantidos pela comunidade
|
||||
- Informação de disponibilidade em tempo real (apenas na Europa)
|
||||
- Comparação de preços integrada usando o Chargeprice.app (apenas na Europa)
|
||||
- Informação do mapa via OpenStreetMap
|
||||
- Informação do mapa via OpenStreetMap (Mapbox)
|
||||
- Pesquise lugares
|
||||
- Opções de filtragem avançadas, incluindo filtros de pesquisa que podem ser guardados
|
||||
- Lista de favoritos, também com informações de disponibilidade
|
||||
|
||||