mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-24 15:47:44 -05:00
Compare commits
38 Commits
1.8.1
...
screenshot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e1e22a44b | ||
|
|
d638a88f4d | ||
|
|
b9c08a8e75 | ||
|
|
ab2a77d25a | ||
|
|
e0ddc9f734 | ||
|
|
168fa244d3 | ||
|
|
76388ccc6e | ||
|
|
76aac7dae4 | ||
|
|
ee8b586079 | ||
|
|
360e7767bd | ||
|
|
5f0c9fd31d | ||
|
|
536c884f23 | ||
|
|
7daf5a0adb | ||
|
|
862f2b06d8 | ||
|
|
198a9ecc48 | ||
|
|
2762a32105 | ||
|
|
8a83a80e75 | ||
|
|
75e8569964 | ||
|
|
00b26d224f | ||
|
|
836f42b299 | ||
|
|
3de994f09d | ||
|
|
d78eda9d97 | ||
|
|
ed4be05aed | ||
|
|
45de4c8ff0 | ||
|
|
b5b785be07 | ||
|
|
0b8589d599 | ||
|
|
ba757831f3 | ||
|
|
1990152836 | ||
|
|
50fd433439 | ||
|
|
381e6f3d98 | ||
|
|
9c5582f19c | ||
|
|
1c16d8cbb6 | ||
|
|
1734f1c09e | ||
|
|
ebf0f82597 | ||
|
|
85a38a6da1 | ||
|
|
60ca97179c | ||
|
|
4413cba9fa | ||
|
|
e2e6a3060b |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java environment
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'zulu'
|
||||
|
||||
115
.github/workflows/screenshots.yml
vendored
Normal file
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
2
.github/workflows/tests.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java environment
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'zulu'
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2023 Johan von Forstner and contributors
|
||||
Copyright (c) 2020-2024 Johan von Forstner and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -8,10 +8,8 @@ plugins {
|
||||
id("kotlin-kapt")
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
id("com.mikepenz.aboutlibraries.plugin")
|
||||
id("pt.jcosta.resourceplaceholders")
|
||||
}
|
||||
|
||||
val supportedLocales = "en,de,fr,nb-rNO,nl,pt,ro,cs"
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
@@ -20,12 +18,10 @@ android {
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
|
||||
versionCode = 210
|
||||
versionName = "1.8.1"
|
||||
versionCode = 212
|
||||
versionName = "1.8.2"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
resourceConfigurations += supportedLocales.split(",")
|
||||
buildConfigField("String", "supportedLocales", "\"$supportedLocales\"")
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
@@ -101,6 +97,9 @@ android {
|
||||
disable += listOf("NullSafeMutableLiveData")
|
||||
warning += listOf("MissingTranslation")
|
||||
}
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
@@ -108,9 +107,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
resourcePlaceholders {
|
||||
files("xml/shortcuts.xml")
|
||||
}
|
||||
namespace = "net.vonforst.evmap"
|
||||
|
||||
// add API keys from environment variable if not set in apikeys.xml
|
||||
@@ -265,7 +261,7 @@ dependencies {
|
||||
automotiveImplementation("androidx.car.app:app-automotive:$carAppVersion")
|
||||
|
||||
// AnyMaps
|
||||
val anyMapsVersion = "60b6d4f821"
|
||||
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")
|
||||
@@ -280,6 +276,14 @@ dependencies {
|
||||
// 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
|
||||
googleImplementation("com.google.android.libraries.places:places:3.3.0")
|
||||
googleImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3")
|
||||
@@ -301,7 +305,10 @@ dependencies {
|
||||
implementation("androidx.room:room-runtime:$room_version")
|
||||
kapt("androidx.room:room-compiler:$room_version")
|
||||
implementation("androidx.room:room-ktx:$room_version")
|
||||
implementation("com.github.anboralabs:spatia-room:0.2.7")
|
||||
implementation("com.github.anboralabs:spatia-room:0.2.9") {
|
||||
exclude(group = "com.github.dalgarins", module = "android-spatialite")
|
||||
}
|
||||
implementation("com.github.EV-map:android-spatialite:e5495c83ad") // version with minSdk increased to 21 & FORTIFY_SOURCE enabled
|
||||
|
||||
// billing library
|
||||
val billing_version = "6.1.0"
|
||||
@@ -318,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")
|
||||
@@ -328,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,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
<package android:name="com.google.android.projection.gearhead" />
|
||||
<package android:name="com.google.android.apps.automotive.templates.host" />
|
||||
<package android:name="com.google.android.apps.maps" />
|
||||
</queries>
|
||||
|
||||
<application
|
||||
@@ -40,8 +41,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:localeConfig="@xml/locales_config">
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<meta-data
|
||||
android:name="com.mapbox.ACCESS_TOKEN"
|
||||
|
||||
@@ -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)
|
||||
@@ -268,7 +266,7 @@ class MapsActivity : AppCompatActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
fun openUrl(url: String) {
|
||||
fun openUrl(url: String, preferBrowser: Boolean = true) {
|
||||
val pkg = CustomTabsClient.getPackageName(this, null)
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
@@ -280,7 +278,7 @@ class MapsActivity : AppCompatActivity(),
|
||||
pkg?.let {
|
||||
// prefer to open URL in custom tab, even if native app
|
||||
// available (such as EVMap itself)
|
||||
intent.intent.setPackage(pkg)
|
||||
if (preferBrowser) intent.intent.setPackage(pkg)
|
||||
}
|
||||
try {
|
||||
intent.launchUrl(this, Uri.parse(url))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
@@ -121,4 +122,21 @@ fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): Pa
|
||||
getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(flags.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION") getPackageInfo(packageName, flags)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun PackageManager.getApplicationInfoCompat(packageName: String, flags: Int = 0): ApplicationInfo =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
getApplicationInfo(packageName, PackageManager.ApplicationInfoFlags.of(flags.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION") getApplicationInfo(packageName, flags)
|
||||
}
|
||||
|
||||
|
||||
fun PackageManager.isAppInstalled(packageName: String): Boolean {
|
||||
return try {
|
||||
getApplicationInfoCompat(packageName, 0).enabled
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,28 @@ import kotlinx.coroutines.withContext
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.addDebugInterceptors
|
||||
import net.vonforst.evmap.api.*
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.api.ChargepointApi
|
||||
import net.vonforst.evmap.api.ChargepointList
|
||||
import net.vonforst.evmap.api.FiltersSQLQuery
|
||||
import net.vonforst.evmap.api.StringProvider
|
||||
import net.vonforst.evmap.api.mapPower
|
||||
import net.vonforst.evmap.api.mapPowerInverse
|
||||
import net.vonforst.evmap.api.nameForPlugType
|
||||
import net.vonforst.evmap.api.powerSteps
|
||||
import net.vonforst.evmap.model.BooleanFilter
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Chargepoint
|
||||
import net.vonforst.evmap.model.ChargepointListItem
|
||||
import net.vonforst.evmap.model.Filter
|
||||
import net.vonforst.evmap.model.FilterValue
|
||||
import net.vonforst.evmap.model.FilterValues
|
||||
import net.vonforst.evmap.model.MultipleChoiceFilter
|
||||
import net.vonforst.evmap.model.MultipleChoiceFilterValue
|
||||
import net.vonforst.evmap.model.ReferenceData
|
||||
import net.vonforst.evmap.model.SliderFilter
|
||||
import net.vonforst.evmap.model.getBooleanValue
|
||||
import net.vonforst.evmap.model.getMultipleChoiceValue
|
||||
import net.vonforst.evmap.model.getSliderValue
|
||||
import net.vonforst.evmap.viewmodel.Resource
|
||||
import net.vonforst.evmap.viewmodel.getClusterDistance
|
||||
import okhttp3.Cache
|
||||
@@ -22,7 +42,11 @@ import retrofit2.HttpException
|
||||
import retrofit2.Response
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.http.*
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
import java.io.IOException
|
||||
import java.time.Duration
|
||||
|
||||
@@ -123,6 +147,8 @@ interface GoingElectricApi {
|
||||
}
|
||||
}
|
||||
|
||||
private const val STATUS_OK = "ok"
|
||||
|
||||
class GoingElectricApiWrapper(
|
||||
val apikey: String,
|
||||
baseurl: String = "https://api.goingelectric.de",
|
||||
@@ -211,11 +237,11 @@ class GoingElectricApiWrapper(
|
||||
categories = categories,
|
||||
startkey = startkey
|
||||
)
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
if (!response.isSuccessful || response.body()!!.status != STATUS_OK) {
|
||||
return Resource.error(response.message(), null)
|
||||
} else {
|
||||
val body = response.body()!!
|
||||
data.addAll(body.chargelocations)
|
||||
data.addAll(body.chargelocations!!)
|
||||
startkey = body.startkey
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
@@ -308,11 +334,11 @@ class GoingElectricApiWrapper(
|
||||
categories = categories,
|
||||
startkey = startkey
|
||||
)
|
||||
if (!response.isSuccessful || response.body()!!.status != "ok") {
|
||||
if (!response.isSuccessful || response.body()!!.status != STATUS_OK) {
|
||||
return Resource.error(response.message(), null)
|
||||
} else {
|
||||
val body = response.body()!!
|
||||
data.addAll(body.chargelocations)
|
||||
data.addAll(body.chargelocations!!)
|
||||
startkey = body.startkey
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
@@ -393,9 +419,9 @@ class GoingElectricApiWrapper(
|
||||
): Resource<ChargeLocation> {
|
||||
try {
|
||||
val response = api.getChargepointDetail(id)
|
||||
return if (response.isSuccessful && response.body()!!.status == "ok" && response.body()!!.chargelocations.size == 1) {
|
||||
return if (response.isSuccessful && response.body()!!.status == STATUS_OK && response.body()!!.chargelocations!!.size == 1) {
|
||||
Resource.success(
|
||||
(response.body()!!.chargelocations[0] as GEChargeLocation).convert(
|
||||
(response.body()!!.chargelocations!![0] as GEChargeLocation).convert(
|
||||
apikey, true
|
||||
)
|
||||
)
|
||||
@@ -423,16 +449,19 @@ class GoingElectricApiWrapper(
|
||||
|
||||
val responses = listOf(plugsResponse, chargeCardsResponse, networksResponse)
|
||||
|
||||
if (responses.map { it.isSuccessful }.all { it }) {
|
||||
if (responses.map { it.isSuccessful }.all { it }
|
||||
&& plugsResponse.body()!!.status == STATUS_OK
|
||||
&& chargeCardsResponse.body()!!.status == STATUS_OK
|
||||
&& networksResponse.body()!!.status == STATUS_OK) {
|
||||
Resource.success(
|
||||
GEReferenceData(
|
||||
plugsResponse.body()!!.result,
|
||||
networksResponse.body()!!.result,
|
||||
chargeCardsResponse.body()!!.result
|
||||
plugsResponse.body()!!.result!!,
|
||||
networksResponse.body()!!.result!!,
|
||||
chargeCardsResponse.body()!!.result!!
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Resource.error(responses.find { !it.isSuccessful }!!.message(), null)
|
||||
Resource.error(responses.find { !it.isSuccessful }?.message(), null)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Resource.error(e.message, null)
|
||||
|
||||
@@ -27,20 +27,20 @@ import java.time.LocalTime
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GEChargepointList(
|
||||
val status: String,
|
||||
val chargelocations: List<GEChargepointListItem>,
|
||||
val chargelocations: List<GEChargepointListItem>?,
|
||||
@JsonObjectOrFalse val startkey: Int?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GEStringList(
|
||||
val status: String,
|
||||
val result: List<String>
|
||||
val result: List<String>?
|
||||
)
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GEChargeCardList(
|
||||
val status: String,
|
||||
val result: List<GEChargeCard>
|
||||
val result: List<GEChargeCard>?
|
||||
)
|
||||
|
||||
sealed class GEChargepointListItem {
|
||||
|
||||
@@ -8,8 +8,26 @@ import com.squareup.moshi.Moshi
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.addDebugInterceptors
|
||||
import net.vonforst.evmap.api.*
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.api.ChargepointApi
|
||||
import net.vonforst.evmap.api.ChargepointList
|
||||
import net.vonforst.evmap.api.FiltersSQLQuery
|
||||
import net.vonforst.evmap.api.StringProvider
|
||||
import net.vonforst.evmap.api.mapPower
|
||||
import net.vonforst.evmap.api.mapPowerInverse
|
||||
import net.vonforst.evmap.api.powerSteps
|
||||
import net.vonforst.evmap.model.BooleanFilter
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.ChargepointListItem
|
||||
import net.vonforst.evmap.model.Filter
|
||||
import net.vonforst.evmap.model.FilterValue
|
||||
import net.vonforst.evmap.model.FilterValues
|
||||
import net.vonforst.evmap.model.MultipleChoiceFilter
|
||||
import net.vonforst.evmap.model.MultipleChoiceFilterValue
|
||||
import net.vonforst.evmap.model.ReferenceData
|
||||
import net.vonforst.evmap.model.SliderFilter
|
||||
import net.vonforst.evmap.model.getBooleanValue
|
||||
import net.vonforst.evmap.model.getMultipleChoiceValue
|
||||
import net.vonforst.evmap.model.getSliderValue
|
||||
import net.vonforst.evmap.viewmodel.Resource
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
@@ -390,9 +408,7 @@ class OpenChargeMapApiWrapper(
|
||||
}
|
||||
|
||||
override fun filteringInSQLRequiresDetails(filters: FilterValues): Boolean {
|
||||
val operators = filters.getMultipleChoiceValue("operators")
|
||||
return (operators != null && !operators.all)
|
||||
// TODO: it would be possible to implement this without requiring details if we extended the data structure to also save the operator ID in the DB
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
@@ -63,7 +63,7 @@ data class OCMChargepoint(
|
||||
Coordinate(addressInfo.latitude, addressInfo.longitude),
|
||||
addressInfo.toAddress(refData),
|
||||
connections.map { it.convert(refData) },
|
||||
operatorInfo?.title,
|
||||
operatorInfo?.title ?: refData.operators.find { it.id == operatorId }?.title,
|
||||
"https://map.openchargemap.io/?id=$id",
|
||||
"https://map.openchargemap.io/?id=$id",
|
||||
convertFaultReport(),
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.*
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Matrix
|
||||
import android.graphics.RectF
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.net.Uri
|
||||
import android.text.SpannableString
|
||||
@@ -11,7 +15,17 @@ import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.model.*
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.ActionStrip
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.CarIconSpan
|
||||
import androidx.car.app.model.ForegroundCarColorSpan
|
||||
import androidx.car.app.model.Pane
|
||||
import androidx.car.app.model.PaneTemplate
|
||||
import androidx.car.app.model.ParkedOnlyOnClickListener
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.scale
|
||||
import androidx.core.text.HtmlCompat
|
||||
@@ -22,13 +36,17 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.EXTRA_CHARGER_ID
|
||||
import net.vonforst.evmap.EXTRA_LAT
|
||||
import net.vonforst.evmap.EXTRA_LON
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.formatTeslaParkingFee
|
||||
import net.vonforst.evmap.adapter.formatTeslaPricing
|
||||
import net.vonforst.evmap.api.availability.AvailabilityRepository
|
||||
import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.tesla.Pricing
|
||||
import net.vonforst.evmap.api.availability.tesla.TeslaChargingOwnershipGraphQlApi
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.fronyx.FronyxApi
|
||||
@@ -41,6 +59,7 @@ import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Cost
|
||||
import net.vonforst.evmap.model.FaultReport
|
||||
import net.vonforst.evmap.model.Favorite
|
||||
import net.vonforst.evmap.plus
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.ChargeLocationsRepository
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
@@ -131,7 +150,16 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
)
|
||||
.setTitle(carContext.getString(R.string.auto_prices))
|
||||
.setOnClickListener {
|
||||
screenManager.push(ChargepriceScreen(carContext, charger))
|
||||
if (prefs.chargepriceNativeIntegration) {
|
||||
screenManager.push(ChargepriceScreen(carContext, charger))
|
||||
} else {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(ChargepriceApi.getPoiUrl(charger))
|
||||
)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
carContext.startActivity(intent)
|
||||
}
|
||||
}
|
||||
.build())
|
||||
}
|
||||
|
||||
@@ -14,14 +14,30 @@ import androidx.car.app.CarToast
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.annotations.ExperimentalCarApi
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.model.*
|
||||
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.ListTemplate
|
||||
import androidx.car.app.model.MessageTemplate
|
||||
import androidx.car.app.model.ParkedOnlyOnClickListener
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.SectionedItemList
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.car.app.model.Toggle
|
||||
import androidx.core.content.IntentCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.EXTRA_DONATE
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.addDebugInterceptors
|
||||
import net.vonforst.evmap.api.availability.tesla.TeslaAuthenticationApi
|
||||
import net.vonforst.evmap.api.availability.tesla.TeslaOwnerApi
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
@@ -29,6 +45,7 @@ import net.vonforst.evmap.api.chargeprice.ChargepriceCar
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceTariff
|
||||
import net.vonforst.evmap.fragment.oauth.OAuthLoginFragment
|
||||
import net.vonforst.evmap.fragment.oauth.OAuthLoginFragmentArgs
|
||||
import net.vonforst.evmap.getPackageInfoCompat
|
||||
import net.vonforst.evmap.storage.AppDatabase
|
||||
import net.vonforst.evmap.storage.EncryptedPreferenceDataStore
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
@@ -408,12 +425,21 @@ class ChargepriceSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
setTitle(carContext.getString(R.string.settings_chargeprice))
|
||||
setHeaderAction(Action.BACK)
|
||||
setSingleList(ItemList.Builder().apply {
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_chargeprice_native_integration))
|
||||
addText(carContext.getString(if (prefs.chargepriceNativeIntegration) R.string.pref_chargeprice_native_integration_on else R.string.pref_chargeprice_native_integration_off))
|
||||
setToggle(Toggle.Builder {
|
||||
prefs.chargepriceNativeIntegration = it
|
||||
invalidate()
|
||||
}.setChecked(prefs.chargepriceNativeIntegration).build())
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_my_vehicle))
|
||||
setBrowsable(true)
|
||||
setOnClickListener {
|
||||
screenManager.push(SelectVehiclesScreen(carContext))
|
||||
}
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_my_tariffs))
|
||||
@@ -437,6 +463,7 @@ class ChargepriceSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
)
|
||||
}
|
||||
)
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.settings_android_auto_chargeprice_range))
|
||||
@@ -454,6 +481,7 @@ class ChargepriceSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
setOnClickListener {
|
||||
screenManager.push(SelectChargingRangeScreen(carContext))
|
||||
}
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_chargeprice_currency))
|
||||
@@ -469,27 +497,31 @@ class ChargepriceSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
setOnClickListener {
|
||||
screenManager.push(SelectCurrencyScreen(carContext))
|
||||
}
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_chargeprice_no_base_fee))
|
||||
setToggle(Toggle.Builder {
|
||||
prefs.chargepriceNoBaseFee = it
|
||||
}.setChecked(prefs.chargepriceNoBaseFee).build())
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_chargeprice_show_provider_customer_tariffs))
|
||||
addText(carContext.getString(R.string.pref_chargeprice_show_provider_customer_tariffs_summary))
|
||||
setToggle(Toggle.Builder {
|
||||
prefs.chargepriceShowProviderCustomerTariffs = it
|
||||
}.setChecked(prefs.chargepriceShowProviderCustomerTariffs).build())
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
if (maxRows > 6) {
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_chargeprice_show_provider_customer_tariffs))
|
||||
addText(carContext.getString(R.string.pref_chargeprice_show_provider_customer_tariffs_summary))
|
||||
setToggle(Toggle.Builder {
|
||||
prefs.chargepriceShowProviderCustomerTariffs = it
|
||||
}.setChecked(prefs.chargepriceShowProviderCustomerTariffs).build())
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_chargeprice_allow_unbalanced_load))
|
||||
addText(carContext.getString(R.string.pref_chargeprice_allow_unbalanced_load_summary))
|
||||
setToggle(Toggle.Builder {
|
||||
prefs.chargepriceAllowUnbalancedLoad = it
|
||||
}.setChecked(prefs.chargepriceAllowUnbalancedLoad).build())
|
||||
setEnabled(prefs.chargepriceNativeIntegration)
|
||||
}.build())
|
||||
}
|
||||
}.build())
|
||||
|
||||
@@ -22,5 +22,8 @@ abstract class DonateFragmentBase : Fragment() {
|
||||
referrals.referralEwieeinfach.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.ewieeinfach_referral_link))
|
||||
}
|
||||
referrals.referralEprimo.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.eprimo_referral_link))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.databinding.DataBindingUtil
|
||||
@@ -108,31 +113,19 @@ class FilterFragment : Fragment(), MenuProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveProfile(error: Boolean = false) {
|
||||
showEditTextDialog(requireContext()) { dialog, input ->
|
||||
private fun saveProfile() {
|
||||
showEditTextDialog(requireContext(), { dialog, input ->
|
||||
vm.filterProfile.value?.let { profile ->
|
||||
input.setText(profile.name)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
input.error = getString(R.string.required)
|
||||
}
|
||||
|
||||
dialog.setTitle(R.string.save_as_profile)
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
if (input.text.isBlank()) {
|
||||
saveProfile(true)
|
||||
} else {
|
||||
lifecycleScope.launch {
|
||||
vm.saveAsProfile(input.text.toString())
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
|
||||
}
|
||||
}
|
||||
}, {
|
||||
lifecycleScope.launch {
|
||||
vm.saveAsProfile(it)
|
||||
findNavController().popBackStack()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -183,20 +183,16 @@ class FilterProfilesFragment : Fragment() {
|
||||
adapter = FilterProfilesAdapter(touchHelper, onDelete = { fp ->
|
||||
delete(fp)
|
||||
}, onRename = { fp ->
|
||||
showEditTextDialog(requireContext()) { dialog, input ->
|
||||
showEditTextDialog(requireContext(), { dialog, input ->
|
||||
input.setText(fp.name)
|
||||
|
||||
dialog.setTitle(R.string.rename)
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
lifecycleScope.launch {
|
||||
vm.update(fp.copy(name = input.text.toString()))
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ ->
|
||||
|
||||
}
|
||||
}
|
||||
}, {
|
||||
lifecycleScope.launch {
|
||||
vm.update(fp.copy(name = it))
|
||||
}
|
||||
})
|
||||
})
|
||||
binding.filterProfilesList.apply {
|
||||
this.adapter = this@FilterProfilesFragment.adapter
|
||||
|
||||
@@ -11,7 +11,14 @@ import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.method.KeyListener
|
||||
import android.view.*
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ImageView
|
||||
@@ -23,7 +30,13 @@ import androidx.annotation.RequiresPermission
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.location.LocationListenerCompat
|
||||
import androidx.core.view.*
|
||||
import androidx.core.view.MenuCompat
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.doOnLayout
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.databinding.DataBindingUtil
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
@@ -31,7 +44,6 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.FragmentNavigatorExtras
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
@@ -57,7 +69,12 @@ import com.google.android.material.transition.MaterialContainerTransform.FADE_MO
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.*
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.BottomSheetCallback
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_ANCHOR_POINT
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_COLLAPSED
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.STATE_SETTLING
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike.from
|
||||
import com.mahc.custombottomsheetbehavior.MergedAppBarLayoutBehavior
|
||||
import com.stfalcon.imageviewer.StfalconImageViewer
|
||||
import io.michaelrocks.bimap.HashBiMap
|
||||
@@ -72,6 +89,7 @@ import net.vonforst.evmap.adapter.ConnectorAdapter
|
||||
import net.vonforst.evmap.adapter.DetailsAdapter
|
||||
import net.vonforst.evmap.adapter.GalleryAdapter
|
||||
import net.vonforst.evmap.adapter.PlaceAutocompleteAdapter
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.autocomplete.ApiUnavailableException
|
||||
import net.vonforst.evmap.autocomplete.PlaceWithBounds
|
||||
import net.vonforst.evmap.bold
|
||||
@@ -79,17 +97,35 @@ import net.vonforst.evmap.databinding.FragmentMapBinding
|
||||
import net.vonforst.evmap.location.FusionEngine
|
||||
import net.vonforst.evmap.location.LocationEngine
|
||||
import net.vonforst.evmap.location.Priority
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.ChargeLocationCluster
|
||||
import net.vonforst.evmap.model.ChargepointListItem
|
||||
import net.vonforst.evmap.model.ChargerPhoto
|
||||
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.navigation.safeNavigate
|
||||
import net.vonforst.evmap.shouldUseImperialUnits
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.*
|
||||
import net.vonforst.evmap.ui.ChargerIconGenerator
|
||||
import net.vonforst.evmap.ui.ClusterIconGenerator
|
||||
import net.vonforst.evmap.ui.HideOnScrollFabBehavior
|
||||
import net.vonforst.evmap.ui.MarkerAnimator
|
||||
import net.vonforst.evmap.ui.chargerZ
|
||||
import net.vonforst.evmap.ui.clusterZ
|
||||
import net.vonforst.evmap.ui.getMarkerTint
|
||||
import net.vonforst.evmap.ui.placeSearchZ
|
||||
import net.vonforst.evmap.ui.setTouchModal
|
||||
import net.vonforst.evmap.utils.boundingBox
|
||||
import net.vonforst.evmap.utils.checkAnyLocationPermission
|
||||
import net.vonforst.evmap.utils.checkFineLocationPermission
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import net.vonforst.evmap.utils.formatDecimal
|
||||
import net.vonforst.evmap.viewmodel.*
|
||||
import net.vonforst.evmap.viewmodel.GalleryViewModel
|
||||
import net.vonforst.evmap.viewmodel.MapPosition
|
||||
import net.vonforst.evmap.viewmodel.MapViewModel
|
||||
import net.vonforst.evmap.viewmodel.Resource
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import java.io.IOException
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@@ -119,6 +155,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private var connectionErrorSnackbar: Snackbar? = null
|
||||
private var previousChargepointIds: Set<Long>? = null
|
||||
private var mapTopPadding: Int = 0
|
||||
private var popupMenu: PopupMenu? = null
|
||||
|
||||
private lateinit var clusterIconGenerator: ClusterIconGenerator
|
||||
private lateinit var chargerIconGenerator: ChargerIconGenerator
|
||||
@@ -399,12 +436,16 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
binding.detailView.btnChargeprice.setOnClickListener {
|
||||
val charger = vm.charger.value?.data ?: return@setOnClickListener
|
||||
val extras =
|
||||
FragmentNavigatorExtras(binding.detailView.btnChargeprice to getString(R.string.shared_element_chargeprice))
|
||||
findNavController().safeNavigate(
|
||||
MapFragmentDirections.actionMapToChargepriceFragment(charger),
|
||||
extras
|
||||
)
|
||||
if (prefs.chargepriceNativeIntegration) {
|
||||
val extras =
|
||||
FragmentNavigatorExtras(binding.detailView.btnChargeprice to getString(R.string.shared_element_chargeprice))
|
||||
findNavController().safeNavigate(
|
||||
MapFragmentDirections.actionMapToChargepriceFragment(charger),
|
||||
extras
|
||||
)
|
||||
} else {
|
||||
(activity as? MapsActivity)?.openUrl(ChargepriceApi.getPoiUrl(charger), false)
|
||||
}
|
||||
}
|
||||
binding.detailView.btnChargerWebsite.setOnClickListener {
|
||||
val charger = vm.charger.value?.data ?: return@setOnClickListener
|
||||
@@ -689,6 +730,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
displaySearchResult(place, moveCamera = true)
|
||||
}
|
||||
vm.layersMenuOpen.observe(viewLifecycleOwner) { open ->
|
||||
HideOnScrollFabBehavior.from(binding.fabLayers)?.hidden = open
|
||||
binding.fabLayers.visibility = if (open) View.INVISIBLE else View.VISIBLE
|
||||
binding.layersSheet.visibility = if (open) View.VISIBLE else View.INVISIBLE
|
||||
updateBackPressedCallback()
|
||||
@@ -1359,14 +1401,13 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
MenuCompat.setGroupDividerEnabled(popup.menu, true)
|
||||
popup.setForceShowIcon(true)
|
||||
popup.setOnMenuItemClickListener {
|
||||
val navController = requireView().findNavController()
|
||||
when (it.itemId) {
|
||||
R.id.menu_edit_filters -> {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
lifecycleScope.launch {
|
||||
vm.copyFiltersToCustom()
|
||||
navController.safeNavigate(
|
||||
findNavController().safeNavigate(
|
||||
MapFragmentDirections.actionMapToFilterFragment()
|
||||
)
|
||||
}
|
||||
@@ -1376,7 +1417,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
R.id.menu_manage_filter_profiles -> {
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
navController.safeNavigate(
|
||||
findNavController().safeNavigate(
|
||||
MapFragmentDirections.actionMapToFilterProfilesFragment()
|
||||
)
|
||||
true
|
||||
@@ -1456,6 +1497,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
popup.setTouchModal(false)
|
||||
popupMenu = popup
|
||||
popup.show()
|
||||
}
|
||||
|
||||
@@ -1539,5 +1581,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
/* if we don't dismiss the popup menu, it will be recreated in some cases
|
||||
(split-screen mode) and then have references to a destroyed fragment. */
|
||||
popupMenu?.dismiss()
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.text.Spanned
|
||||
import android.text.style.RelativeSizeSpan
|
||||
import android.view.View
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.ui.MultiSelectDialogPreference
|
||||
import net.vonforst.evmap.viewmodel.SettingsViewModel
|
||||
@@ -28,9 +29,11 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
|
||||
private lateinit var myVehiclePreference: MultiSelectDialogPreference
|
||||
private lateinit var myTariffsPreference: MultiSelectDialogPreference
|
||||
private lateinit var nativeIntegrationPreference: CheckBoxPreference
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
nativeIntegrationPreference = findPreference("chargeprice_native_integration")!!
|
||||
|
||||
myVehiclePreference = findPreference("chargeprice_my_vehicle")!!
|
||||
myVehiclePreference.isEnabled = false
|
||||
@@ -48,7 +51,7 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
)
|
||||
}
|
||||
}.toTypedArray()
|
||||
myVehiclePreference.isEnabled = true
|
||||
myVehiclePreference.isEnabled = nativeIntegrationPreference.isChecked
|
||||
updateMyVehiclesSummary()
|
||||
}
|
||||
}
|
||||
@@ -65,10 +68,28 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
it.name
|
||||
}
|
||||
}.toTypedArray()
|
||||
myTariffsPreference.isEnabled = true
|
||||
myTariffsPreference.isEnabled = nativeIntegrationPreference.isChecked
|
||||
updateMyTariffsSummary()
|
||||
}
|
||||
}
|
||||
updateNativeIntegrationState()
|
||||
}
|
||||
|
||||
private fun updateNativeIntegrationState() {
|
||||
for (i in 0 until preferenceScreen.preferenceCount) {
|
||||
val pref = preferenceScreen.getPreference(i)
|
||||
if (pref == nativeIntegrationPreference) {
|
||||
continue
|
||||
} else if (pref == myTariffsPreference) {
|
||||
pref.isEnabled =
|
||||
nativeIntegrationPreference.isChecked && vm.tariffs.value?.data != null
|
||||
} else if (pref == myVehiclePreference) {
|
||||
pref.isEnabled =
|
||||
nativeIntegrationPreference.isChecked && vm.tariffs.value?.data != null
|
||||
} else {
|
||||
pref.isEnabled = nativeIntegrationPreference.isChecked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateMyTariffsSummary() {
|
||||
@@ -110,6 +131,10 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
"chargeprice_my_tariffs" -> {
|
||||
updateMyTariffsSummary()
|
||||
}
|
||||
|
||||
"chargeprice_native_integration" -> {
|
||||
updateNativeIntegrationState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,11 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.isAppInstalled
|
||||
import net.vonforst.evmap.ui.getAppLocale
|
||||
import net.vonforst.evmap.ui.updateAppLocale
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
@@ -16,6 +18,7 @@ import net.vonforst.evmap.ui.updateNightMode
|
||||
class UiSettingsFragment : BaseSettingsFragment() {
|
||||
override val isTopLevel = false
|
||||
lateinit var langPref: ListPreference
|
||||
lateinit var immediateNavPref: CheckBoxPreference
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.settings_ui, rootKey)
|
||||
@@ -28,11 +31,18 @@ class UiSettingsFragment : BaseSettingsFragment() {
|
||||
|
||||
val appLinkPref = findPreference<Preference>("applink_associate")!!
|
||||
appLinkPref.isVisible = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||
|
||||
immediateNavPref = findPreference("navigate_use_maps")!!
|
||||
immediateNavPref.isVisible = isGoogleMapsInstalled()
|
||||
}
|
||||
|
||||
private fun isGoogleMapsInstalled() =
|
||||
requireContext().packageManager.isAppInstalled("com.google.android.apps.maps")
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
langPref.value = getAppLocale()
|
||||
langPref.value = getAppLocale(requireContext())
|
||||
immediateNavPref.isVisible = isGoogleMapsInstalled()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
|
||||
@@ -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(
|
||||
@@ -147,6 +150,12 @@ class PreferenceDataSource(val context: Context) {
|
||||
sp.edit().putBoolean("update_0.6.0_androidauto_dialog_shown", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceNativeIntegration: Boolean
|
||||
get() = sp.getBoolean("chargeprice_native_integration", true)
|
||||
set(value) {
|
||||
sp.edit().putBoolean("chargeprice_native_integration", value).apply()
|
||||
}
|
||||
|
||||
var chargepriceMyVehicles: Set<String>
|
||||
get() = try {
|
||||
sp.getStringSet("chargeprice_my_vehicle", emptySet())!!
|
||||
|
||||
@@ -26,9 +26,13 @@ import androidx.viewpager2.widget.ViewPager2
|
||||
import coil.load
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.slider.RangeSlider
|
||||
import net.vonforst.evmap.*
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.api.iconForPlugType
|
||||
import net.vonforst.evmap.isDarkMode
|
||||
import net.vonforst.evmap.kmPerMile
|
||||
import net.vonforst.evmap.meterPerFt
|
||||
import net.vonforst.evmap.shouldUseImperialUnits
|
||||
import java.time.Instant
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.floor
|
||||
@@ -69,7 +73,7 @@ fun invisibleUnlessAnimated(view: View, oldValue: Boolean, newValue: Boolean) {
|
||||
if (oldValue == newValue) {
|
||||
if (!newValue && view.visibility == View.VISIBLE && view.alpha == 1f) {
|
||||
// view is initially invisible
|
||||
view.visibility = View.GONE
|
||||
view.visibility = View.INVISIBLE
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package net.vonforst.evmap.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
|
||||
fun updateNightMode(prefs: PreferenceDataSource) {
|
||||
AppCompatDelegate.setDefaultNightMode(
|
||||
when (prefs.darkmode) {
|
||||
@@ -25,13 +27,14 @@ fun updateAppLocale(language: String) {
|
||||
)
|
||||
}
|
||||
|
||||
fun getAppLocale(): String? {
|
||||
fun getAppLocale(context: Context): String? {
|
||||
val locales = AppCompatDelegate.getApplicationLocales()
|
||||
return if (locales.isEmpty) {
|
||||
"default"
|
||||
} else {
|
||||
val arr = Array(locales.size()) { locales.get(it)!!.toLanguageTag() }
|
||||
LocaleListCompat.forLanguageTags(BuildConfig.supportedLocales).getFirstMatch(arr)
|
||||
?.toLanguageTag()
|
||||
val choices =
|
||||
context.resources.getStringArray(R.array.pref_language_values).joinToString(",")
|
||||
LocaleListCompat.forLanguageTags(choices).getFirstMatch(arr)?.toLanguageTag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,50 +10,60 @@ import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.FrameLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatDialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import net.vonforst.evmap.R
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private fun dialogEditText(ctx: Context): Pair<View, EditText> {
|
||||
val container = FrameLayout(ctx)
|
||||
container.setPadding(
|
||||
(16 * ctx.resources.displayMetrics.density).toInt(), 0,
|
||||
(16 * ctx.resources.displayMetrics.density).toInt(), 0
|
||||
)
|
||||
val input = EditText(ctx)
|
||||
input.isSingleLine = true
|
||||
container.addView(input)
|
||||
return container to input
|
||||
private fun dialogEditText(ctx: Context): Pair<TextInputLayout, EditText> {
|
||||
val view = LayoutInflater.from(ctx).inflate(R.layout.dialog_textinput, null)
|
||||
return view as TextInputLayout to view.findViewById(R.id.input)
|
||||
}
|
||||
|
||||
fun showEditTextDialog(
|
||||
ctx: Context,
|
||||
customize: (MaterialAlertDialogBuilder, EditText) -> Unit
|
||||
customize: (MaterialAlertDialogBuilder, EditText) -> Unit,
|
||||
okAction: (String) -> Unit
|
||||
): AlertDialog {
|
||||
val (container, input) = dialogEditText(ctx)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(ctx)
|
||||
.setView(container)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> }
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
|
||||
customize(dialogBuilder, input)
|
||||
|
||||
val dialog = dialogBuilder.show()
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
|
||||
val okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
|
||||
// focus and show keyboard
|
||||
input.requestFocus()
|
||||
input.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
val text = input.text
|
||||
val button = dialog.getButton(DialogInterface.BUTTON_POSITIVE)
|
||||
if (text != null && button != null) {
|
||||
button.performClick()
|
||||
if (text != null && okButton != null) {
|
||||
okButton.performClick()
|
||||
return@setOnEditorActionListener true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
okButton?.setOnClickListener {
|
||||
if (input.text.isBlank()) {
|
||||
container.isErrorEnabled = true
|
||||
container.error = ctx.getString(R.string.required)
|
||||
} else {
|
||||
container.isErrorEnabled = false
|
||||
okAction(input.text.toString())
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
|
||||
class HideOnScrollFabBehavior(context: Context, attrs: AttributeSet) :
|
||||
FloatingActionButton.Behavior(context, attrs) {
|
||||
var hidden: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun from(view: View): HideOnScrollFabBehavior? {
|
||||
return ((view.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior as? HideOnScrollFabBehavior)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStartNestedScroll(
|
||||
coordinatorLayout: CoordinatorLayout,
|
||||
@@ -61,13 +68,13 @@ class HideOnScrollFabBehavior(context: Context, attrs: AttributeSet) :
|
||||
child: FloatingActionButton,
|
||||
dependency: View
|
||||
): Boolean {
|
||||
val behavior = BottomSheetBehaviorGoogleMapsLike.from<View>(dependency)
|
||||
val behavior = BottomSheetBehaviorGoogleMapsLike.from(dependency)
|
||||
when (behavior.state) {
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_SETTLING -> {
|
||||
|
||||
}
|
||||
BottomSheetBehaviorGoogleMapsLike.STATE_HIDDEN -> {
|
||||
child.show()
|
||||
if (!hidden) child.show()
|
||||
}
|
||||
else -> {
|
||||
child.hide()
|
||||
@@ -103,7 +110,7 @@ class HideOnScrollFabBehavior(context: Context, attrs: AttributeSet) :
|
||||
child.hide()
|
||||
} else if (dyConsumed < 0 && child.visibility != View.VISIBLE) {
|
||||
// User scrolled up and the FAB is currently not visible -> show the FAB
|
||||
child.show()
|
||||
if (!hidden) child.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,13 +148,9 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
MutableLiveData<Set<Long>>()
|
||||
}
|
||||
|
||||
val chargerSparse: MutableLiveData<ChargeLocation?> by lazy {
|
||||
state.getLiveData<ChargeLocation?>("chargerSparse").apply {
|
||||
observeForever {
|
||||
selectedChargepoint.value = null
|
||||
}
|
||||
}
|
||||
}
|
||||
val chargerSparse: MutableLiveData<ChargeLocation?> =
|
||||
state.getLiveData<ChargeLocation?>("chargerSparse")
|
||||
|
||||
private val triggerChargerDetailsRefresh = MutableLiveData(false)
|
||||
val chargerDetails: LiveData<Resource<ChargeLocation>> = chargerSparse.switchMap { charger ->
|
||||
triggerChargerDetailsRefresh.value = false
|
||||
@@ -173,6 +169,12 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
val selectedChargepoint: MutableLiveData<Chargepoint?> =
|
||||
state.getLiveData("selectedChargepoint")
|
||||
|
||||
init {
|
||||
chargerSparse.observeForever {
|
||||
selectedChargepoint.value = null
|
||||
}
|
||||
}
|
||||
|
||||
val charger: MediatorLiveData<Resource<ChargeLocation>> by lazy {
|
||||
MediatorLiveData<Resource<ChargeLocation>>().apply {
|
||||
addSource(chargerDetails) {
|
||||
|
||||
@@ -158,7 +158,6 @@
|
||||
android:textColor="@android:color/white"
|
||||
app:backgroundTintAvailability="@{BindingAdaptersKt.flatten(filteredAvailability.data.status.values())}"
|
||||
app:invisibleUnless="@{filteredAvailability.data != null && !expanded}"
|
||||
app:invisibleUnlessAnimated="@{filteredAvailability.data != null && !expanded}"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
app:layout_constraintTop_toTopOf="@+id/txtName"
|
||||
tools:backgroundTint="@color/available"
|
||||
|
||||
14
app/src/main/res/layout/dialog_textinput.xml
Normal file
14
app/src/main/res/layout/dialog_textinput.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.textfield.TextInputLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
@@ -38,7 +38,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:constraint_referenced_ids="referral_tesla,referral_juicify,referral_geldfuereauto,referral_maingau,referral_ewieeinfach"
|
||||
app:constraint_referenced_ids="referral_tesla,referral_juicify,referral_geldfuereauto,referral_maingau,referral_eprimo,referral_ewieeinfach"
|
||||
app:flow_horizontalGap="16dp"
|
||||
app:flow_horizontalStyle="packed"
|
||||
app:flow_verticalAlign="baseline"
|
||||
@@ -76,6 +76,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_maingau" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_eprimo"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_eprimo" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_ewieeinfach"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
|
||||
@@ -238,7 +238,8 @@
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:layout_marginTop="@dimen/layers_fab_top_padding"
|
||||
app:tint="?android:colorControlNormal"
|
||||
app:tint="?colorControlNormal"
|
||||
app:backgroundTint="?android:colorBackground"
|
||||
app:borderWidth="0dp"
|
||||
app:srcCompat="@drawable/ic_layers"
|
||||
app:layout_behavior="@string/hide_on_scroll_fab_behavior"
|
||||
@@ -261,4 +262,4 @@
|
||||
app:vm="@{vm}" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</layout>
|
||||
</layout>
|
||||
|
||||
1
app/src/main/res/resources.properties
Normal file
1
app/src/main/res/resources.properties
Normal file
@@ -0,0 +1 @@
|
||||
unqualifiedResLocale=en-US
|
||||
@@ -244,7 +244,7 @@
|
||||
<string name="average_utilization">Průměrné využití</string>
|
||||
<string name="website">Webové stránky</string>
|
||||
<string name="pref_map_scale">Zobrazit ovládání přiblížení mapy</string>
|
||||
<string name="pref_map_scale_meters_and_miles">Míle a metry na ovládání přiblížení mapy</string>
|
||||
<string name="pref_map_scale_meters_and_miles">Míle a metry na ukazateli měřítka</string>
|
||||
<string name="pref_units">Jednotky</string>
|
||||
<string name="pref_units_metric">Metrické</string>
|
||||
<string name="pref_units_imperial">Imperiální</string>
|
||||
@@ -380,4 +380,7 @@
|
||||
<string name="status_faulted">Mimo provoz</string>
|
||||
<string name="status_unknown">Stav neznámý</string>
|
||||
<string name="status_since">%1$s od %2$s</string>
|
||||
<string name="pref_chargeprice_native_integration">Porovnání cen v EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_on">Data o cenách budou zobrazena přímo v EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_off">Tlačítko porovnání cen bude odkazovat na aplikaci nebo web Chargeprice</string>
|
||||
</resources>
|
||||
@@ -375,4 +375,7 @@
|
||||
<string name="status_unknown">Status unbekannt</string>
|
||||
<string name="status_since">%1$s seit %2$s</string>
|
||||
<string name="charger_name">Ladestationsname</string>
|
||||
<string name="pref_chargeprice_native_integration">Preisvergleich in EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_on">Preise werden direkt in EVMap angezeigt</string>
|
||||
<string name="pref_chargeprice_native_integration_off">Preisvergleich verlinkt auf die App oder Website von Chargeprice</string>
|
||||
</resources>
|
||||
@@ -374,4 +374,13 @@
|
||||
<string name="powered_by_fronyx">previsão feita por fronyx</string>
|
||||
<string name="copied">Informação copiada</string>
|
||||
<string name="charger_name">Nome do carregador</string>
|
||||
<string name="status_available">Disponível</string>
|
||||
<string name="status_occupied">Ocupado</string>
|
||||
<string name="status_charging">Carregando</string>
|
||||
<string name="status_faulted">Fora de serviço</string>
|
||||
<string name="status_since">%1$s desde %2$s</string>
|
||||
<string name="status_unknown">Estado Desconhecido</string>
|
||||
<string name="pref_chargeprice_native_integration">Comparação de preços no EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_on">Os preços serão exibidos diretamente no EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_off">O botão de comparação de preços abrirá a app ou site do Chargeprice</string>
|
||||
</resources>
|
||||
@@ -40,10 +40,12 @@
|
||||
<string name="geldfuereauto_referral_link" translatable="false">https://trck.geld-fuer-eauto.de/trck/eclick/c4713e9520bdb8842a3f1fbfa3a0669b3e58421043df78ad</string>
|
||||
<string name="maingau_referral_link" translatable="false">https://trck.maingau-energie.de/trck/eclick/799b39cda39575dab1dcd3351abeb77b62dc33e4f9558a57</string>
|
||||
<string name="ewieeinfach_referral_link" translatable="false">https://trck.e-wie-einfach.de/trck/eclick/fca74c186b54e7287a62102a13e073be4fc963825b85f7df</string>
|
||||
<string name="eprimo_referral_link" translatable="false">https://netzwerk.uppr.de/trck/eclick/781768d2e779806b5e09229932662c14adddd69323594c52</string>
|
||||
<string name="referral_juicify" translatable="false">Juicify</string>
|
||||
<string name="referral_geldfuereauto" translatable="false">Geld für eAuto</string>
|
||||
<string name="referral_maingau" translatable="false">Maingau</string>
|
||||
<string name="referral_ewieeinfach" translatable="false">E wie einfach</string>
|
||||
<string name="copyright_summary">©2020–2023 Johan von Forstner and contributors</string>
|
||||
<string name="referral_eprimo" translatable="false">eprimo</string>
|
||||
<string name="copyright_summary">©2020–2024 Johan von Forstner and contributors</string>
|
||||
<string name="acra_backend_url" translatable="false">https://acra.muc.vonforst.net/report</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -375,4 +375,7 @@
|
||||
<string name="status_unknown">Status Unknown</string>
|
||||
<string name="status_since">%1$s since %2$s</string>
|
||||
<string name="charger_name">Charger name</string>
|
||||
<string name="pref_chargeprice_native_integration">Price comparison within EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_on">Pricing data will be shown directly in EVMap</string>
|
||||
<string name="pref_chargeprice_native_integration_off">Price comparison button will refer to the Chargeprice app or website</string>
|
||||
</resources>
|
||||
@@ -15,6 +15,7 @@
|
||||
<item name="preferenceTheme">@style/AppTheme.Preference</item>
|
||||
<item name="alertDialogTheme">@style/AppTheme.AlertDialog</item>
|
||||
<item name="materialAlertDialogTheme">@style/AppTheme.AlertDialog</item>
|
||||
<item name="snackbarButtonStyle">@style/Button.TextButton.Snackbar.App</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Preference" parent="@style/PreferenceThemeOverlay">
|
||||
@@ -82,6 +83,10 @@
|
||||
<item name="backgroundInsetBottom">24dp</item>
|
||||
</style>
|
||||
|
||||
<style name="Button.TextButton.Snackbar.App" parent="Widget.Material3.Button.TextButton.Snackbar">
|
||||
<item name="android:textColor">@color/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="CarAppTheme">
|
||||
<item name="carColorPrimary">@color/colorPrimary</item>
|
||||
<item name="carColorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en" />
|
||||
<locale android:name="de" />
|
||||
<locale android:name="fr" />
|
||||
<locale android:name="nb-NO" />
|
||||
</locale-config>
|
||||
@@ -1,6 +1,12 @@
|
||||
<?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">
|
||||
<CheckBoxPreference
|
||||
android:key="chargeprice_native_integration"
|
||||
android:title="@string/pref_chargeprice_native_integration"
|
||||
android:summaryOn="@string/pref_chargeprice_native_integration_on"
|
||||
android:summaryOff="@string/pref_chargeprice_native_integration_off"
|
||||
app:defaultValue="true" />
|
||||
<net.vonforst.evmap.ui.MultiSelectDialogPreference
|
||||
android:key="chargeprice_my_vehicle"
|
||||
android:title="@string/pref_my_vehicle"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
(e.g. in the debug version). -->
|
||||
<intent
|
||||
android:action="android.intent.action.VIEW"
|
||||
android:targetPackage="${applicationId}"
|
||||
android:targetPackage="net.vonforst.evmap"
|
||||
android:targetClass="net.vonforst.evmap.MapsActivity">
|
||||
<extra
|
||||
android:name="favorites"
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -67,7 +67,7 @@ class NewMotionAvailabilityDetectorTest {
|
||||
fun apiTest() {
|
||||
for (chargepoint in listOf(2105L, 18284L)) {
|
||||
val charger = runBlocking { api.getChargepointDetail(chargepoint).body()!! }
|
||||
.chargelocations[0].convert("", true) as ChargeLocation
|
||||
.chargelocations!![0].convert("", true) as ChargeLocation
|
||||
println(charger)
|
||||
|
||||
runBlocking {
|
||||
|
||||
@@ -60,7 +60,7 @@ class ChargepriceApiTest {
|
||||
fun apiTest() {
|
||||
for (chargepoint in listOf(2105L, 18284L)) {
|
||||
val charger = runBlocking { ge.getChargepointDetail(chargepoint).body()!! }
|
||||
.chargelocations[0].convert("", true) as ChargeLocation
|
||||
.chargelocations!![0].convert("", true) as ChargeLocation
|
||||
println(charger)
|
||||
|
||||
runBlocking {
|
||||
|
||||
@@ -63,8 +63,8 @@ class GoingElectricApiTest {
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
assertEquals(null, body.startkey)
|
||||
assertEquals(1, body.chargelocations.size)
|
||||
val charger = body.chargelocations[0] as GEChargeLocation
|
||||
assertEquals(1, body.chargelocations!!.size)
|
||||
val charger = body.chargelocations!![0] as GEChargeLocation
|
||||
assertEquals(2105, charger.id)
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ class GoingElectricApiTest {
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
assertEquals(null, body.startkey)
|
||||
assertEquals(1, body.chargelocations.size)
|
||||
val charger = body.chargelocations[0] as GEChargeLocation
|
||||
assertEquals(1, body.chargelocations!!.size)
|
||||
val charger = body.chargelocations!![0] as GEChargeLocation
|
||||
assertEquals(34210, charger.id)
|
||||
assertEquals(LocalTime.MIN, charger.openinghours!!.days!!.monday.start)
|
||||
assertEquals(LocalTime.MAX, charger.openinghours!!.days!!.monday.end)
|
||||
@@ -92,8 +92,8 @@ class GoingElectricApiTest {
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
assertEquals(null, body.startkey)
|
||||
assertEquals(2, body.chargelocations.size)
|
||||
val charger = body.chargelocations[0] as GEChargeLocation
|
||||
assertEquals(2, body.chargelocations!!.size)
|
||||
val charger = body.chargelocations!![0] as GEChargeLocation
|
||||
assertEquals(41161, charger.id)
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class GoingElectricApiTest {
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
assertEquals(null, body.startkey)
|
||||
assertEquals(0, body.chargelocations.size)
|
||||
assertEquals(0, body.chargelocations!!.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -118,8 +118,8 @@ class GoingElectricApiTest {
|
||||
val body = response.body()!!
|
||||
assertEquals("ok", body.status)
|
||||
assertEquals(2, body.startkey)
|
||||
assertEquals(2, body.chargelocations.size)
|
||||
val charger = body.chargelocations[0] as GEChargeLocation
|
||||
assertEquals(2, body.chargelocations!!.size)
|
||||
val charger = body.chargelocations!![0] as GEChargeLocation
|
||||
assertEquals(41161, charger.id)
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ buildscript {
|
||||
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")
|
||||
classpath("pt.jcosta.resourceplaceholders:plugin:0.7")
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
@@ -27,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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
fastlane/metadata/android/de-DE/changelogs/212.txt
Normal file
6
fastlane/metadata/android/de-DE/changelogs/212.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Verbesserungen:
|
||||
- Option "Sofort navigieren" wird nur angezeigt, wenn kompatible Navigationsapp installiert ist
|
||||
|
||||
Fehler behoben:
|
||||
- OpenChargeMap: Korrektur des Verhaltens der Kartenmarker bei Filter nach Betreiber
|
||||
- Abstürze behoben
|
||||
6
fastlane/metadata/android/en-US/changelogs/212.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/212.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
Improvements:
|
||||
- Option "Immediate navigation" will only be shown if compatible navigation app is installed
|
||||
|
||||
Bugfixes:
|
||||
- OpenChargeMap: Fixed strange map marker behavior when filtering by operator
|
||||
- Fixed crashes
|
||||
Reference in New Issue
Block a user