mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-24 07:37:46 -05:00
Compare commits
53 Commits
openstreet
...
1.9.16
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8f7d77a36 | ||
|
|
d03cf70499 | ||
|
|
7a6bebd143 | ||
|
|
66d68ca68e | ||
|
|
772885a8eb | ||
|
|
6b07ce012a | ||
|
|
29dbc202d8 | ||
|
|
cf8371d095 | ||
|
|
01cb551cbc | ||
|
|
45fe297616 | ||
|
|
32cabefe7d | ||
|
|
9ff8329171 | ||
|
|
e9b70a2f00 | ||
|
|
c4c3aba7c7 | ||
|
|
890af2ddef | ||
|
|
ba0b36b3ec | ||
|
|
161b48789f | ||
|
|
042b983aa3 | ||
|
|
1c21da7be0 | ||
|
|
405baed0f7 | ||
|
|
19c0d57f2b | ||
|
|
42c2a2f72a | ||
|
|
36ee3ff231 | ||
|
|
883735ef05 | ||
|
|
4c68356ae9 | ||
|
|
7fde5b50aa | ||
|
|
7c4136c66d | ||
|
|
6e56f5c3ff | ||
|
|
017be6f31a | ||
|
|
b398a5dc81 | ||
|
|
3fb0dec868 | ||
|
|
8c4de115ec | ||
|
|
334b68cf5e | ||
|
|
788c68c9dd | ||
|
|
7842a15529 | ||
|
|
e7c9432191 | ||
|
|
76b6abd3ca | ||
|
|
752c184146 | ||
|
|
5471ac5073 | ||
|
|
69ae13a199 | ||
|
|
8a2e2d9a25 | ||
|
|
fe69a78b94 | ||
|
|
2663bd7964 | ||
|
|
3b54b2799f | ||
|
|
3a24711626 | ||
|
|
c158744bc2 | ||
|
|
c01033a036 | ||
|
|
16474c3864 | ||
|
|
7ce2f8d452 | ||
|
|
28df158d94 | ||
|
|
90b3645a0b | ||
|
|
de901aa825 | ||
|
|
2ce61f2f6b |
52
.github/workflows/tests.yml
vendored
52
.github/workflows/tests.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Copy apikeys.xml
|
||||
run: cp .github/workflows/apikeys-ci.xml app/src/main/res/values/apikeys.xml
|
||||
run: cp _ci/apikeys-ci.xml app/src/main/res/values/apikeys.xml
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assemble${{ matrix.buildvariant }}Debug --no-daemon
|
||||
@@ -36,3 +36,53 @@ jobs:
|
||||
run: ./gradlew lint${{ matrix.buildvariant }}Debug --no-daemon
|
||||
- name: Check licenses
|
||||
run: ./gradlew exportLibraryDefinitions --no-daemon
|
||||
|
||||
apk_check:
|
||||
name: Release APK checks (${{ matrix.buildvariant }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
buildvariant: [ FossNormal, FossAutomotive, GoogleNormal, GoogleAutomotive ]
|
||||
|
||||
steps:
|
||||
- name: Install checksec
|
||||
run: sudo apt install -y checksec
|
||||
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java environment
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: 17
|
||||
distribution: 'zulu'
|
||||
cache: 'gradle'
|
||||
|
||||
- name: Copy apikeys.xml
|
||||
run: cp _ci/apikeys-ci.xml app/src/main/res/values/apikeys.xml
|
||||
|
||||
- name: Build app
|
||||
run: ./gradlew assemble${{ matrix.buildvariant }}Release --no-daemon
|
||||
|
||||
- name: Unpack native libraries from APK
|
||||
run: |
|
||||
VARIANT_FILENAME=$(echo ${{ matrix.buildvariant }} | sed -E 's/([a-z])([A-Z])/\1-\2/g' | tr 'A-Z' 'a-z')
|
||||
VARIANT_FOLDER=$(echo ${{ matrix.buildvariant }} | sed -E 's/^([A-Z])/\L\1/')
|
||||
APK_FILE="app/build/outputs/apk/$VARIANT_FOLDER/release/app-$VARIANT_FILENAME-release-unsigned.apk"
|
||||
unzip $APK_FILE "lib/*"
|
||||
|
||||
- name: Run checksec on native libraries
|
||||
run: |
|
||||
checksec --output=json --dir=lib > checksec_output.json
|
||||
jq --argjson exceptions '[
|
||||
"lib/armeabi-v7a/libc++_shared.so",
|
||||
"lib/x86/libc++_shared.so"
|
||||
]' '
|
||||
to_entries
|
||||
| map(select(.value.fortify_source == "no" and (.key as $lib | $exceptions | index($lib) | not)))
|
||||
| if length > 0 then
|
||||
error("The following libraries do not have fortify enabled (and are not in the exception list): " + (map(.key) | join(", ")))
|
||||
else
|
||||
"All libraries have fortify enabled or are in the exception list."
|
||||
end
|
||||
' checksec_output.json
|
||||
|
||||
@@ -92,8 +92,4 @@ free, i.e. the background map displayed in the app if OpenStreetMap is selected
|
||||
|
||||
<a href="https://chargeprice.app"><img src="https://raw.githubusercontent.com/ev-map/EVMap/master/_img/powered_by_chargeprice.svg" alt="Powered by Chargeprice" height="58"/></a><br>
|
||||
Since April 2021, **Chargeprice.app** provide their price comparison API at a greatly reduced
|
||||
price for EVMap. This data is used in EVMap's price comparison feature.
|
||||
|
||||
<a href="https://fronyx.io/"><img src="https://github.com/ev-map/EVMap/blob/master/_img/powered_by_fronyx.svg" alt="Powered by Fronyx" height="68"/></a><br>
|
||||
Since September 2022, for certain charging stations, **Fronyx** provide us free access to their API
|
||||
for availability predictions.
|
||||
price for EVMap. This data is used in EVMap's price comparison feature.
|
||||
@@ -20,16 +20,18 @@ android {
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
|
||||
versionCode = 230
|
||||
versionName = "1.9.6"
|
||||
versionCode = 252
|
||||
versionName = "1.9.16"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
val isRunningOnCI = System.getenv("CI") == "true"
|
||||
val isCIKeystoreAvailable = System.getenv("KEYSTORE_PASSWORD") != null
|
||||
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
val isRunningOnCI = System.getenv("CI") == "true"
|
||||
if (isRunningOnCI) {
|
||||
if (isRunningOnCI && isCIKeystoreAvailable) {
|
||||
// configure keystore
|
||||
storeFile = file("../_ci/keystore.jks")
|
||||
storePassword = System.getenv("KEYSTORE_PASSWORD")
|
||||
@@ -46,7 +48,11 @@ android {
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
signingConfig = signingConfigs.getByName("release")
|
||||
signingConfig = if (isRunningOnCI && !isCIKeystoreAvailable) {
|
||||
null
|
||||
} else {
|
||||
signingConfigs.getByName("release")
|
||||
}
|
||||
}
|
||||
create("releaseAutomotivePackageName") {
|
||||
// Faurecia Aptoide requires the automotive variant to use a separate package name
|
||||
@@ -257,7 +263,7 @@ aboutLibraries {
|
||||
"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
|
||||
"Unicode/ICU License", "Unicode-3.0", // icu4j
|
||||
"Bouncy Castle Licence", // bcprov
|
||||
"CDDL + GPLv2 with classpath exception", // javax.annotation-api
|
||||
)
|
||||
@@ -283,10 +289,11 @@ dependencies {
|
||||
implementation("androidx.cardview:cardview:1.0.0")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.browser:browser:1.8.0")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.viewpager2:viewpager2:1.1.0")
|
||||
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
implementation("com.github.ev-map:CustomBottomSheetBehavior:e48f73ea7b")
|
||||
@@ -309,13 +316,13 @@ dependencies {
|
||||
implementation("com.github.erfansn:locale-config-x:1.0.1")
|
||||
|
||||
// Android Auto
|
||||
val carAppVersion = "1.4.0"
|
||||
val carAppVersion = "1.7.0-rc01"
|
||||
implementation("androidx.car.app:app:$carAppVersion")
|
||||
normalImplementation("androidx.car.app:app-projected:$carAppVersion")
|
||||
automotiveImplementation("androidx.car.app:app-automotive:$carAppVersion")
|
||||
|
||||
// AnyMaps
|
||||
val anyMapsVersion = "3e6c71410f"
|
||||
val anyMapsVersion = "a3290b148d"
|
||||
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:19.0.0")
|
||||
@@ -323,7 +330,7 @@ dependencies {
|
||||
// duplicates classes from mapbox-sdk-services
|
||||
exclude("org.maplibre.gl", "android-sdk-geojson")
|
||||
}
|
||||
implementation("org.maplibre.gl:android-sdk:10.3.2-pre3") {
|
||||
implementation("org.maplibre.gl:android-sdk:10.3.4") {
|
||||
exclude("org.maplibre.gl", "android-sdk-geojson")
|
||||
}
|
||||
|
||||
@@ -348,7 +355,12 @@ 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.3.0")
|
||||
implementation("com.github.anboralabs:spatia-room:0.3.0") {
|
||||
exclude("com.github.dalgarins", "android-spatialite")
|
||||
}
|
||||
// forked version with upgraded sqlite & libxml
|
||||
// https://github.com/dalgarins/android-spatialite/pull/10
|
||||
implementation("com.github.ev-map:android-spatialite:31495dcd81")
|
||||
|
||||
// billing library
|
||||
val billing_version = "7.0.0"
|
||||
@@ -365,6 +377,8 @@ 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("com.jakewharton.timber:timber:5.0.1")
|
||||
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
|
||||
|
||||
// testing
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.facebook.flipper.plugins.network.NetworkFlipperPlugin
|
||||
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin
|
||||
import com.facebook.soloader.SoLoader
|
||||
import okhttp3.OkHttpClient
|
||||
import timber.log.Timber
|
||||
|
||||
private val networkFlipperPlugin = NetworkFlipperPlugin()
|
||||
|
||||
@@ -24,6 +25,8 @@ fun addDebugInterceptors(context: Context) {
|
||||
client.addPlugin(DatabasesFlipperPlugin(context))
|
||||
client.addPlugin(SharedPreferencesFlipperPlugin(context))
|
||||
client.start()
|
||||
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
|
||||
fun OkHttpClient.Builder.addDebugInterceptors(): OkHttpClient.Builder {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">EVMap (debug)</string>
|
||||
<string name="app_name">EVMap</string>
|
||||
</resources>
|
||||
@@ -5,7 +5,6 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
@@ -43,7 +42,7 @@ class DonateFragment : DonateFragmentBase() {
|
||||
)
|
||||
|
||||
binding.btnDonate.setOnClickListener {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.paypal_link))
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.paypal_link), binding.root)
|
||||
}
|
||||
|
||||
setupReferrals(referrals)
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
<intent>
|
||||
<action android:name="android.support.customtabs.action.CustomTabsService" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
|
||||
<package android:name="com.google.android.projection.gearhead" />
|
||||
<package android:name="com.google.android.apps.automotive.templates.host" />
|
||||
|
||||
@@ -3,6 +3,8 @@ package net.vonforst.evmap
|
||||
import android.app.PendingIntent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ResolveInfo
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
@@ -11,7 +13,6 @@ import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||
import androidx.browser.customtabs.CustomTabsClient
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.splashscreen.SplashScreen
|
||||
@@ -44,14 +45,11 @@ const val EXTRA_DONATE = "donate"
|
||||
|
||||
class MapsActivity : AppCompatActivity(),
|
||||
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
interface FragmentCallback {
|
||||
fun getRootView(): View
|
||||
}
|
||||
|
||||
private var reenterState: Bundle? = null
|
||||
private lateinit var navController: NavController
|
||||
private lateinit var navHostFragment: NavHostFragment
|
||||
lateinit var appBarConfiguration: AppBarConfiguration
|
||||
var fragmentCallback: FragmentCallback? = null
|
||||
private lateinit var prefs: PreferenceDataSource
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@@ -70,7 +68,7 @@ class MapsActivity : AppCompatActivity(),
|
||||
),
|
||||
drawerLayout
|
||||
)
|
||||
val navHostFragment =
|
||||
navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
||||
navController = navHostFragment.navController
|
||||
val navGraph = navController.navInflater.inflate(R.navigation.nav_graph)
|
||||
@@ -237,7 +235,7 @@ class MapsActivity : AppCompatActivity(),
|
||||
deepLink?.send()
|
||||
}
|
||||
|
||||
fun navigateTo(charger: ChargeLocation) {
|
||||
fun navigateTo(charger: ChargeLocation, rootView: View) {
|
||||
// google maps navigation
|
||||
val coord = charger.coordinates
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
@@ -247,11 +245,11 @@ class MapsActivity : AppCompatActivity(),
|
||||
startActivity(intent)
|
||||
} else {
|
||||
// fallback: generic geo intent
|
||||
showLocation(charger)
|
||||
showLocation(charger, rootView)
|
||||
}
|
||||
}
|
||||
|
||||
fun showLocation(charger: ChargeLocation) {
|
||||
fun showLocation(charger: ChargeLocation, rootView: View) {
|
||||
val coord = charger.coordinates
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(
|
||||
@@ -259,20 +257,33 @@ class MapsActivity : AppCompatActivity(),
|
||||
Uri.encode(charger.name)
|
||||
})"
|
||||
)
|
||||
if (intent.resolveActivity(packageManager) != null) {
|
||||
|
||||
val resolveInfo =
|
||||
packageManager.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
val pkg =
|
||||
resolveInfo?.activityInfo?.packageName.takeIf { it != "android" && it != packageName }
|
||||
if (pkg == null) {
|
||||
// There is no default maps app or EVMap itself is the current default, fall back to app chooser
|
||||
val chooserIntent = Intent.createChooser(intent, null).apply {
|
||||
putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(componentName))
|
||||
}
|
||||
startActivity(chooserIntent)
|
||||
return
|
||||
}
|
||||
intent.setPackage(pkg)
|
||||
|
||||
try {
|
||||
startActivity(intent)
|
||||
} else {
|
||||
val cb = fragmentCallback ?: return
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Snackbar.make(
|
||||
cb.getRootView(),
|
||||
rootView,
|
||||
R.string.no_maps_app_found,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
fun openUrl(url: String, preferBrowser: Boolean = true) {
|
||||
val pkg = CustomTabsClient.getPackageName(this, null)
|
||||
fun openUrl(url: String, rootView: View, preferBrowser: Boolean = true) {
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
CustomTabColorSchemeParams.Builder()
|
||||
@@ -280,17 +291,49 @@ class MapsActivity : AppCompatActivity(),
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
pkg?.let {
|
||||
// prefer to open URL in custom tab, even if native app
|
||||
// available (such as EVMap itself)
|
||||
if (preferBrowser) intent.intent.setPackage(pkg)
|
||||
|
||||
val uri = Uri.parse(url)
|
||||
val viewIntent = Intent(Intent.ACTION_VIEW, uri)
|
||||
if (preferBrowser) {
|
||||
// EVMap may be set as default app for this link, but we want to open it in a browser
|
||||
// try to find default web browser
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://"))
|
||||
val resolveInfo =
|
||||
packageManager.resolveActivity(browserIntent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
val pkg = resolveInfo?.activityInfo?.packageName.takeIf { it != "android" }
|
||||
if (pkg == null) {
|
||||
// There is no default browser, fall back to app chooser
|
||||
val chooserIntent = Intent.createChooser(viewIntent, null).apply {
|
||||
putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(componentName))
|
||||
}
|
||||
val targets: List<ResolveInfo> = packageManager.queryIntentActivities(
|
||||
viewIntent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY
|
||||
)
|
||||
|
||||
// add missing browsers (if EVMap is already set as default, Android might not find other browsers with the specific intent)
|
||||
val browsers = packageManager.queryIntentActivities(
|
||||
browserIntent,
|
||||
PackageManager.MATCH_DEFAULT_ONLY
|
||||
)
|
||||
val extraIntents = browsers.filter { browser ->
|
||||
targets.find { it.activityInfo.packageName == browser.activityInfo.packageName } == null
|
||||
}.map { browser ->
|
||||
Intent(Intent.ACTION_VIEW, uri).apply {
|
||||
setPackage(browser.activityInfo.packageName)
|
||||
}
|
||||
}
|
||||
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toTypedArray())
|
||||
startActivity(chooserIntent)
|
||||
return
|
||||
}
|
||||
intent.intent.setPackage(pkg)
|
||||
}
|
||||
try {
|
||||
intent.launchUrl(this, Uri.parse(url))
|
||||
intent.launchUrl(this, uri)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
val cb = fragmentCallback ?: return
|
||||
Snackbar.make(
|
||||
cb.getRootView(),
|
||||
rootView,
|
||||
R.string.no_browser_app_found,
|
||||
Snackbar.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
@@ -16,6 +16,8 @@ import android.text.SpannableStringBuilder
|
||||
import android.text.SpannedString
|
||||
import android.text.TextUtils
|
||||
import android.text.style.StyleSpan
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import java.util.Currency
|
||||
import java.util.Locale
|
||||
@@ -142,4 +144,12 @@ fun PackageManager.isAppInstalled(packageName: String): Boolean {
|
||||
}
|
||||
}
|
||||
|
||||
fun currencyDisplayName(code: String) = "${Currency.getInstance(code).displayName} ($code)"
|
||||
fun currencyDisplayName(code: String) = "${Currency.getInstance(code).displayName} ($code)"
|
||||
|
||||
inline fun View.waitForLayout(crossinline f: () -> Unit) =
|
||||
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
f()
|
||||
}
|
||||
})
|
||||
@@ -5,7 +5,6 @@ import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
@@ -14,6 +13,7 @@ import coil.load
|
||||
import coil.memory.MemoryCache
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.model.ChargerPhoto
|
||||
import net.vonforst.evmap.waitForLayout
|
||||
|
||||
|
||||
class GalleryAdapter(context: Context, val itemClickListener: ItemClickListener? = null) :
|
||||
@@ -39,12 +39,9 @@ class GalleryAdapter(context: Context, val itemClickListener: ItemClickListener?
|
||||
val item = getItem(position)
|
||||
|
||||
if (holder.view.height == 0) {
|
||||
holder.view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
|
||||
override fun onGlobalLayout() {
|
||||
holder.view.viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||
loadImage(item, holder)
|
||||
}
|
||||
})
|
||||
holder.view.waitForLayout {
|
||||
loadImage(item, holder)
|
||||
}
|
||||
} else {
|
||||
loadImage(item, holder)
|
||||
}
|
||||
|
||||
@@ -45,14 +45,20 @@ interface LocationAwareScreen {
|
||||
class CarAppService : androidx.car.app.CarAppService() {
|
||||
private val CHANNEL_ID = "car_location"
|
||||
private val NOTIFICATION_ID = 1000
|
||||
private val TAG = "CarAppService"
|
||||
private var foregroundStarted = false
|
||||
|
||||
fun ensureForegroundService() {
|
||||
// we want to run as a foreground service to make sure we can use location
|
||||
if (!foregroundStarted) {
|
||||
createNotificationChannel()
|
||||
startForeground(NOTIFICATION_ID, getNotification())
|
||||
foregroundStarted = true
|
||||
try {
|
||||
if (!foregroundStarted) {
|
||||
createNotificationChannel()
|
||||
startForeground(NOTIFICATION_ID, getNotification())
|
||||
foregroundStarted = true
|
||||
Log.i(TAG, "Started foreground service")
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.w(TAG, "Failed to start foreground service: ", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,7 +162,7 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
}
|
||||
if (!prefs.privacyAccepted) {
|
||||
screens.add(
|
||||
AcceptPrivacyScreen(carContext)
|
||||
AcceptPrivacyScreen(carContext, this)
|
||||
)
|
||||
}
|
||||
handleACRAIntent(intent)?.let {
|
||||
|
||||
@@ -3,13 +3,22 @@ package net.vonforst.evmap.auto
|
||||
import androidx.car.app.CarContext
|
||||
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.hardware.CarHardwareManager
|
||||
import androidx.car.app.hardware.info.Model
|
||||
import androidx.car.app.model.*
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.ActionStrip
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.ItemList
|
||||
import androidx.car.app.model.ListTemplate
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.SectionedItemList
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.github.erfansn.localeconfigx.currentOrDefaultLocale
|
||||
import jsonapi.Meta
|
||||
import jsonapi.Relationship
|
||||
import jsonapi.Relationships
|
||||
@@ -18,7 +27,16 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.api.chargeprice.ChargePrice
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceCar
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceChargepointMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceInclude
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceOptions
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceRequest
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceRequestTariffMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceStation
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
import net.vonforst.evmap.api.nameForPlugType
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
@@ -32,7 +50,9 @@ import retrofit2.HttpException
|
||||
import java.io.IOException
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(ctx) {
|
||||
@ExperimentalCarApi
|
||||
class ChargepriceScreen(ctx: CarContext, val session: EVMapSession, val charger: ChargeLocation) :
|
||||
Screen(ctx) {
|
||||
private val prefs = PreferenceDataSource(ctx)
|
||||
private val db = AppDatabase.getInstance(carContext)
|
||||
private val api by lazy {
|
||||
@@ -70,7 +90,7 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
carContext.stringProvider(),
|
||||
chargepoint.type
|
||||
)
|
||||
} ${chargepoint.formatPower()} ${
|
||||
} ${chargepoint.formatPower(carContext.currentOrDefaultLocale)} ${
|
||||
carContext.getString(
|
||||
R.string.chargeprice_stats,
|
||||
meta.energy,
|
||||
@@ -130,7 +150,7 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
|
||||
)
|
||||
).build()
|
||||
).setOnClickListener {
|
||||
openUrl(carContext, ChargepriceApi.getPoiUrl(charger))
|
||||
openUrl(carContext, session.cas, ChargepriceApi.getPoiUrl(charger))
|
||||
}.build()
|
||||
).build()
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
import androidx.car.app.HostException
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.annotations.ExperimentalCarApi
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.ActionStrip
|
||||
@@ -34,6 +35,7 @@ import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import com.github.erfansn.localeconfigx.currentOrDefaultLocale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -51,14 +53,11 @@ import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.api.availability.tesla.Pricing
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.createApi
|
||||
import net.vonforst.evmap.api.fronyx.FronyxApi
|
||||
import net.vonforst.evmap.api.fronyx.PredictionData
|
||||
import net.vonforst.evmap.api.fronyx.PredictionRepository
|
||||
import net.vonforst.evmap.api.iconForPlugType
|
||||
import net.vonforst.evmap.api.nameForPlugType
|
||||
import net.vonforst.evmap.api.stringProvider
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Coordinate
|
||||
import net.vonforst.evmap.model.Cost
|
||||
import net.vonforst.evmap.model.FaultReport
|
||||
import net.vonforst.evmap.model.Favorite
|
||||
@@ -79,7 +78,12 @@ import kotlin.math.roundToInt
|
||||
|
||||
private const val TAG = "ChargerDetailScreen"
|
||||
|
||||
class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) : Screen(ctx) {
|
||||
@ExperimentalCarApi
|
||||
class ChargerDetailScreen(
|
||||
ctx: CarContext,
|
||||
val chargerSparse: ChargeLocation,
|
||||
val session: EVMapSession
|
||||
) : Screen(ctx) {
|
||||
var charger: ChargeLocation? = null
|
||||
var photo: Bitmap? = null
|
||||
private var availability: ChargeLocationStatus? = null
|
||||
@@ -92,7 +96,8 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
private val repo =
|
||||
ChargeLocationsRepository(createApi(prefs.dataSource, ctx), lifecycleScope, db, prefs)
|
||||
private val availabilityRepo = AvailabilityRepository(ctx)
|
||||
private val predictionRepo = PredictionRepository(ctx)
|
||||
|
||||
//private val predictionRepo = PredictionRepository(ctx)
|
||||
private val timeFormat = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT)
|
||||
|
||||
private val imageSize = 128 // images should be 128dp according to docs
|
||||
@@ -155,14 +160,20 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
.setTitle(carContext.getString(R.string.auto_prices))
|
||||
.setOnClickListener {
|
||||
if (prefs.chargepriceNativeIntegration) {
|
||||
screenManager.push(ChargepriceScreen(carContext, charger))
|
||||
screenManager.push(
|
||||
ChargepriceScreen(
|
||||
carContext,
|
||||
session,
|
||||
charger
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val intent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(ChargepriceApi.getPoiUrl(charger))
|
||||
)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
carContext.startActivity(intent)
|
||||
session.cas.startActivity(intent)
|
||||
}
|
||||
}
|
||||
.build())
|
||||
@@ -181,12 +192,12 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.open_in_app))
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
val intent = Intent(carContext, MapsActivity::class.java)
|
||||
val intent = Intent(session.cas, MapsActivity::class.java)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(EXTRA_CHARGER_ID, chargerSparse.id)
|
||||
.putExtra(EXTRA_LAT, chargerSparse.coordinates.lat)
|
||||
.putExtra(EXTRA_LON, chargerSparse.coordinates.lng)
|
||||
carContext.startActivity(intent)
|
||||
session.cas.startActivity(intent)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.opened_on_phone,
|
||||
@@ -513,7 +524,7 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
} else {
|
||||
append(nameForPlugType(carContext.stringProvider(), cp.type))
|
||||
}
|
||||
cp.formatPower()?.let {
|
||||
cp.formatPower(carContext.currentOrDefaultLocale)?.let {
|
||||
append(" ")
|
||||
append(it)
|
||||
}
|
||||
@@ -547,10 +558,13 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
}
|
||||
|
||||
private fun navigateToCharger(charger: ChargeLocation) {
|
||||
val success = navigateCarApp(charger)
|
||||
var success = navigateCarApp(charger)
|
||||
if (!success && BuildConfig.FLAVOR_automotive == "automotive") {
|
||||
// on AAOS, some OEMs' navigation apps might not support
|
||||
navigateRegularApp(charger)
|
||||
success = navigateRegularApp(charger)
|
||||
}
|
||||
if (!success) {
|
||||
CarToast.makeText(carContext, R.string.no_maps_app_found, CarToast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +598,7 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
Uri.encode(charger.name)
|
||||
})"
|
||||
)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
if (intent.resolveActivity(carContext.packageManager) != null) {
|
||||
carContext.startActivity(intent)
|
||||
return true
|
||||
@@ -645,12 +660,12 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
)
|
||||
this@ChargerDetailScreen.photo = outImg
|
||||
}
|
||||
fronyxSupported = charger.chargepoints.any {
|
||||
fronyxSupported = false /*charger.chargepoints.any {
|
||||
FronyxApi.isChargepointSupported(
|
||||
charger,
|
||||
it
|
||||
)
|
||||
} && !availabilityRepo.isSupercharger(charger)
|
||||
} && !availabilityRepo.isSupercharger(charger)*/
|
||||
teslaSupported = availabilityRepo.isTeslaSupported(charger)
|
||||
|
||||
invalidate()
|
||||
@@ -659,7 +674,7 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
|
||||
invalidate()
|
||||
|
||||
prediction = predictionRepo.getPredictionData(charger, availability)
|
||||
//prediction = predictionRepo.getPredictionData(charger, availability)
|
||||
|
||||
invalidate()
|
||||
} else {
|
||||
|
||||
@@ -15,14 +15,34 @@ import androidx.car.app.hardware.info.CarInfo
|
||||
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.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.CarLocation
|
||||
import androidx.car.app.model.CarText
|
||||
import androidx.car.app.model.DistanceSpan
|
||||
import androidx.car.app.model.ForegroundCarColorSpan
|
||||
import androidx.car.app.model.ItemList
|
||||
import androidx.car.app.model.Metadata
|
||||
import androidx.car.app.model.OnContentRefreshListener
|
||||
import androidx.car.app.model.Place
|
||||
import androidx.car.app.model.PlaceListMapTemplate
|
||||
import androidx.car.app.model.PlaceMarker
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.car2go.maps.model.LatLng
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.AvailabilityRepository
|
||||
@@ -386,7 +406,7 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
)
|
||||
|
||||
setOnClickListener {
|
||||
screenManager.push(ChargerDetailScreen(carContext, charger))
|
||||
screenManager.push(ChargerDetailScreen(carContext, charger, session))
|
||||
session.mapScreen = null
|
||||
}
|
||||
}.build()
|
||||
@@ -472,13 +492,13 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
zoom = 16f,
|
||||
filtersWithValue
|
||||
).awaitFinished()
|
||||
if (response.status == Status.ERROR && if (radius == radiusValues.last()) response.data.isNullOrEmpty() else response.data == null) {
|
||||
if (response.status == Status.ERROR && if (radius == radiusValues.last()) response.data?.items.isNullOrEmpty() else response.data == null) {
|
||||
loadingError = true
|
||||
this@MapScreen.chargers = null
|
||||
invalidate()
|
||||
return@launch
|
||||
}
|
||||
chargers = response.data?.filterIsInstance(ChargeLocation::class.java)
|
||||
chargers = response.data?.items?.filterIsInstance<ChargeLocation>()
|
||||
if (prefs.placeSearchResultAndroidAutoName == null) {
|
||||
chargers = headingFilter(
|
||||
chargers,
|
||||
|
||||
@@ -4,7 +4,14 @@ 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.ItemList
|
||||
import androidx.car.app.model.Row
|
||||
import androidx.car.app.model.SearchTemplate
|
||||
import androidx.car.app.model.Template
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -45,7 +52,7 @@ abstract class MultiSelectSearchScreen<T>(ctx: CarContext) : Screen(ctx),
|
||||
} ?: run {
|
||||
setLoading(true)
|
||||
}
|
||||
if (isMultiSelect) {
|
||||
if (isMultiSelect && shouldShowSelectAll) {
|
||||
setActionStrip(ActionStrip.Builder().apply {
|
||||
addAction(
|
||||
Action.Builder().setIcon(
|
||||
|
||||
@@ -79,7 +79,7 @@ class SettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
)
|
||||
setBrowsable(true)
|
||||
setOnClickListener {
|
||||
screenManager.push(DataSettingsScreen(carContext))
|
||||
screenManager.push(DataSettingsScreen(carContext, session))
|
||||
}
|
||||
}.build())
|
||||
addItem(Row.Builder().apply {
|
||||
@@ -144,7 +144,7 @@ class SettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
)
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener {
|
||||
screenManager.push(AboutScreen(carContext))
|
||||
screenManager.push(AboutScreen(carContext, session))
|
||||
}
|
||||
.build()
|
||||
)
|
||||
@@ -153,7 +153,8 @@ class SettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
@ExperimentalCarApi
|
||||
class DataSettingsScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
val encryptedPrefs = EncryptedPreferenceDataStore(ctx)
|
||||
val db = AppDatabase.getInstance(ctx)
|
||||
@@ -216,7 +217,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
}
|
||||
}
|
||||
}.build())
|
||||
addItem(
|
||||
/*addItem(
|
||||
Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.pref_prediction_enabled))
|
||||
.addText(carContext.getString(R.string.pref_prediction_enabled_summary))
|
||||
@@ -224,7 +225,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
prefs.predictionEnabled = it
|
||||
}.setChecked(prefs.predictionEnabled).build())
|
||||
.build()
|
||||
)
|
||||
)*/
|
||||
addItem(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.pref_tesla_account))
|
||||
addText(
|
||||
@@ -279,7 +280,7 @@ class DataSettingsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
}
|
||||
}, IntentFilter(OAuthLoginFragment.ACTION_OAUTH_RESULT))
|
||||
|
||||
carContext.startActivity(intent)
|
||||
session.cas.startActivity(intent)
|
||||
|
||||
if (BuildConfig.FLAVOR_automotive != "automotive") {
|
||||
CarToast.makeText(
|
||||
@@ -752,7 +753,8 @@ class SelectChargingRangeScreen(ctx: CarContext) : Screen(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
class AboutScreen(ctx: CarContext) : Screen(ctx) {
|
||||
@ExperimentalCarApi
|
||||
class AboutScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
var developerOptionsCounter = 0
|
||||
private val maxRows = ctx.getContentLimit(ConstraintManager.CONTENT_LIMIT_TYPE_LIST)
|
||||
@@ -797,7 +799,11 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) {
|
||||
.setTitle(carContext.getString(R.string.faq))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(carContext, carContext.getString(R.string.faq_link))
|
||||
openUrl(
|
||||
carContext,
|
||||
session.cas,
|
||||
carContext.getString(R.string.faq_link)
|
||||
)
|
||||
}).build()
|
||||
)
|
||||
addItem(
|
||||
@@ -808,12 +814,16 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) {
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
if (BuildConfig.FLAVOR_automotive == "automotive") {
|
||||
// we can't open the donation page on the phone in this case
|
||||
openUrl(carContext, carContext.getString(R.string.donate_link))
|
||||
openUrl(
|
||||
carContext,
|
||||
session.cas,
|
||||
carContext.getString(R.string.donate_link)
|
||||
)
|
||||
} else {
|
||||
val intent = Intent(carContext, MapsActivity::class.java)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.putExtra(EXTRA_DONATE, true)
|
||||
carContext.startActivity(intent)
|
||||
session.cas.startActivity(intent)
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
R.string.opened_on_phone,
|
||||
@@ -825,39 +835,75 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) {
|
||||
}.build(), carContext.getString(R.string.about)))
|
||||
addSectionedList(SectionedItemList.create(ItemList.Builder().apply {
|
||||
addItem(Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.twitter))
|
||||
.addText(carContext.getString(R.string.twitter_handle))
|
||||
.setTitle(carContext.getString(R.string.mastodon))
|
||||
.addText(carContext.getString(R.string.mastodon_handle))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(carContext, carContext.getString(R.string.twitter_url))
|
||||
openUrl(
|
||||
carContext,
|
||||
session.cas,
|
||||
carContext.getString(R.string.mastodon_url)
|
||||
)
|
||||
}).build()
|
||||
)
|
||||
if (maxRows > 8) {
|
||||
addItem(
|
||||
Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.twitter))
|
||||
.addText(carContext.getString(R.string.twitter_handle))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(
|
||||
carContext,
|
||||
session.cas,
|
||||
carContext.getString(R.string.twitter_url)
|
||||
)
|
||||
}).build()
|
||||
)
|
||||
}
|
||||
if (maxRows > 6) {
|
||||
addItem(Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.goingelectric_forum))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(
|
||||
carContext,
|
||||
carContext, session.cas,
|
||||
carContext.getString(R.string.goingelectric_forum_url)
|
||||
)
|
||||
}).build()
|
||||
)
|
||||
}
|
||||
if (maxRows > 7) {
|
||||
addItem(
|
||||
Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.tff_forum))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(
|
||||
carContext, session.cas,
|
||||
carContext.getString(R.string.tff_forum_url)
|
||||
)
|
||||
}).build()
|
||||
)
|
||||
}
|
||||
}.build(), carContext.getString(R.string.contact)))
|
||||
addSectionedList(SectionedItemList.create(ItemList.Builder().apply {
|
||||
addItem(Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.github_link_title))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(carContext, carContext.getString(R.string.github_link))
|
||||
openUrl(carContext, session.cas, carContext.getString(R.string.github_link))
|
||||
}).build()
|
||||
)
|
||||
addItem(Row.Builder()
|
||||
.setTitle(carContext.getString(R.string.privacy))
|
||||
.setBrowsable(true)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(carContext, carContext.getString(R.string.privacy_link))
|
||||
openUrl(
|
||||
carContext,
|
||||
session.cas,
|
||||
carContext.getString(R.string.privacy_link)
|
||||
)
|
||||
}).build()
|
||||
)
|
||||
}.build(), carContext.getString(R.string.other)))
|
||||
@@ -865,7 +911,8 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
class AcceptPrivacyScreen(ctx: CarContext) : Screen(ctx) {
|
||||
@ExperimentalCarApi
|
||||
class AcceptPrivacyScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
override fun onGetTemplate(): Template {
|
||||
val textWithoutLink = HtmlCompat.fromHtml(
|
||||
@@ -886,7 +933,7 @@ class AcceptPrivacyScreen(ctx: CarContext) : Screen(ctx) {
|
||||
addAction(Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.privacy))
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(carContext, carContext.getString(R.string.privacy_link))
|
||||
openUrl(carContext, session.cas, carContext.getString(R.string.privacy_link))
|
||||
}).build()
|
||||
)
|
||||
}.build()
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.car.app.CarContext
|
||||
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.hardware.common.CarUnit
|
||||
import androidx.car.app.model.CarColor
|
||||
@@ -221,13 +222,14 @@ fun supportsCarApiLevel3(ctx: CarContext): Boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
fun openUrl(carContext: CarContext, url: String) {
|
||||
@ExperimentalCarApi
|
||||
fun openUrl(carContext: CarContext, cas: CarAppService, url: String) {
|
||||
val intent = CustomTabsIntent.Builder()
|
||||
.setDefaultColorSchemeParams(
|
||||
CustomTabColorSchemeParams.Builder()
|
||||
.setToolbarColor(
|
||||
ContextCompat.getColor(
|
||||
carContext,
|
||||
cas,
|
||||
R.color.colorPrimary
|
||||
)
|
||||
)
|
||||
@@ -237,7 +239,7 @@ fun openUrl(carContext: CarContext, url: String) {
|
||||
intent.data = Uri.parse(url)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
try {
|
||||
carContext.startActivity(intent)
|
||||
cas.startActivity(intent)
|
||||
if (BuildConfig.FLAVOR_automotive != "automotive") {
|
||||
// only show the toast "opened on phone" if we're running on a phone
|
||||
CarToast.makeText(
|
||||
|
||||
@@ -100,9 +100,9 @@ class ChargepriceFragment : Fragment() {
|
||||
inflater,
|
||||
R.layout.fragment_chargeprice_header, container, false
|
||||
)
|
||||
binding.lifecycleOwner = this
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.vm = vm
|
||||
headerBinding.lifecycleOwner = this
|
||||
headerBinding.lifecycleOwner = viewLifecycleOwner
|
||||
headerBinding.vm = vm
|
||||
|
||||
binding.toolbar.inflateMenu(R.menu.chargeprice)
|
||||
@@ -141,7 +141,7 @@ class ChargepriceFragment : Fragment() {
|
||||
|
||||
val chargepriceAdapter = ChargepriceAdapter().apply {
|
||||
onClickListener = {
|
||||
(requireActivity() as MapsActivity).openUrl(it.url)
|
||||
(requireActivity() as MapsActivity).openUrl(it.url, binding.root)
|
||||
}
|
||||
}
|
||||
val joinedAdapter = ConcatAdapter(
|
||||
@@ -194,7 +194,10 @@ class ChargepriceFragment : Fragment() {
|
||||
}
|
||||
|
||||
binding.imgChargepriceLogo.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(ChargepriceApi.getPoiUrl(charger))
|
||||
(requireActivity() as MapsActivity).openUrl(
|
||||
ChargepriceApi.getPoiUrl(charger),
|
||||
binding.root
|
||||
)
|
||||
}
|
||||
|
||||
binding.btnSettings.setOnClickListener {
|
||||
@@ -213,11 +216,19 @@ class ChargepriceFragment : Fragment() {
|
||||
}
|
||||
false
|
||||
}
|
||||
headerBinding.tvChargeFromTo.setOnClickListener {
|
||||
it.postDelayed({
|
||||
vm.resetBatteryRangeToDefault()
|
||||
}, 250)
|
||||
}
|
||||
|
||||
binding.toolbar.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
R.id.menu_help -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.chargeprice_faq_link))
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
getString(R.string.chargeprice_faq_link),
|
||||
binding.root
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
||||
@@ -14,14 +14,14 @@ import net.vonforst.evmap.api.availability.ChargeLocationStatus
|
||||
import net.vonforst.evmap.databinding.DialogConnectorDetailsBinding
|
||||
import net.vonforst.evmap.databinding.DialogConnectorDetailsHeaderBinding
|
||||
import net.vonforst.evmap.model.Chargepoint
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
|
||||
class ConnectorDetailsDialog(
|
||||
val binding: DialogConnectorDetailsBinding,
|
||||
binding: DialogConnectorDetailsBinding,
|
||||
context: Context,
|
||||
onClose: () -> Unit
|
||||
) {
|
||||
private val headerBinding: DialogConnectorDetailsHeaderBinding
|
||||
private var headerBinding_: DialogConnectorDetailsHeaderBinding? = null
|
||||
private val headerBinding get() = headerBinding_!!
|
||||
private val detailsAdapter = ConnectorDetailsAdapter()
|
||||
|
||||
init {
|
||||
@@ -30,7 +30,7 @@ class ConnectorDetailsDialog(
|
||||
layoutManager =
|
||||
LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
|
||||
}
|
||||
headerBinding = DataBindingUtil.inflate(
|
||||
headerBinding_ = DataBindingUtil.inflate(
|
||||
LayoutInflater.from(context),
|
||||
R.layout.dialog_connector_details_header, binding.list, false
|
||||
)
|
||||
@@ -60,4 +60,8 @@ class ConnectorDetailsDialog(
|
||||
headerBinding.divider.visibility = if (items.isEmpty()) View.GONE else View.VISIBLE
|
||||
headerBinding.item = ConnectorAdapter.ChargepointWithAvailability(cp, cpStatus)
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
headerBinding_ = null
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,26 @@
|
||||
package net.vonforst.evmap.fragment
|
||||
|
||||
import android.content.Intent
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.fragment.app.Fragment
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.databinding.FragmentDonateReferralBinding
|
||||
|
||||
abstract class DonateFragmentBase : Fragment() {
|
||||
fun setupReferrals(referrals: FragmentDonateReferralBinding) {
|
||||
referrals.referralTesla.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.tesla_referral_link))
|
||||
}
|
||||
referrals.referralJuicify.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.juicify_referral_link))
|
||||
}
|
||||
referrals.referralGeldfuereauto.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.geldfuereauto_referral_link))
|
||||
}
|
||||
referrals.referralMaingau.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.maingau_referral_link))
|
||||
}
|
||||
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))
|
||||
referrals.referralWebView.loadUrl(getString(R.string.referral_link))
|
||||
referrals.referralWebView.webViewClient = object : WebViewClient() {
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView,
|
||||
request: WebResourceRequest
|
||||
): Boolean {
|
||||
Intent(Intent.ACTION_VIEW, request.url).apply {
|
||||
startActivity(this)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class FavoritesFragment : Fragment() {
|
||||
inflater,
|
||||
R.layout.fragment_favorites, container, false
|
||||
)
|
||||
binding.lifecycleOwner = this
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.vm = vm
|
||||
|
||||
return binding.root
|
||||
|
||||
@@ -45,7 +45,7 @@ class FilterFragment : Fragment(), MenuProvider {
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_filter, container, false)
|
||||
binding.lifecycleOwner = this
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.vm = vm
|
||||
vm.filterProfile.observe(viewLifecycleOwner) {}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ class FilterProfilesFragment : Fragment() {
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentFilterProfilesBinding.inflate(inflater, container, false)
|
||||
binding.lifecycleOwner = this
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.vm = vm
|
||||
|
||||
return binding.root
|
||||
@@ -188,9 +188,17 @@ class FilterProfilesFragment : Fragment() {
|
||||
|
||||
dialog.setTitle(R.string.rename)
|
||||
.setMessage(R.string.save_profile_enter_name)
|
||||
}, {
|
||||
}, { newName ->
|
||||
lifecycleScope.launch {
|
||||
vm.update(fp.copy(name = it))
|
||||
if (vm.filterProfiles.value?.find { it.name == newName } != null) {
|
||||
Snackbar.make(
|
||||
view,
|
||||
R.string.filterprofile_name_not_unique,
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
} else {
|
||||
vm.update(fp.copy(name = newName))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -55,6 +55,7 @@ import androidx.transition.TransitionManager
|
||||
import coil.load
|
||||
import coil.memory.MemoryCache
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.MapFactory
|
||||
import com.car2go.maps.MapFragment
|
||||
import com.car2go.maps.OnMapReadyCallback
|
||||
import com.car2go.maps.model.BitmapDescriptor
|
||||
@@ -89,6 +90,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.ChargepointList
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.autocomplete.ApiUnavailableException
|
||||
import net.vonforst.evmap.autocomplete.PlaceWithBounds
|
||||
@@ -136,8 +138,9 @@ import kotlin.collections.set
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallback, MenuProvider {
|
||||
private lateinit var binding: FragmentMapBinding
|
||||
class MapFragment : Fragment(), OnMapReadyCallback, MenuProvider {
|
||||
private var _binding: FragmentMapBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private val vm: MapViewModel by viewModels()
|
||||
private val galleryVm: GalleryViewModel by activityViewModels()
|
||||
private var mapFragment: MapFragment? = null
|
||||
@@ -153,6 +156,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
private var searchResultMarker: Marker? = null
|
||||
private var searchResultIcon: BitmapDescriptor? = null
|
||||
private var connectionErrorSnackbar: Snackbar? = null
|
||||
private var zoomInSnackbar: Snackbar? = null
|
||||
private var previousChargepointIds: Set<Long>? = null
|
||||
private var mapTopPadding: Int = 0
|
||||
private var popupMenu: PopupMenu? = null
|
||||
@@ -211,9 +215,9 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_map, container, false)
|
||||
_binding = DataBindingUtil.inflate(inflater, R.layout.fragment_map, container, false)
|
||||
println(binding.detailView.sourceButton)
|
||||
binding.lifecycleOwner = this
|
||||
binding.lifecycleOwner = viewLifecycleOwner
|
||||
binding.vm = vm
|
||||
|
||||
val provider = prefs.mapProvider
|
||||
@@ -221,16 +225,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
mapFragment =
|
||||
childFragmentManager.findFragmentByTag(mapFragmentTag) as MapFragment?
|
||||
}
|
||||
if (mapFragment == null || mapFragment!!.priority[0] != provider) {
|
||||
if (mapFragment == null || mapFragment!!.priority[0] != getMapProvider(provider)) {
|
||||
mapFragment = MapFragment()
|
||||
mapFragment!!.priority = arrayOf(
|
||||
when (provider) {
|
||||
"mapbox" -> MapFragment.MAPLIBRE
|
||||
"google" -> MapFragment.GOOGLE
|
||||
else -> null
|
||||
},
|
||||
MapFragment.GOOGLE,
|
||||
MapFragment.MAPLIBRE
|
||||
getMapProvider(provider),
|
||||
MapFactory.GOOGLE,
|
||||
MapFactory.MAPLIBRE
|
||||
)
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
@@ -293,6 +293,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
return binding.root
|
||||
}
|
||||
|
||||
private fun getMapProvider(provider: String) = when (provider) {
|
||||
"mapbox" -> MapFactory.MAPLIBRE
|
||||
"google" -> MapFactory.GOOGLE
|
||||
else -> null
|
||||
}
|
||||
|
||||
val bottomSheetCollapsible
|
||||
get() = resources.getBoolean(R.bool.bottom_sheet_collapsible)
|
||||
|
||||
@@ -362,9 +368,11 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
binding.appLogo.root.animate().alpha(1f)
|
||||
.withEndAction {
|
||||
if (_binding == null) return@withEndAction
|
||||
binding.appLogo.root.animate().alpha(0f).apply {
|
||||
startDelay = 1000
|
||||
}.withEndAction {
|
||||
if (_binding == null) return@withEndAction
|
||||
binding.appLogo.root.visibility = View.GONE
|
||||
binding.search.visibility = View.VISIBLE
|
||||
binding.search.alpha = 0f
|
||||
@@ -388,9 +396,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val hostActivity = activity as? MapsActivity ?: return
|
||||
hostActivity.fragmentCallback = this
|
||||
|
||||
vm.reloadPrefs()
|
||||
if (requestingLocationUpdates && requireContext().checkAnyLocationPermission()
|
||||
) {
|
||||
@@ -422,7 +427,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
val charger = vm.charger.value?.data
|
||||
if (charger != null) {
|
||||
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||
(requireActivity() as MapsActivity).navigateTo(charger)
|
||||
(requireActivity() as MapsActivity).navigateTo(charger, binding.root)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -435,7 +440,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
binding.detailView.sourceButton.setOnClickListener {
|
||||
val charger = vm.charger.value?.data
|
||||
if (charger != null) {
|
||||
(activity as? MapsActivity)?.openUrl(charger.url)
|
||||
(activity as? MapsActivity)?.openUrl(charger.url, binding.root, true)
|
||||
}
|
||||
}
|
||||
binding.detailView.btnChargeprice.setOnClickListener {
|
||||
@@ -448,12 +453,15 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
extras
|
||||
)
|
||||
} else {
|
||||
(activity as? MapsActivity)?.openUrl(ChargepriceApi.getPoiUrl(charger), false)
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
ChargepriceApi.getPoiUrl(charger),
|
||||
binding.root
|
||||
)
|
||||
}
|
||||
}
|
||||
binding.detailView.btnChargerWebsite.setOnClickListener {
|
||||
val charger = vm.charger.value?.data ?: return@setOnClickListener
|
||||
charger.chargerUrl?.let { (activity as? MapsActivity)?.openUrl(it) }
|
||||
charger.chargerUrl?.let { (activity as? MapsActivity)?.openUrl(it, binding.root) }
|
||||
}
|
||||
binding.detailView.btnLogin.setOnClickListener {
|
||||
findNavController().safeNavigate(
|
||||
@@ -461,7 +469,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
)
|
||||
}
|
||||
binding.detailView.imgPredictionSource.setOnClickListener {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.fronyx_url))
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.fronyx_url), binding.root)
|
||||
}
|
||||
binding.detailView.btnPredictionHelp.setOnClickListener {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
@@ -501,7 +509,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
R.id.menu_edit -> {
|
||||
val charger = vm.charger.value?.data
|
||||
if (charger?.editUrl != null) {
|
||||
(activity as? MapsActivity)?.openUrl(charger.editUrl)
|
||||
(activity as? MapsActivity)?.openUrl(charger.editUrl, binding.root, true)
|
||||
if (vm.apiId.value == "goingelectric") {
|
||||
// instructions specific to GoingElectric
|
||||
Toast.makeText(
|
||||
@@ -702,12 +710,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
vm.chargepoints.observe(viewLifecycleOwner, Observer { res ->
|
||||
val chargepoints = res.data
|
||||
if (chargepoints != null) {
|
||||
updateMap(chargepoints)
|
||||
updateMap(chargepoints.items)
|
||||
}
|
||||
val view = view ?: return@Observer
|
||||
when (res.status) {
|
||||
Status.ERROR -> {
|
||||
val view = view ?: return@Observer
|
||||
|
||||
zoomInSnackbar?.dismiss()
|
||||
connectionErrorSnackbar?.dismiss()
|
||||
connectionErrorSnackbar = Snackbar
|
||||
.make(view, R.string.connection_error, Snackbar.LENGTH_INDEFINITE)
|
||||
@@ -719,13 +727,20 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
Status.SUCCESS -> {
|
||||
connectionErrorSnackbar?.dismiss()
|
||||
if (res.data != null && !res.data.isComplete) {
|
||||
zoomInSnackbar?.dismiss()
|
||||
zoomInSnackbar = Snackbar
|
||||
.make(view, R.string.zoom_in_to_see_more, Snackbar.LENGTH_INDEFINITE)
|
||||
zoomInSnackbar!!.show()
|
||||
}
|
||||
}
|
||||
Status.LOADING -> {
|
||||
zoomInSnackbar?.dismiss()
|
||||
}
|
||||
}
|
||||
})
|
||||
vm.useMiniMarkers.observe(viewLifecycleOwner) {
|
||||
vm.chargepoints.value?.data?.let { updateMap(it) }
|
||||
vm.chargepoints.value?.data?.let { updateMap(it.items) }
|
||||
}
|
||||
vm.favorites.observe(viewLifecycleOwner) {
|
||||
updateFavoriteToggle()
|
||||
@@ -917,10 +932,14 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
if (charger != null) {
|
||||
when (it.icon) {
|
||||
R.drawable.ic_location, R.drawable.ic_address -> {
|
||||
(activity as? MapsActivity)?.showLocation(charger)
|
||||
(activity as? MapsActivity)?.showLocation(charger, binding.root)
|
||||
}
|
||||
R.drawable.ic_fault_report -> {
|
||||
(activity as? MapsActivity)?.openUrl(charger.url)
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
charger.url,
|
||||
binding.root,
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
R.drawable.ic_payment -> {
|
||||
@@ -928,7 +947,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
|
||||
R.drawable.ic_network -> {
|
||||
charger.networkUrl?.let { (activity as? MapsActivity)?.openUrl(it) }
|
||||
charger.networkUrl?.let {
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
it,
|
||||
binding.root
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1047,13 +1071,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
.setTitle(R.string.charge_cards)
|
||||
.setItems(names.toTypedArray()) { _, i ->
|
||||
val card = data[i]
|
||||
(activity as? MapsActivity)?.openUrl("https:${card.url}")
|
||||
(activity as? MapsActivity)?.openUrl("https:${card.url}", binding.root)
|
||||
}.show()
|
||||
}
|
||||
|
||||
override fun onMapReady(map: AnyMap) {
|
||||
this.map = map
|
||||
vm.mapProjection = map.projection
|
||||
val context = this.context ?: return
|
||||
view ?: return
|
||||
|
||||
@@ -1062,7 +1085,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
vm.mapTrafficSupported.value =
|
||||
mapFragment?.let { AnyMap.Feature.TRAFFIC_LAYER in it.supportedFeatures } ?: false
|
||||
|
||||
if (BuildConfig.FLAVOR.contains("google") && mapFragment!!.priority[0] == MapFragment.GOOGLE) {
|
||||
if (BuildConfig.FLAVOR.contains("google") && mapFragment!!.priority[0] == MapFactory.GOOGLE) {
|
||||
// Google Maps: icons can be generated in background thread
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.Default) {
|
||||
@@ -1083,14 +1106,12 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
map.uiSettings.setIndoorLevelPickerEnabled(false)
|
||||
|
||||
map.setOnCameraIdleListener {
|
||||
vm.mapProjection = map.projection
|
||||
vm.mapPosition.value = MapPosition(
|
||||
map.projection.visibleRegion.latLngBounds, map.cameraPosition.zoom
|
||||
)
|
||||
vm.reloadChargepoints()
|
||||
}
|
||||
map.setOnCameraMoveListener {
|
||||
vm.mapProjection = map.projection
|
||||
vm.mapPosition.value = MapPosition(
|
||||
map.projection.visibleRegion.latLngBounds, map.cameraPosition.zoom
|
||||
)
|
||||
@@ -1113,7 +1134,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
vm.mapPosition.observe(viewLifecycleOwner) {
|
||||
binding.scaleView.update(map.cameraPosition.zoom, map.cameraPosition.target.latitude)
|
||||
val target = map.cameraPosition.target ?: return@observe
|
||||
binding.scaleView.update(map.cameraPosition.zoom, target.latitude)
|
||||
}
|
||||
|
||||
map.setOnCameraMoveStartedListener { reason ->
|
||||
@@ -1130,6 +1152,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
map.setOnMarkerClickListener { marker ->
|
||||
val map = this@MapFragment.map ?: return@setOnMarkerClickListener false
|
||||
when (marker) {
|
||||
in markers -> {
|
||||
vm.chargerSparse.value = markers[marker]
|
||||
@@ -1145,6 +1168,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
)
|
||||
true
|
||||
}
|
||||
searchResultMarker -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
@@ -1206,10 +1230,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
// show charger detail after chargers were loaded
|
||||
vm.chargepoints.observe(
|
||||
viewLifecycleOwner,
|
||||
object : Observer<Resource<List<ChargepointListItem>>> {
|
||||
override fun onChanged(value: Resource<List<ChargepointListItem>>) {
|
||||
object : Observer<Resource<ChargepointList>> {
|
||||
override fun onChanged(value: Resource<ChargepointList>) {
|
||||
if (value.data == null) return
|
||||
for (item in value.data) {
|
||||
for (item in value.data.items) {
|
||||
if (item is ChargeLocation && item.id == chargerId) {
|
||||
vm.chargerSparse.value = item
|
||||
vm.chargepoints.removeObserver(this)
|
||||
@@ -1536,10 +1560,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
else -> false
|
||||
}
|
||||
|
||||
override fun getRootView(): View {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@RequiresPermission(anyOf = [ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION])
|
||||
private fun requestLocationUpdates() {
|
||||
locationEngine.requestLocationUpdates(
|
||||
@@ -1589,8 +1609,17 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
detailsDialog.onDestroy()
|
||||
|
||||
map = null
|
||||
mapFragment = null
|
||||
_binding = null
|
||||
markers.clear()
|
||||
clusterMarkers = emptyList()
|
||||
searchResultMarker = null
|
||||
searchResultIcon = null
|
||||
/* 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()
|
||||
|
||||
@@ -6,8 +6,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.AnimatedVectorDrawable
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@@ -15,15 +13,21 @@ import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.ImageView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.text.method.LinkMovementMethodCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.databinding.*
|
||||
import net.vonforst.evmap.databinding.FragmentOnboardingAndroidAutoBinding
|
||||
import net.vonforst.evmap.databinding.FragmentOnboardingBinding
|
||||
import net.vonforst.evmap.databinding.FragmentOnboardingDataSourceBinding
|
||||
import net.vonforst.evmap.databinding.FragmentOnboardingIconsBinding
|
||||
import net.vonforst.evmap.databinding.FragmentOnboardingWelcomeBinding
|
||||
import net.vonforst.evmap.model.FILTERS_DISABLED
|
||||
import net.vonforst.evmap.navigation.safeNavigate
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.waitForLayout
|
||||
|
||||
class OnboardingFragment : Fragment() {
|
||||
private lateinit var binding: FragmentOnboardingBinding
|
||||
@@ -59,7 +63,6 @@ class OnboardingFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onPageSelected(position: Int) {
|
||||
binding.pageIndicatorView.selection = position
|
||||
binding.forward?.visibility =
|
||||
if (position == adapter.itemCount - 1) View.INVISIBLE else View.VISIBLE
|
||||
binding.backward?.visibility = if (position == 0) View.INVISIBLE else View.VISIBLE
|
||||
@@ -76,9 +79,13 @@ class OnboardingFragment : Fragment() {
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
if (prefs.welcomeDialogShown) {
|
||||
// skip to last page for selecting data source or accepting the privacy policy
|
||||
binding.viewPager.currentItem = adapter.itemCount - 1
|
||||
binding.root.waitForLayout {
|
||||
binding.viewPager.currentItem = if (prefs.welcomeDialogShown) {
|
||||
// skip to last page for selecting data source or accepting the privacy policy
|
||||
adapter.itemCount - 1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +241,7 @@ class DataSourceSelectFragment : OnboardingPageFragment() {
|
||||
), HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
)
|
||||
binding.cbAcceptPrivacy.linksClickable = true
|
||||
binding.cbAcceptPrivacy.movementMethod = LinkMovementMethod.getInstance()
|
||||
binding.cbAcceptPrivacy.movementMethod = LinkMovementMethodCompat.getInstance()
|
||||
binding.btnGetStarted.visibility = View.INVISIBLE
|
||||
|
||||
for (rb in listOf(
|
||||
|
||||
@@ -78,22 +78,25 @@ class AboutFragment : PreferenceFragmentCompat() {
|
||||
}
|
||||
|
||||
"website" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.website_url))
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.website_url), requireView())
|
||||
true
|
||||
}
|
||||
|
||||
"github_link" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.github_link))
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.github_link), requireView())
|
||||
true
|
||||
}
|
||||
|
||||
"privacy" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.privacy_link))
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
getString(R.string.privacy_link),
|
||||
requireView()
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
"faq" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.faq_link))
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.faq_link), requireView())
|
||||
true
|
||||
}
|
||||
"oss_licenses" -> {
|
||||
@@ -115,12 +118,29 @@ class AboutFragment : PreferenceFragmentCompat() {
|
||||
findNavController().safeNavigate(AboutFragmentDirections.actionAboutToGithubSponsors())
|
||||
true
|
||||
}
|
||||
"mastodon" -> {
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
getString(R.string.mastodon_url),
|
||||
requireView()
|
||||
)
|
||||
true
|
||||
}
|
||||
"twitter" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.twitter_url))
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.twitter_url), requireView())
|
||||
true
|
||||
}
|
||||
"goingelectric" -> {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.goingelectric_forum_url))
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
getString(R.string.goingelectric_forum_url),
|
||||
requireView()
|
||||
)
|
||||
true
|
||||
}
|
||||
"tffforum" -> {
|
||||
(activity as? MapsActivity)?.openUrl(
|
||||
getString(R.string.tff_forum_url),
|
||||
requireView()
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> super.onPreferenceTreeClick(preference)
|
||||
|
||||
@@ -44,7 +44,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
try {
|
||||
return locationManager.getLastKnownLocation(LocationManager.FUSED_PROVIDER)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Permissions not granted for fused provider", e)
|
||||
Log.w(TAG, "Permissions not granted for fused provider", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
}
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Permissions not granted for provider: $provider", e)
|
||||
Log.w(TAG, "Permissions not granted for provider: $provider", e)
|
||||
}
|
||||
}
|
||||
return bestLocation
|
||||
@@ -103,7 +103,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
enableFused(gpsInterval)
|
||||
checkLastKnownFused()
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Permissions not granted for fused provider", e)
|
||||
Log.w(TAG, "Permissions not granted for fused provider", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for GPS updates.", e)
|
||||
Log.w(TAG, "Unable to register for GPS updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for network updates.", e)
|
||||
Log.w(TAG, "Unable to register for network updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for passive updates.", e)
|
||||
Log.w(TAG, "Unable to register for passive updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ class FusionEngine(context: Context) : LocationEngine(context),
|
||||
looper
|
||||
)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Log.e(TAG, "Unable to register for passive updates.", e)
|
||||
Log.w(TAG, "Unable to register for passive updates.", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import java.time.LocalDate
|
||||
import java.time.LocalTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
|
||||
sealed class ChargepointListItem
|
||||
@@ -140,9 +141,9 @@ data class ChargeLocation(
|
||||
val totalChargepoints: Int
|
||||
get() = chargepoints.sumOf { it.count }
|
||||
|
||||
fun formatChargepoints(sp: StringProvider): String {
|
||||
fun formatChargepoints(sp: StringProvider, locale: Locale): String {
|
||||
return chargepointsMerged.joinToString(" · ") {
|
||||
"${it.count} × ${nameForPlugType(sp, it.type)} ${it.formatPower()}"
|
||||
"${it.count} × ${nameForPlugType(sp, it.type)} ${it.formatPower(locale)}"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -413,12 +414,12 @@ data class Chargepoint(
|
||||
* If chargepoint power is defined, format it into a string.
|
||||
* Otherwise, return null.
|
||||
*/
|
||||
fun formatPower(): String? {
|
||||
fun formatPower(locale: Locale): String? {
|
||||
if (power == null) return null
|
||||
val powerFmt = if (abs(power - power.toInt()) < 0.1) {
|
||||
"%.0f".format(power)
|
||||
"%.0f".format(locale, power)
|
||||
} else {
|
||||
"%.1f".format(power)
|
||||
"%.1f".format(locale, power)
|
||||
}
|
||||
return "$powerFmt kW"
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ import net.vonforst.evmap.api.goingelectric.GoingElectricApiWrapper
|
||||
import net.vonforst.evmap.api.openchargemap.OpenChargeMapApiWrapper
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.ui.cluster
|
||||
import net.vonforst.evmap.utils.crossesAntimeridian
|
||||
import net.vonforst.evmap.utils.splitAtAntimeridian
|
||||
import net.vonforst.evmap.viewmodel.Resource
|
||||
import net.vonforst.evmap.viewmodel.Status
|
||||
import net.vonforst.evmap.viewmodel.await
|
||||
@@ -144,7 +146,15 @@ class ChargeLocationsRepository(
|
||||
zoom: Float,
|
||||
filters: FilterValues?,
|
||||
overrideCache: Boolean = false
|
||||
): LiveData<Resource<List<ChargepointListItem>>> {
|
||||
): LiveData<Resource<ChargepointList>> {
|
||||
if (bounds.crossesAntimeridian()) {
|
||||
val (a, b) = bounds.splitAtAntimeridian()
|
||||
val liveDataA = getChargepoints(a, zoom, filters, overrideCache)
|
||||
val liveDataB = getChargepoints(b, zoom, filters, overrideCache)
|
||||
return combineLiveData(liveDataA, liveDataB)
|
||||
}
|
||||
|
||||
|
||||
val api = api.value!!
|
||||
|
||||
val dbResult = if (filters == null) {
|
||||
@@ -158,7 +168,7 @@ class ChargeLocationsRepository(
|
||||
)
|
||||
} else {
|
||||
queryWithFilters(api, filters, bounds)
|
||||
}.map { applyLocalClustering(it, zoom) }
|
||||
}.map { ChargepointList(applyLocalClustering(it, zoom), true) }
|
||||
val filtersSerialized =
|
||||
filters?.filter { it.value != it.filter.defaultValue() }?.takeIf { it.isNotEmpty() }
|
||||
?.serialize()
|
||||
@@ -208,12 +218,41 @@ class ChargeLocationsRepository(
|
||||
}
|
||||
}
|
||||
|
||||
private fun combineLiveData(
|
||||
liveDataA: LiveData<Resource<ChargepointList>>,
|
||||
liveDataB: LiveData<Resource<ChargepointList>>
|
||||
) = MediatorLiveData<Resource<ChargepointList>>().apply {
|
||||
listOf(liveDataA, liveDataB).forEach {
|
||||
addSource(it) {
|
||||
val valA = liveDataA.value
|
||||
val valB = liveDataB.value
|
||||
val combinedList = if (valA?.data != null && valB?.data != null) {
|
||||
ChargepointList(
|
||||
valA.data.items + valB.data.items,
|
||||
valA.data.isComplete && valB.data.isComplete
|
||||
)
|
||||
} else if (valA?.data != null) {
|
||||
ChargepointList(valA.data.items, false)
|
||||
} else if (valB?.data != null) {
|
||||
ChargepointList(valB.data.items, false)
|
||||
} else null
|
||||
if (valA?.status == Status.SUCCESS && valB?.status == Status.SUCCESS) {
|
||||
Resource.success(combinedList)
|
||||
} else if (valA?.status == Status.ERROR || valB?.status == Status.ERROR) {
|
||||
Resource.error(valA?.message ?: valB?.message, combinedList)
|
||||
} else {
|
||||
Resource.loading(combinedList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getChargepointsRadius(
|
||||
location: LatLng,
|
||||
radius: Int,
|
||||
zoom: Float,
|
||||
filters: FilterValues?
|
||||
): LiveData<Resource<List<ChargepointListItem>>> {
|
||||
): LiveData<Resource<ChargepointList>> {
|
||||
val api = api.value!!
|
||||
|
||||
val radiusMeters = radius.toDouble() * 1000
|
||||
@@ -227,7 +266,7 @@ class ChargeLocationsRepository(
|
||||
)
|
||||
} else {
|
||||
queryWithFilters(api, filters, location, radiusMeters)
|
||||
}.map { applyLocalClustering(it, zoom) }
|
||||
}.map { ChargepointList(applyLocalClustering(it, zoom), true) }
|
||||
val filtersSerialized =
|
||||
filters?.filter { it.value != it.filter.defaultValue() }?.takeIf { it.isNotEmpty() }
|
||||
?.serialize()
|
||||
@@ -277,18 +316,18 @@ class ChargeLocationsRepository(
|
||||
private fun applyLocalClustering(
|
||||
result: Resource<ChargepointList>,
|
||||
zoom: Float
|
||||
): Resource<List<ChargepointListItem>> {
|
||||
): Resource<ChargepointList> {
|
||||
val list = result.data ?: return Resource(result.status, null, result.message)
|
||||
val chargers = list.items.filterIsInstance<ChargeLocation>()
|
||||
|
||||
if (chargers.size != list.items.size) return Resource(
|
||||
result.status,
|
||||
list.items,
|
||||
list,
|
||||
result.message
|
||||
) // list already contains clusters
|
||||
|
||||
val clustered = applyLocalClustering(chargers, zoom)
|
||||
return Resource(result.status, clustered, result.message)
|
||||
return Resource(result.status, ChargepointList(clustered, list.isComplete), result.message)
|
||||
}
|
||||
|
||||
private fun applyLocalClustering(
|
||||
|
||||
@@ -6,8 +6,9 @@ import android.graphics.Color
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.text.SpannableString
|
||||
import android.text.format.DateUtils
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.util.Linkify
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import android.widget.ImageView
|
||||
@@ -215,21 +216,25 @@ fun setTopMargin(view: View, topMargin: Float) {
|
||||
|
||||
/**
|
||||
* Linkify is already possible using the autoLink and linksClickable attributes, but this does not
|
||||
* remove spans correctly. So we implement a new version that manually removes the spans.
|
||||
* remove spans correctly after autoLink is set to false.
|
||||
* So we implement a new version that manually uses Linkify to create links if necessary.
|
||||
*/
|
||||
@BindingAdapter("linkify")
|
||||
fun setLinkify(textView: TextView, oldValue: Int, newValue: Int) {
|
||||
if (oldValue == newValue) return
|
||||
@BindingAdapter(value = ["linkify", "android:text"])
|
||||
fun setLinkify(
|
||||
textView: TextView,
|
||||
oldLinkify: Int,
|
||||
oldText: CharSequence?,
|
||||
newLinkify: Int,
|
||||
newText: CharSequence?
|
||||
) {
|
||||
if (oldLinkify == newLinkify && oldText == newText) return
|
||||
|
||||
textView.autoLinkMask = newValue
|
||||
textView.linksClickable = newValue != 0
|
||||
|
||||
// remove spans
|
||||
val text = textView.text
|
||||
if (newValue == 0 && text != null && text is SpannableString) {
|
||||
text.getSpans(0, text.length, Any::class.java).forEach {
|
||||
text.removeSpan(it)
|
||||
}
|
||||
textView.text = newText
|
||||
if (newLinkify != 0) {
|
||||
Linkify.addLinks(textView, newLinkify)
|
||||
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||
} else {
|
||||
textView.movementMethod = null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,20 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.location.Location
|
||||
import android.text.BidiFormatter
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import net.vonforst.evmap.model.Coordinate
|
||||
import java.util.*
|
||||
import kotlin.math.*
|
||||
import java.util.Locale
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.asin
|
||||
import kotlin.math.atan2
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.sqrt
|
||||
|
||||
/**
|
||||
* Adds a certain distance in meters to a location. Approximate calculation.
|
||||
@@ -147,9 +155,32 @@ private fun dms(value: Double, lon: Boolean): String {
|
||||
}
|
||||
|
||||
fun Coordinate.formatDecimal(accuracy: Int = 6): String {
|
||||
return "%.${accuracy}f, %.${accuracy}f".format(Locale.ENGLISH, lat, lng)
|
||||
return BidiFormatter.getInstance()
|
||||
.unicodeWrap("%.${accuracy}f, %.${accuracy}f".format(Locale.ENGLISH, lat, lng))
|
||||
}
|
||||
|
||||
fun Location.formatDecimal(accuracy: Int = 6): String {
|
||||
return "%.${accuracy}f, %.${accuracy}f".format(Locale.ENGLISH, latitude, longitude)
|
||||
return BidiFormatter.getInstance()
|
||||
.unicodeWrap("%.${accuracy}f, %.${accuracy}f".format(Locale.ENGLISH, latitude, longitude))
|
||||
}
|
||||
|
||||
fun LatLngBounds.normalize() = LatLngBounds(
|
||||
LatLng(southwest.latitude, normalizeLongitude(southwest.longitude)),
|
||||
LatLng(northeast.latitude, normalizeLongitude(northeast.longitude)),
|
||||
)
|
||||
|
||||
private fun normalizeLongitude(long: Double) =
|
||||
if (-180.0 <= long && long <= 180.0) long else (long + 180) % 360 - 180
|
||||
|
||||
fun LatLngBounds.crossesAntimeridian() = southwest.longitude > 0 && northeast.longitude < 0
|
||||
|
||||
fun LatLngBounds.splitAtAntimeridian(): Pair<LatLngBounds, LatLngBounds> {
|
||||
if (!crossesAntimeridian()) throw IllegalArgumentException("does not cross antimeridian")
|
||||
return LatLngBounds(
|
||||
LatLng(southwest.latitude, southwest.longitude),
|
||||
LatLng(northeast.latitude, 180.0),
|
||||
) to LatLngBounds(
|
||||
LatLng(southwest.latitude, -180.0),
|
||||
LatLng(northeast.latitude, northeast.longitude),
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,29 @@
|
||||
package net.vonforst.evmap.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.*
|
||||
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.viewModelScope
|
||||
import jsonapi.Meta
|
||||
import jsonapi.Relationship
|
||||
import jsonapi.Relationships
|
||||
import jsonapi.ResourceIdentifier
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.api.chargeprice.*
|
||||
import net.vonforst.evmap.api.chargeprice.ChargePrice
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceCar
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceChargepointMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceInclude
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceOptions
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceRequest
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceRequestTariffMeta
|
||||
import net.vonforst.evmap.api.chargeprice.ChargepriceStation
|
||||
import net.vonforst.evmap.api.equivalentPlugTypes
|
||||
import net.vonforst.evmap.model.ChargeLocation
|
||||
import net.vonforst.evmap.model.Chargepoint
|
||||
@@ -298,4 +313,8 @@ class ChargepriceViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetBatteryRangeToDefault() {
|
||||
batteryRange.value = prefs.chargepriceBatteryRangeAndroidAuto
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
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
|
||||
@@ -14,7 +13,6 @@ import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.car2go.maps.AnyMap
|
||||
import com.car2go.maps.Projection
|
||||
import com.car2go.maps.model.LatLng
|
||||
import com.car2go.maps.model.LatLngBounds
|
||||
import com.mahc.custombottomsheetbehavior.BottomSheetBehaviorGoogleMapsLike
|
||||
@@ -22,12 +20,12 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import net.vonforst.evmap.api.ChargepointList
|
||||
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.createApi
|
||||
import net.vonforst.evmap.api.fronyx.PredictionData
|
||||
import net.vonforst.evmap.api.fronyx.PredictionRepository
|
||||
import net.vonforst.evmap.api.goingelectric.GEChargepoint
|
||||
import net.vonforst.evmap.api.openchargemap.OCMConnection
|
||||
import net.vonforst.evmap.api.openchargemap.OCMReferenceData
|
||||
@@ -35,7 +33,6 @@ 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
|
||||
@@ -51,7 +48,8 @@ import net.vonforst.evmap.storage.FilterProfile
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.cluster
|
||||
import net.vonforst.evmap.utils.distanceBetween
|
||||
import kotlin.math.roundToInt
|
||||
import net.vonforst.evmap.utils.normalize
|
||||
import kotlin.math.cos
|
||||
|
||||
@Parcelize
|
||||
data class MapPosition(val bounds: LatLngBounds, val zoom: Float) : Parcelable
|
||||
@@ -77,7 +75,6 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
prefs
|
||||
)
|
||||
private val availabilityRepo = AvailabilityRepository(application)
|
||||
var mapProjection: Projection? = null
|
||||
|
||||
val apiId = repo.api.map { it.id }
|
||||
|
||||
@@ -144,10 +141,10 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
}
|
||||
}
|
||||
val chargepoints: MediatorLiveData<Resource<List<ChargepointListItem>>> by lazy {
|
||||
MediatorLiveData<Resource<List<ChargepointListItem>>>()
|
||||
val chargepoints: MediatorLiveData<Resource<ChargepointList>> by lazy {
|
||||
MediatorLiveData<Resource<ChargepointList>>()
|
||||
.apply {
|
||||
value = Resource.loading(emptyList())
|
||||
value = Resource.loading(ChargepointList(emptyList(), false))
|
||||
// this is not automatically updated with mapPosition, as we only want to update
|
||||
// when map is idle.
|
||||
listOf(filtersWithValue, repo.api).forEach {
|
||||
@@ -266,13 +263,14 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
it.data?.extraData as? Pricing
|
||||
}
|
||||
|
||||
private val predictionRepository = PredictionRepository(application)
|
||||
//private val predictionRepository = PredictionRepository(application)
|
||||
|
||||
val predictionData: LiveData<PredictionData> = availability.switchMap { av ->
|
||||
liveData {
|
||||
/*liveData {
|
||||
val charger = charger.value?.data ?: return@liveData
|
||||
emit(predictionRepository.getPredictionData(charger, av.data, filteredConnectors.value))
|
||||
}
|
||||
}*/
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
val myLocationEnabled: MutableLiveData<Boolean> by lazy {
|
||||
@@ -393,7 +391,7 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
}
|
||||
}.distinctUntilChanged()
|
||||
|
||||
private var chargepointsInternal: LiveData<Resource<List<ChargepointListItem>>>? = null
|
||||
private var chargepointsInternal: LiveData<Resource<ChargepointList>>? = null
|
||||
private var chargepointLoader =
|
||||
throttleLatest(
|
||||
500L,
|
||||
@@ -420,13 +418,13 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
filteredConnectors.value = null
|
||||
filteredMinPower.value = null
|
||||
filteredChargeCards.value = null
|
||||
chargepoints.value = Resource.success(chargersClustered)
|
||||
chargepoints.value = Resource.success(ChargepointList(chargersClustered, true))
|
||||
return@throttleLatest
|
||||
}
|
||||
|
||||
val result = repo.getChargepoints(bounds, mapPosition.zoom, filters, overrideCache)
|
||||
chargepointsInternal?.let { chargepoints.removeSource(it) }
|
||||
chargepointsInternal = result
|
||||
chargepointsInternal
|
||||
chargepoints.addSource(result) {
|
||||
val apiId = apiId.value
|
||||
when (apiId) {
|
||||
@@ -471,14 +469,26 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
|
||||
* expands LatLngBounds beyond the viewport (1.5x the width and height)
|
||||
*/
|
||||
private fun extendBounds(bounds: LatLngBounds): LatLngBounds {
|
||||
val mapProjection = mapProjection ?: return bounds
|
||||
val swPoint = mapProjection.toScreenLocation(bounds.southwest)
|
||||
val nePoint = mapProjection.toScreenLocation(bounds.northeast)
|
||||
val dx = ((nePoint.x - swPoint.x) * 0.25).roundToInt()
|
||||
val dy = ((nePoint.y - swPoint.y) * 0.25).roundToInt()
|
||||
val newSw = mapProjection.fromScreenLocation(Point(swPoint.x - dx, swPoint.y - dy))
|
||||
val newNe = mapProjection.fromScreenLocation(Point(nePoint.x + dx, nePoint.y + dy))
|
||||
return LatLngBounds(newSw, newNe)
|
||||
val sw = bounds.southwest
|
||||
val ne = bounds.northeast
|
||||
|
||||
// do not expand bounds if the map area shown is very large
|
||||
val expansion = if (ne.longitude - sw.longitude > 10) 1.0 else 1.5
|
||||
val factor = (expansion - 1.0) * 0.5
|
||||
|
||||
var west = sw.longitude - (ne.longitude - sw.longitude) * factor
|
||||
var east = ne.longitude + (ne.longitude - sw.longitude) * factor
|
||||
val south =
|
||||
sw.latitude - (ne.latitude - sw.latitude) * factor * cos(Math.toRadians(sw.latitude))
|
||||
val north =
|
||||
ne.latitude + (ne.latitude - sw.latitude) * factor * cos(Math.toRadians(ne.latitude))
|
||||
|
||||
if (east - west >= 360) {
|
||||
west = -180.0
|
||||
east = 180.0
|
||||
}
|
||||
|
||||
return LatLngBounds(LatLng(south, west), LatLng(north, east)).normalize()
|
||||
}
|
||||
|
||||
fun reloadAvailability() {
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
app:piv_rtl_mode="auto"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/card"
|
||||
|
||||
@@ -59,8 +59,6 @@
|
||||
app:layout_constraintBottom_toTopOf="@+id/welcomeTitle"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.7"
|
||||
app:srcCompat="@drawable/android_auto" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -19,6 +19,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textAppearance="@style/TextAppearance.Material3.HeadlineLarge"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbGoingElectric"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/data_source_goingelectric"
|
||||
android:textColor="#098ac7"
|
||||
@@ -21,6 +21,7 @@
|
||||
android:layout_marginTop="-8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:text="@string/data_source_goingelectric_desc" />
|
||||
|
||||
<RadioButton
|
||||
@@ -39,6 +40,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="-8dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:textAlignment="viewStart"
|
||||
android:text="@string/data_source_openchargemap_desc" />
|
||||
|
||||
</RadioGroup>
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
<import type="java.util.Map" />
|
||||
|
||||
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
|
||||
|
||||
<import type="java.time.ZonedDateTime" />
|
||||
|
||||
<import type="net.vonforst.evmap.model.ChargeLocation" />
|
||||
@@ -120,6 +122,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAlignment="viewStart"
|
||||
android:text="@{charger.data.address.toString()}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:invisibleUnless="@{charger.data.address != null}"
|
||||
@@ -134,7 +137,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="end"
|
||||
android:textAlignment="viewEnd"
|
||||
android:maxLines="1"
|
||||
android:minWidth="50dp"
|
||||
android:text="@{BindingAdaptersKt.distance(distance, context)}"
|
||||
@@ -153,7 +156,7 @@
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:padding="2dp"
|
||||
android:text="@{String.format("%s/%d", BindingAdaptersKt.availabilityText(BindingAdaptersKt.flatten(filteredAvailability.data.status.values())), filteredAvailability.data.totalChargepoints)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "%s/%d", BindingAdaptersKt.availabilityText(BindingAdaptersKt.flatten(filteredAvailability.data.status.values())), filteredAvailability.data.totalChargepoints)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@android:color/white"
|
||||
app:backgroundTintAvailability="@{BindingAdaptersKt.flatten(filteredAvailability.data.status.values())}"
|
||||
@@ -170,7 +173,8 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{charger.data.formatChargepoints(ChargepointApiKt.stringProvider(context))}"
|
||||
android:textAlignment="viewStart"
|
||||
android:text="@{charger.data.formatChargepoints(ChargepointApiKt.stringProvider(context), LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:layout_constraintEnd_toStartOf="@+id/txtDistance"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
@@ -199,6 +203,7 @@
|
||||
android:text="@string/connectors"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:textColor="?colorPrimary"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnRefreshLiveData"
|
||||
app:layout_constraintStart_toStartOf="@+id/guideline"
|
||||
app:layout_constraintTop_toBottomOf="@+id/txtConnectors" />
|
||||
@@ -315,7 +320,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:gravity="right|end"
|
||||
android:gravity="end"
|
||||
android:text="@{availability.status == Status.SUCCESS ? @string/realtime_data_source(availability.data.source) : availability.status == Status.LOADING ? @string/realtime_data_loading : availability.message == "not signed in" ? @string/realtime_data_login_needed : @string/realtime_data_unavailable}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnLogin"
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
<import type="net.vonforst.evmap.adapter.ConnectorAdapter.ChargepointWithAvailability" />
|
||||
|
||||
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
|
||||
|
||||
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
|
||||
|
||||
<import type="net.vonforst.evmap.api.UtilsKt" />
|
||||
@@ -47,7 +49,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="38dp"
|
||||
android:layout_marginTop="38dp"
|
||||
android:text="@{String.format("\u00D7 %d", item.chargepoint.count)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "\u00D7 %d", item.chargepoint.count)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:goneUnless="@{item.status == null}"
|
||||
app:layout_constraintStart_toStartOf="@+id/imageView"
|
||||
@@ -63,7 +65,7 @@
|
||||
android:layout_marginTop="30dp"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:padding="2dp"
|
||||
android:text="@{String.format("%s/%d", BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "%s/%d", BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@android:color/white"
|
||||
app:backgroundTintAvailability="@{item.status}"
|
||||
@@ -79,7 +81,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="36dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@{item != null ? UtilsKt.nameForPlugType(ChargepointApiKt.stringProvider(context), item.chargepoint.type) + " · " + item.chargepoint.formatPower() : null}"
|
||||
android:text="@{item != null ? UtilsKt.nameForPlugType(ChargepointApiKt.stringProvider(context), item.chargepoint.type) + " · " + item.chargepoint.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context)) : null}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:goneUnless="@{item.chargepoint.hasKnownPower()}"
|
||||
app:layout_constraintBottom_toTopOf="@id/textView8"
|
||||
|
||||
@@ -48,11 +48,14 @@
|
||||
tools:orientation="horizontal" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView2"
|
||||
android:id="@+id/tvChargeFromTo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?selectableItemBackground"
|
||||
android:text="@{String.format(@string/chargeprice_battery_range, vm.batteryRange[0], vm.batteryRange[1])}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:textColor="?colorPrimary"
|
||||
@@ -68,8 +71,8 @@
|
||||
android:text="@{@string/chargeprice_stats(vm.chargepriceMetaForChargepoint.data.energy, BindingAdaptersKt.time((int) Math.round(vm.chargepriceMetaForChargepoint.data.duration)), vm.chargepriceMetaForChargepoint.data.energy / vm.chargepriceMetaForChargepoint.data.duration * 60)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:invisibleUnlessAnimated="@{!vm.batteryRangeSliderDragging && vm.chargepriceMetaForChargepoint.status == Status.SUCCESS}"
|
||||
app:layout_constraintStart_toStartOf="@+id/textView2"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView2"
|
||||
app:layout_constraintStart_toStartOf="@+id/tvChargeFromTo"
|
||||
app:layout_constraintTop_toBottomOf="@+id/tvChargeFromTo"
|
||||
tools:text="(18 kWh, approx. 23 min, ⌀ 50 kW)" />
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
android:layout_marginBottom="16dp"
|
||||
tools:ignore="WebViewLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
@@ -18,76 +19,27 @@
|
||||
android:text="@string/referrals"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:textColor="?colorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@+id/textView21"
|
||||
app:layout_constraintBottom_toTopOf="@+id/referralWebView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView21"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/referrals_info"
|
||||
app:layout_constraintBottom_toTopOf="@+id/referral_tesla"
|
||||
app:layout_constraintStart_toStartOf="@+id/textView20"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20" />
|
||||
|
||||
<androidx.constraintlayout.helper.widget.Flow
|
||||
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_eprimo,referral_ewieeinfach"
|
||||
app:flow_horizontalGap="16dp"
|
||||
app:flow_horizontalStyle="packed"
|
||||
app:flow_verticalAlign="baseline"
|
||||
app:flow_wrapMode="chain"
|
||||
app:layout_constraintBottom_toTopOf="@+id/referralWebView"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView21" />
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_tesla"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
<WebView
|
||||
android:id="@+id/referralWebView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_tesla"
|
||||
android:visibility="gone"
|
||||
app:icon="@drawable/ic_tesla" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_juicify"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_juicify" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_geldfuereauto"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_geldfuereauto" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_maingau"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_ewieeinfach" />
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView21" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -21,6 +21,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="24dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
app:piv_rtl_mode="auto"
|
||||
app:piv_animationType="worm"
|
||||
app:piv_dynamicCount="true"
|
||||
app:piv_interactiveAnimation="true"
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
android:layout_marginBottom="24dp"
|
||||
android:paddingStart="16dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnGetStarted"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<data>
|
||||
|
||||
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
|
||||
<import type="net.vonforst.evmap.adapter.ConnectorAdapter.ChargepointWithAvailability" />
|
||||
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
|
||||
|
||||
@@ -40,7 +42,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="38dp"
|
||||
android:layout_marginTop="38dp"
|
||||
android:text="@{String.format("\u00D7 %d", item.chargepoint.count)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "\u00D7 %d", item.chargepoint.count)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:layout_constraintStart_toStartOf="@+id/imageView"
|
||||
app:layout_constraintTop_toTopOf="@+id/imageView"
|
||||
@@ -54,7 +56,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="30dp"
|
||||
android:text="@{String.format("%s/%d", BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "%s/%d", BindingAdaptersKt.availabilityText(item.status), item.chargepoint.count)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:padding="2dp"
|
||||
@@ -72,7 +74,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@{item.chargepoint.formatPower()}"
|
||||
android:text="@{item.chargepoint.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:goneUnless="@{item.chargepoint.hasKnownPower()}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
|
||||
|
||||
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
|
||||
|
||||
<variable
|
||||
name="item"
|
||||
type="Chargepoint" />
|
||||
@@ -51,7 +53,7 @@
|
||||
android:layout_marginStart="38dp"
|
||||
android:layout_marginTop="38dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:text="@{String.format("× %d", item.count)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "× %d", item.count)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -65,7 +67,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@{item.formatPower()}"
|
||||
android:text="@{item.formatPower(LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@{BindingAdaptersKt.colorEnabled(context, enabled)}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:text="@{item.text}"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
@@ -55,6 +56,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="14dp"
|
||||
android:text="@{item.detailText}"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:linkify="@{item.links ? Linkify.WEB_URLS | Linkify.PHONE_NUMBERS : 0}"
|
||||
app:goneUnless="@{item.detailText != null}"
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
<import type="net.vonforst.evmap.api.UtilsKt" />
|
||||
|
||||
<import type="com.github.erfansn.localeconfigx.LocaleConfigXKt" />
|
||||
|
||||
<import type="net.vonforst.evmap.viewmodel.Status" />
|
||||
|
||||
<import type="net.vonforst.evmap.ui.BindingAdaptersKt" />
|
||||
@@ -61,6 +63,7 @@
|
||||
app:layout_constraintEnd_toStartOf="@+id/textView16"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:textAlignment="viewStart"
|
||||
tools:text="Nikola-Tesla-Parkhaus mit extra langem Namen, der auf mehrere Zeilen umbricht" />
|
||||
|
||||
<TextView
|
||||
@@ -72,6 +75,7 @@
|
||||
android:maxLines="1"
|
||||
android:text="@{item.charger.address.toString()}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textAlignment="viewStart"
|
||||
app:invisibleUnless="@{item.charger.address != null}"
|
||||
app:layout_constraintEnd_toStartOf="@+id/textView7"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@@ -85,8 +89,9 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@{item.charger.formatChargepoints(ChargepointApiKt.stringProvider(context))}"
|
||||
android:text="@{item.charger.formatChargepoints(ChargepointApiKt.stringProvider(context), LocaleConfigXKt.getCurrentOrDefaultLocale(context))}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintEnd_toStartOf="@+id/textView7"
|
||||
app:layout_constraintStart_toStartOf="@+id/textView2"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView2"
|
||||
@@ -111,7 +116,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/rounded_rect"
|
||||
android:padding="2dp"
|
||||
android:text="@{String.format("%s/%d", BindingAdaptersKt.availabilityText(item.available.data), item.total)}"
|
||||
android:text="@{String.format(LocaleConfigXKt.getCurrentOrDefaultLocale(context), "%s/%d", BindingAdaptersKt.availabilityText(item.available.data), item.total)}"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="@android:color/white"
|
||||
app:backgroundTintAvailability="@{item.available.data}"
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@{item.filter.name}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@+id/switch1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@{item.filter.name}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="Connectors" />
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@{item.filter.name}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnEdit"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
@@ -61,6 +62,7 @@
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@{item.value.all ? @string/all_selected : @string/number_selected(item.value.values.size())}"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintEnd_toStartOf="@+id/btnEdit"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView17"
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/map_type"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:textAlignment="viewStart"
|
||||
app:layout_constraintEnd_toStartOf="@id/btnClose"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
@@ -52,7 +53,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@{vm.mapType.equals(AnyMap.Type.NORMAL)}"
|
||||
android:onClick="@{() -> vm.setMapType(AnyMap.Type.NORMAL)}"
|
||||
android:text="@string/map_type_normal" />
|
||||
android:text="@string/map_type_normal"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbSatellite"
|
||||
@@ -60,7 +62,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@{vm.mapType.equals(AnyMap.Type.HYBRID)}"
|
||||
android:onClick="@{() -> vm.setMapType(AnyMap.Type.HYBRID)}"
|
||||
android:text="@string/map_type_satellite" />
|
||||
android:text="@string/map_type_satellite"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/rbTerrain"
|
||||
@@ -68,7 +71,8 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="@{vm.mapType.equals(AnyMap.Type.TERRAIN)}"
|
||||
android:onClick="@{() -> vm.setMapType(AnyMap.Type.TERRAIN)}"
|
||||
android:text="@string/map_type_terrain" />
|
||||
android:text="@string/map_type_terrain"
|
||||
android:textAlignment="viewStart" />
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
@@ -80,6 +84,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/map_details"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:textAlignment="viewStart"
|
||||
app:goneUnless="@{vm.mapTrafficSupported}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
@@ -95,6 +100,7 @@
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/map_traffic"
|
||||
android:checked="@={vm.mapTrafficEnabled}"
|
||||
android:textAlignment="viewStart"
|
||||
app:goneUnless="@{vm.mapTrafficSupported}"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
<string name="no_browser_app_found">Nejprve si nainstalujte webový prohlížeč</string>
|
||||
<string name="address">Adresa</string>
|
||||
<string name="hours">Otevírací doba</string>
|
||||
<string name="open_247"><b>Otevřeno 24/7</b></string>
|
||||
<string name="closed"><b>Zavřeno</b></string>
|
||||
<string name="open_closesat"><b>Otevřeno</b> · Zavírá v %s</string>
|
||||
<string name="closed_opensat"><b>Zavřeno</b> · Otevírá v %s</string>
|
||||
<string name="open_247"><![CDATA[<b>Otevřeno 24/7</b>]]></string>
|
||||
<string name="closed"><![CDATA[<b>Zavřeno</b>]]></string>
|
||||
<string name="open_closesat"><![CDATA[<b>Otevřeno</b> · Zavírá v %s]]></string>
|
||||
<string name="closed_opensat"><![CDATA[<b>Zavřeno</b> · Otevírá v %s]]></string>
|
||||
<string name="cost">Cena</string>
|
||||
<string name="cost_detail"><b>Nabíjení:</b> %1$s · <b>Parkování:</b> %2$s</string>
|
||||
<string name="cost_detail_charging"><b>%s nabíjení</b></string>
|
||||
<string name="cost_detail_parking"><b>%s parkování</b></string>
|
||||
<string name="cost_detail"><![CDATA[<b>Nabíjení:</b> %1$s · <b>Parkování:</b> %2$s]]></string>
|
||||
<string name="cost_detail_charging"><![CDATA[<b>%s nabíjení</b>]]></string>
|
||||
<string name="cost_detail_parking"><![CDATA[<b>%s parkování</b>]]></string>
|
||||
<string name="charging_free">Bezplatné</string>
|
||||
<string name="charging_paid">Placené</string>
|
||||
<string name="parking_free">Bezplatné</string>
|
||||
@@ -177,7 +177,7 @@
|
||||
<string name="crash_report_comment_prompt">Níže můžete přidat komentář:</string>
|
||||
<string name="powered_by_mapbox">používá službu Mapbox</string>
|
||||
<string name="pref_search_provider">Poskytovatel vyhledávání</string>
|
||||
<string name="pref_search_provider_info">Načtení dat pro vyhledávání bývá drahé, obzvláště z Map Google. Zvažte prosím poslání finančního daru v nabídce „O aplikaci“ → „Přispět“.</string>
|
||||
<string name="pref_search_provider_info"><![CDATA[Načtení dat pro vyhledávání bývá drahé, obzvláště z Map Google. Zvažte prosím poslání finančního daru v nabídce „O aplikaci“ → „Přispět“.]]></string>
|
||||
<string name="github_sponsors">GitHub Sponsors</string>
|
||||
<string name="donate_desc">Podpořte vývoj aplikace EVMap jednorázovým darem</string>
|
||||
<string name="github_sponsors_desc">Podpořte EVMap ve službě GitHub Sponsors</string>
|
||||
@@ -281,7 +281,7 @@
|
||||
<string name="loading">Načítání…</string>
|
||||
<string name="auto_multipage_goto">Stránka %d</string>
|
||||
<string name="reload">Obnovit</string>
|
||||
<string name="accept_privacy"><![CDATA[Přečetl/a jsem si a souhlasím se <a href="%s">zásadami ochrany osobních údajů</a> aplikace EVMap.]]></string>
|
||||
<string name="accept_privacy"><![CDATA[Přečetl/a jsem si a souhlasím se <a href=\"%s\">zásadami ochrany osobních údajů</a> aplikace EVMap.]]></string>
|
||||
<string name="referrals">Referenční odkazy</string>
|
||||
<string name="referrals_info">Pro podpoření vývojáře svým nákupem můžete také použít jeden z referenčních odkazů níže.</string>
|
||||
<string name="generic_connection_error">Nepodařilo se načíst data</string>
|
||||
@@ -344,7 +344,7 @@
|
||||
<string name="chargeprice_connection_error">Nepodařilo se načíst ceny</string>
|
||||
<string name="unknown_operator">Neznámý operátor</string>
|
||||
<string name="data_source_goingelectric">GoingElectric.de</string>
|
||||
<string name="data_source_openchargemap_desc">Celosvětové, s různou kvalitou. Popisy jsou v angličtině nebo v místním jazyce. Spravováno komunitou, v některých zemích obsahuje vládní data (např. Severní Amerika, Spojené království, Francie, Norsko).</string>
|
||||
<string name="data_source_openchargemap_desc"><![CDATA[Celosvětové, s různou kvalitou. Popisy jsou v angličtině nebo v místním jazyce. Spravováno komunitou, v některých zemích obsahuje vládní data (např. Severní Amerika, Spojené království, Francie, Norsko).]]></string>
|
||||
<string name="privacy_link">https://ev-map.app/privacypolicy/</string>
|
||||
<string name="chargeprice_faq_link">https://ev-map.app/faq/#price-comparison-feature</string>
|
||||
<string name="pref_darkmode_always_on">vždy zapnut</string>
|
||||
@@ -372,4 +372,5 @@
|
||||
<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>
|
||||
<string name="pref_provider_osm">OpenStreetMap</string>
|
||||
<string name="filterprofile_name_not_unique">Již existuje profil filtru s tímto názvem</string>
|
||||
</resources>
|
||||
@@ -101,6 +101,7 @@
|
||||
<string name="pref_language">App-Sprache</string>
|
||||
<string name="pref_darkmode">Dunkles Design</string>
|
||||
<string name="connection_error">Ladesäulen konnten nicht geladen werden</string>
|
||||
<string name="zoom_in_to_see_more">Hineinzoomen um alle Ladestationen zu sehen</string>
|
||||
<string name="location_error">Standort nicht erkannt. Bitte Systemeinstellungen prüfen</string>
|
||||
<string name="retry">Wiederholen</string>
|
||||
<string name="filter_open_247">24 Stunden geöffnet</string>
|
||||
@@ -110,7 +111,9 @@
|
||||
<string name="and_n_others">und %d weitere</string>
|
||||
<string name="pref_map_provider">Kartenanbieter</string>
|
||||
<string name="twitter">Twitter</string>
|
||||
<string name="mastodon">Mastodon</string>
|
||||
<string name="goingelectric_forum">Forenthread bei GoingElectric.de</string>
|
||||
<string name="tff_forum">Forenthread im TFF-Forum</string>
|
||||
<string name="contact">Kontakt</string>
|
||||
<string name="menu_report_new_charger">Ladesäule melden</string>
|
||||
<string name="edit_at_datasource">Bei %s bearbeiten</string>
|
||||
@@ -151,6 +154,7 @@
|
||||
<string name="delete">Löschen</string>
|
||||
<string name="save_as_profile">Als Profil speichern</string>
|
||||
<string name="save_profile_enter_name">Gib den Namen des Filterprofils ein:</string>
|
||||
<string name="filterprofile_name_not_unique">Ein Filterprofil mit diesem Namen existiert bereits</string>
|
||||
<string name="filterprofiles_empty_state">Du hast keine Filterprofile gespeichert</string>
|
||||
<string name="welcome_to_evmap">Willkommen bei EVMap</string>
|
||||
<string name="welcome_1">Finde Ladestationen für Elektroautos in deiner Nähe</string>
|
||||
@@ -232,7 +236,7 @@
|
||||
<string name="crash_report_comment_prompt">Du kannst unten noch einen Kommentar hinzufügen:</string>
|
||||
<string name="powered_by_mapbox">powered by Mapbox</string>
|
||||
<string name="pref_search_provider">Anbieter für Ortssuche</string>
|
||||
<string name="pref_search_provider_info">Die Daten für die Ortssuche, vor allem von Google Maps, sind relativ teuer. Über eine Spende unter \"Über EVMap -> Spenden\" würde ich mich sehr freuen.</string>
|
||||
<string name="pref_search_provider_info"><![CDATA[Die Daten für die Ortssuche, vor allem von Google Maps, sind relativ teuer. Über eine Spende unter „Über EVMap → Spenden“ würde ich mich sehr freuen.]]></string>
|
||||
<string name="github_sponsors">GitHub Sponsors</string>
|
||||
<string name="donate_desc">Unterstütze die Weiterentwicklung von EVMap mit einer einmaligen Spende</string>
|
||||
<string name="github_sponsors_desc">Unterstütze EVMap über GitHub Sponsors</string>
|
||||
@@ -240,6 +244,7 @@
|
||||
<string name="privacy_link">https://ev-map.app/de/privacypolicy/</string>
|
||||
<string name="faq_link">https://ev-map.app/de/faq/</string>
|
||||
<string name="chargeprice_faq_link">https://ev-map.app/de/faq/#preisvergleichsfunktion</string>
|
||||
<string name="referral_link">https://ev-map.app/de/referrals/</string>
|
||||
<string name="required">erforderlich</string>
|
||||
<string name="edit_filter_profile">„%s“ bearbeiten</string>
|
||||
<string name="pref_search_delete_recent">Suchverlauf löschen</string>
|
||||
|
||||
4
app/src/main/res/values-ldrtl-w960dp/dimens.xml
Normal file
4
app/src/main/res/values-ldrtl-w960dp/dimens.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="directions_fab_translationx">44dp</dimen>
|
||||
</resources>
|
||||
@@ -148,7 +148,7 @@
|
||||
<string name="pref_data_source">Fonte da informação</string>
|
||||
<string name="data_source_openchargemap">Open Charge Map</string>
|
||||
<string name="next">próximo</string>
|
||||
<string name="data_source_openchargemap_desc">Mundial, com vários níveis de qualidade. Descrições em inglês ou língua local. Mantido pela comunidade e usa informação governamental publica em alguns países (ex: América do Norte, Reino Unido, França, Noruega, etc).</string>
|
||||
<string name="data_source_openchargemap_desc"><![CDATA[Mundial, com vários níveis de qualidade. Descrições em inglês ou língua local. Mantido pela comunidade e usa informação governamental publica em alguns países (ex: América do Norte, Reino Unido, França, Noruega, etc).]]></string>
|
||||
<string name="get_started">Começar</string>
|
||||
<string name="lets_go">Vamos lá</string>
|
||||
<string name="crash_report_text">O EVMap encontrou um problema. Por favor envie um relatório do erro para o criador da app.</string>
|
||||
@@ -160,7 +160,7 @@
|
||||
<string name="pref_map_rotate_gestures_on">Use dois dedos para girar o mapa</string>
|
||||
<string name="pref_map_rotate_gestures_off">Rotação desligada (norte sempre para cima)</string>
|
||||
<string name="refresh_live_data">atualizar estado em tempo real</string>
|
||||
<string name="pref_search_provider_info">As pesquisas são caras, especialmente se o Google Maps for utilizado. Por favor considere doar através de \"Sobre\" → \"Doar\".</string>
|
||||
<string name="pref_search_provider_info"><![CDATA[As pesquisas são caras, especialmente quando o Google Maps é utilizado. Por favor considere doar através de "Sobre" → "Doar".]]></string>
|
||||
<string name="github_sponsors_desc">Apoie o EVMap através do GitHub</string>
|
||||
<string name="unnamed_filter_profile">Filtro sem nome</string>
|
||||
<string name="deleted_recent_search_results">As pesquisas recentes foram eliminadas</string>
|
||||
@@ -205,18 +205,18 @@
|
||||
<string name="operator">Operador</string>
|
||||
<string name="network">Rede</string>
|
||||
<string name="hours">Horário de abertura</string>
|
||||
<string name="open_247"><b>Aberto 24/7</b></string>
|
||||
<string name="closed"><b>Fechado</b></string>
|
||||
<string name="open_closesat"><b>Aberto</b> · Fecha às %s</string>
|
||||
<string name="closed_opensat"><b>Fechado</b> · Abre às %s</string>
|
||||
<string name="open_247"><![CDATA[<b>Aberto 24/7</b>]]></string>
|
||||
<string name="closed"><![CDATA[<b>Fechado</b>]]></string>
|
||||
<string name="open_closesat"><![CDATA[<b>Aberto</b> · Fecha às %s]]></string>
|
||||
<string name="closed_opensat"><![CDATA[<b>Fechado</b> · Abre às %s]]></string>
|
||||
<string name="app_name">EVMap</string>
|
||||
<string name="title_activity_maps">EVMap</string>
|
||||
<string name="closed_unfmt">Fechado</string>
|
||||
<string name="holiday">Feriado</string>
|
||||
<string name="cost">Custo</string>
|
||||
<string name="cost_detail"><b>Carregamento:</b> %1$s · <b>Parque:</b> %2$s</string>
|
||||
<string name="cost_detail_charging"><b>Carregamento %s</b></string>
|
||||
<string name="cost_detail_parking"><b>Parque %s</b></string>
|
||||
<string name="cost_detail"><![CDATA[<b>Carregamento:</b> %1$s · <b>Parque:</b> %2$s]]></string>
|
||||
<string name="cost_detail_charging"><![CDATA[<b>Carregamento %s</b>]]></string>
|
||||
<string name="cost_detail_parking"><![CDATA[<b>Parque %s</b>]]></string>
|
||||
<string name="charging_free">Gratuito</string>
|
||||
<string name="charging_paid">Pago</string>
|
||||
<string name="parking_free">Gratuito</string>
|
||||
@@ -372,4 +372,5 @@
|
||||
<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>
|
||||
<string name="pref_provider_osm">OpenStreetMap</string>
|
||||
<string name="filterprofile_name_not_unique">Já existe um filtro com este nome</string>
|
||||
</resources>
|
||||
@@ -5,7 +5,10 @@
|
||||
<string name="github_link">https://github.com/ev-map/EVMap</string>
|
||||
<string name="twitter_handle">\@ev_map</string>
|
||||
<string name="twitter_url">https://twitter.com/ev_map</string>
|
||||
<string name="mastodon_handle">\@evmap\@electroverse.tech</string>
|
||||
<string name="mastodon_url">https://electroverse.tech/@evmap</string>
|
||||
<string name="goingelectric_forum_url"><![CDATA[https://www.goingelectric.de/forum/viewtopic.php?f=5&t=56342]]></string>
|
||||
<string name="tff_forum_url"><![CDATA[https://tff-forum.de/t/283834]]></string>
|
||||
<string name="github_sponsors_link">https://github.com/sponsors/johan12345/</string>
|
||||
<string name="chargeprice_api_url">https://api.chargeprice.app/v1/</string>
|
||||
<string name="fronyx_url">https://fronyx.io/</string>
|
||||
@@ -27,17 +30,6 @@
|
||||
<string name="hide_on_scroll_fab_behavior">net.vonforst.evmap.ui.HideOnScrollFabBehavior</string>
|
||||
<string name="paypal_link" translatable="false">https://paypal.me/johan98</string>
|
||||
<string name="donate_link" translatable="false">https://ev-map.app/donate/</string>
|
||||
<string name="tesla_referral_link" translatable="false">http://ts.la/johan94494</string>
|
||||
<string name="juicify_referral_link" translatable="false">https://trck.juicify.green/trck/eclick/9dba357fbfed1e82fb05c7ec004ee2972ea174ce46d8ae0d</string>
|
||||
<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="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>
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
<string name="pref_language">App language</string>
|
||||
<string name="pref_darkmode">Dark mode</string>
|
||||
<string name="connection_error">Could not load charging stations</string>
|
||||
<string name="zoom_in_to_see_more">Zoom in to see all charging stations</string>
|
||||
<string name="location_error">Failed to detect location. Please check system settings</string>
|
||||
<string name="retry">Retry</string>
|
||||
<string name="filter_open_247">Available 24/7</string>
|
||||
@@ -110,7 +111,9 @@
|
||||
<string name="and_n_others">and %d others</string>
|
||||
<string name="pref_map_provider">Map provider</string>
|
||||
<string name="twitter">Twitter</string>
|
||||
<string name="mastodon">Mastodon</string>
|
||||
<string name="goingelectric_forum">Forum thread at GoingElectric.de</string>
|
||||
<string name="tff_forum">Forum thread at TFF-Forum.de</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="menu_report_new_charger">New charger</string>
|
||||
<string name="edit_at_datasource">Edit at %s</string>
|
||||
@@ -151,6 +154,7 @@
|
||||
<string name="delete">Delete</string>
|
||||
<string name="save_as_profile">Save as profile</string>
|
||||
<string name="save_profile_enter_name">Enter the name of the filter profile:</string>
|
||||
<string name="filterprofile_name_not_unique">There is already a filter profile with that name</string>
|
||||
<string name="filterprofiles_empty_state">You have no filter profiles saved</string>
|
||||
<string name="welcome_to_evmap">Welcome to EVMap</string>
|
||||
<string name="welcome_1">Find electric vehicle chargers around you</string>
|
||||
@@ -240,6 +244,7 @@
|
||||
<string name="privacy_link">https://ev-map.app/privacypolicy/</string>
|
||||
<string name="faq_link">https://ev-map.app/faq/</string>
|
||||
<string name="chargeprice_faq_link">https://ev-map.app/faq/#price-comparison-feature</string>
|
||||
<string name="referral_link">https://ev-map.app/referrals/</string>
|
||||
<string name="required">required</string>
|
||||
<string name="edit_filter_profile">Edit “%s”</string>
|
||||
<string name="pref_search_delete_recent">Delete recent search results</string>
|
||||
|
||||
@@ -39,6 +39,10 @@
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/contact">
|
||||
<Preference
|
||||
android:key="mastodon"
|
||||
android:title="@string/mastodon"
|
||||
android:summary="@string/mastodon_handle" />
|
||||
|
||||
<Preference
|
||||
android:key="twitter"
|
||||
@@ -49,6 +53,10 @@
|
||||
android:key="goingelectric"
|
||||
android:title="@string/goingelectric_forum" />
|
||||
|
||||
<Preference
|
||||
android:key="tffforum"
|
||||
android:title="@string/tff_forum" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/other">
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
android:defaultValue="goingelectric"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
<!--<CheckBoxPreference
|
||||
android:key="prediction_enabled"
|
||||
android:title="@string/pref_prediction_enabled"
|
||||
android:defaultValue="true"
|
||||
android:summary="@string/pref_prediction_enabled_summary" />
|
||||
android:summary="@string/pref_prediction_enabled_summary" />-->
|
||||
|
||||
<Preference
|
||||
android:key="tesla_account"
|
||||
|
||||
2
fastlane/metadata/android/de-DE/changelogs/244.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/244.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Fehler behoben:
|
||||
- Abstürze behoben
|
||||
2
fastlane/metadata/android/de-DE/changelogs/246.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/246.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Fehler behoben:
|
||||
- Anzeigefehler behoben
|
||||
5
fastlane/metadata/android/de-DE/changelogs/248.txt
Normal file
5
fastlane/metadata/android/de-DE/changelogs/248.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Verbesserungen:
|
||||
- Preisvergleich: Zurücksetzen der Ladebereichsauswahl durch Tippen auf den Titel darüber
|
||||
|
||||
Fehler behoben:
|
||||
- Anzeigefehler behoben
|
||||
2
fastlane/metadata/android/de-DE/changelogs/250.txt
Normal file
2
fastlane/metadata/android/de-DE/changelogs/250.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Fehler behoben:
|
||||
- Anzeigefehler behoben
|
||||
3
fastlane/metadata/android/de-DE/changelogs/252.txt
Normal file
3
fastlane/metadata/android/de-DE/changelogs/252.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Fehler behoben:
|
||||
- Anzeigefehler behoben
|
||||
- Abstürze behoben
|
||||
2
fastlane/metadata/android/en-US/changelogs/244.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/244.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Bugfixes:
|
||||
- Fixed crashes
|
||||
2
fastlane/metadata/android/en-US/changelogs/246.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/246.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Bugfixes:
|
||||
- Fixed display errors
|
||||
5
fastlane/metadata/android/en-US/changelogs/248.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/248.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
Improvements:
|
||||
- Price comparison: Reset charging range by tapping on title above it
|
||||
|
||||
Bugfixes:
|
||||
- Fixed display errors
|
||||
2
fastlane/metadata/android/en-US/changelogs/250.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/250.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Bugfixes:
|
||||
- Fixed display errors
|
||||
3
fastlane/metadata/android/en-US/changelogs/252.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/252.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Bugfixes:
|
||||
- Fixed display errors
|
||||
- Fixed crashes
|
||||
@@ -12,7 +12,7 @@
|
||||
# org.gradle.parallel=true
|
||||
#Sun Jul 24 11:49:27 CEST 2022
|
||||
kotlin.code.style=official
|
||||
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.jvmargs=-Xmx4096M -Dkotlin.daemon.jvm.options\="-Xmx4096M"
|
||||
android.useAndroidX=true
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=true
|
||||
|
||||
Reference in New Issue
Block a user