Compare commits

...

40 Commits

Author SHA1 Message Date
johan12345
c4c3aba7c7 Release 1.9.14 2025-03-15 15:48:53 +01:00
johan12345
890af2ddef try to better handle situations where map bounds cross the Antimeridian 2025-03-12 22:04:12 +01:00
johan12345
ba0b36b3ec update AnyMaps 2025-03-12 21:11:32 +01:00
johan12345
161b48789f Chargeprice: reset to default charging range when tapping title 2025-03-09 23:07:37 +01:00
johan12345
042b983aa3 CI: run checksec on release APKs 2025-03-04 22:11:32 +01:00
johan12345
1c21da7be0 CI: move apikeys-ci.xml to _ci folder 2025-03-04 21:24:19 +01:00
johan12345
405baed0f7 upgrade android-spatialite 2025-03-04 21:24:05 +01:00
johan12345
19c0d57f2b upgrade maplibre 2025-03-04 20:21:19 +01:00
johan12345
42c2a2f72a improve setLinkify BindingAdapter
fixes #371
2025-02-26 21:21:12 +01:00
johan12345
36ee3ff231 CarAppService: ignore if starting foreground service fails
this happens on AAOS API 34+ due to https://developer.android.com/develop/background-work/services/fgs/restrictions-bg-start. However, the app still works even without the foreground service.
2025-02-26 20:08:23 +01:00
johan12345
883735ef05 FusionEngine: change log level 2025-02-26 20:00:22 +01:00
johan12345
4c68356ae9 Release 1.9.13 2025-02-18 21:56:07 +01:00
johan12345
7fde5b50aa fix disappearing markers
fixes #368
2025-02-18 21:49:55 +01:00
johan12345
7c4136c66d Release 1.9.12 2025-02-07 22:08:41 +01:00
johan12345
6e56f5c3ff update social links 2025-02-07 22:05:23 +01:00
johan12345
017be6f31a increase heap space 2025-02-07 22:02:11 +01:00
johan12345
b398a5dc81 embed referral links as webpage instead of native Android buttons 2025-02-07 19:13:58 +01:00
johan12345
3fb0dec868 Release 1.9.11 (only for Harman Ignite) 2025-01-28 20:58:23 +01:00
johan12345
8c4de115ec remove setting for fronyx predictions in AAOS app
see also e7c9432191
2025-01-28 20:51:05 +01:00
johan12345
334b68cf5e SearchSelectScreen: fix displaying selectAll buttons 2025-01-27 23:40:37 +01:00
johan12345
788c68c9dd Onboarding Android Auto page: fix icon disappearing 2025-01-18 23:43:14 +01:00
johan12345
7842a15529 fix more possible memory leak issues 2025-01-07 20:42:22 +01:00
johan12345
e7c9432191 Disable fronyx predictions
API has been broken for >4 months now
2024-12-03 22:18:47 +01:00
johan12345
76b6abd3ca Release 1.9.10 (only for Faurecia Aptoide) 2024-11-24 18:32:51 +01:00
johan12345
752c184146 fix crash on first start 2024-11-24 18:13:39 +01:00
johan12345
5471ac5073 Release 1.9.9 (only for Faurecia Aptoide) 2024-11-13 20:31:33 +01:00
johan12345
69ae13a199 fix crash on first start 2024-11-13 20:30:58 +01:00
johan12345
8a2e2d9a25 release 1.9.8 (only for Faurecia Aptoide) 2024-11-09 13:56:06 +01:00
johan12345
fe69a78b94 fix possible memory leak issues 2024-11-08 20:50:06 +01:00
johan12345
2663bd7964 update MapLibre & AnyMaps 2024-10-26 22:30:25 +02:00
johan12345
3b54b2799f Databinding: use viewLifecycleOwner instead of Fragment as lifecycle owner 2024-10-26 22:25:08 +02:00
johan12345
3a24711626 translations: move nb-rNO to nb 2024-10-25 22:03:52 +02:00
johan12345
c158744bc2 fix unnecessary recreation of MapFragment 2024-10-23 22:51:15 +02:00
johan12345
c01033a036 upgrade AnyMaps 2024-10-23 22:07:33 +02:00
Hosted Weblate
16474c3864 Translated using Weblate (Portuguese)
Currently translated at 100.0% (358 of 358 strings)

Co-authored-by: Celso Azevedo <mail@celsoazevedo.com>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/pt/
Translation: EVMap/Android
2024-10-17 18:55:26 +02:00
Hosted Weblate
7ce2f8d452 Translated using Weblate (Czech)
Currently translated at 100.0% (358 of 358 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (356 of 356 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (355 of 355 strings)

Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/cs/
Translation: EVMap/Android
2024-10-17 18:55:26 +02:00
Hosted Weblate
28df158d94 Translated using Weblate (German)
Currently translated at 100.0% (358 of 358 strings)

Co-authored-by: mcliquid <info@mcliquid.de>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/de/
Translation: EVMap/Android
2024-10-17 18:43:40 +02:00
johan12345
90b3645a0b fix crash when trying to rename a filter profile to a name that already exists 2024-10-15 20:20:27 +02:00
johan12345
de901aa825 Release 1.9.7 (only for Faurecia Aptoide) 2024-08-16 19:13:30 +02:00
johan12345
2ce61f2f6b AAOS: more navigateToCharger fixes 2024-08-16 19:12:34 +02:00
46 changed files with 489 additions and 264 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 = 248
versionName = "1.9.14"
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
@@ -315,7 +321,7 @@ dependencies {
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 +329,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 +354,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 +376,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")

View File

@@ -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 {

View File

@@ -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)

View File

@@ -44,14 +44,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 +67,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 +234,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 +244,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(
@@ -262,16 +259,15 @@ class MapsActivity : AppCompatActivity(),
if (intent.resolveActivity(packageManager) != null) {
startActivity(intent)
} else {
val cb = fragmentCallback ?: return
Snackbar.make(
cb.getRootView(),
rootView,
R.string.no_maps_app_found,
Snackbar.LENGTH_SHORT
).show()
}
}
fun openUrl(url: String, preferBrowser: Boolean = true) {
fun openUrl(url: String, rootView: View, preferBrowser: Boolean = true) {
val pkg = CustomTabsClient.getPackageName(this, null)
val intent = CustomTabsIntent.Builder()
.setDefaultColorSchemeParams(
@@ -288,9 +284,8 @@ class MapsActivity : AppCompatActivity(),
try {
intent.launchUrl(this, Uri.parse(url))
} catch (e: ActivityNotFoundException) {
val cb = fragmentCallback ?: return
Snackbar.make(
cb.getRootView(),
rootView,
R.string.no_browser_app_found,
Snackbar.LENGTH_SHORT
).show()

View File

@@ -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)
}
}

View File

@@ -51,14 +51,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
@@ -92,7 +89,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
@@ -547,10 +545,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 +585,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 +647,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 +661,7 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
invalidate()
prediction = predictionRepo.getPredictionData(charger, availability)
//prediction = predictionRepo.getPredictionData(charger, availability)
invalidate()
} else {

View File

@@ -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(

View File

@@ -216,7 +216,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 +224,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(
@@ -825,13 +825,24 @@ 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, 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, carContext.getString(R.string.twitter_url))
}).build()
)
}
if (maxRows > 6) {
addItem(Row.Builder()
.setTitle(carContext.getString(R.string.goingelectric_forum))
@@ -844,6 +855,19 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) {
}).build()
)
}
if (maxRows > 7) {
addItem(
Row.Builder()
.setTitle(carContext.getString(R.string.tff_forum))
.setBrowsable(true)
.setOnClickListener(ParkedOnlyOnClickListener.create {
openUrl(
carContext,
carContext.getString(R.string.tff_forum_url)
)
}).build()
)
}
}.build(), carContext.getString(R.string.contact)))
addSectionedList(SectionedItemList.create(ItemList.Builder().apply {
addItem(Row.Builder()

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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
}
}
}
}

View File

@@ -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

View File

@@ -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) {}

View File

@@ -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))
}
}
})
})

View File

@@ -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
@@ -136,8 +137,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
@@ -211,9 +213,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 +223,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 +291,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 +366,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 +394,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 +425,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 +438,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)
}
}
binding.detailView.btnChargeprice.setOnClickListener {
@@ -448,12 +451,16 @@ 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,
false
)
}
}
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 +468,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 +508,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)
if (vm.apiId.value == "goingelectric") {
// instructions specific to GoingElectric
Toast.makeText(
@@ -917,10 +924,10 @@ 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)
}
R.drawable.ic_payment -> {
@@ -928,7 +935,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 +1059,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 +1073,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 +1094,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 +1122,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 +1140,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]
@@ -1536,10 +1547,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 +1596,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()

View File

@@ -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)

View File

@@ -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)
}
}

View File

@@ -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
@@ -145,6 +147,14 @@ class ChargeLocationsRepository(
filters: FilterValues?,
overrideCache: Boolean = false
): LiveData<Resource<List<ChargepointListItem>>> {
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) {
@@ -208,6 +218,32 @@ class ChargeLocationsRepository(
}
}
private fun combineLiveData(
liveDataA: LiveData<Resource<List<ChargepointListItem>>>,
liveDataB: LiveData<Resource<List<ChargepointListItem>>>
) = MediatorLiveData<Resource<List<ChargepointListItem>>>().apply {
listOf(liveDataA, liveDataB).forEach {
addSource(it) {
val valA = liveDataA.value
val valB = liveDataB.value
val combinedList = if (valA?.data != null && valB?.data != null) {
valA.data + valB.data
} else if (valA?.data != null) {
valA.data
} else if (valB?.data != null) {
valB.data
} 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,

View File

@@ -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
}
}

View File

@@ -9,8 +9,15 @@ 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.
@@ -152,4 +159,25 @@ fun Coordinate.formatDecimal(accuracy: Int = 6): String {
fun Location.formatDecimal(accuracy: Int = 6): String {
return "%.${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),
)
}

View File

@@ -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
}
}

View File

@@ -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
@@ -27,7 +25,6 @@ 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
@@ -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 }
@@ -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 {
@@ -471,14 +469,21 @@ 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
var west = sw.longitude - (ne.longitude - sw.longitude) * 0.25
var east = ne.longitude + (ne.longitude + sw.longitude) * 0.25
val south =
sw.latitude - (ne.latitude - sw.latitude) * 0.25 * cos(Math.toRadians(sw.latitude))
val north =
ne.latitude + (ne.latitude - sw.latitude) * 0.25 * 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() {

View File

@@ -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>

View File

@@ -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 &amp;&amp; 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

View File

@@ -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>

View File

@@ -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>

View File

@@ -110,7 +110,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 +153,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 +235,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 -&gt; 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 +243,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>

View File

@@ -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>

View File

@@ -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">©20202024 Johan von Forstner and contributors</string>
<string name="acra_backend_url" translatable="false">https://acra.muc.vonforst.net/report</string>
</resources>

View File

@@ -110,7 +110,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 +153,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 +243,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>

View File

@@ -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">

View File

@@ -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"

View File

@@ -0,0 +1,2 @@
Fehler behoben:
- Abstürze behoben

View File

@@ -0,0 +1,2 @@
Fehler behoben:
- Anzeigefehler behoben

View File

@@ -0,0 +1,5 @@
Verbesserungen:
- Preisvergleich: Zurücksetzen der Ladebereichsauswahl durch Tippen auf den Titel darüber
Fehler behoben:
- Anzeigefehler behoben

View File

@@ -0,0 +1,2 @@
Bugfixes:
- Fixed crashes

View File

@@ -0,0 +1,2 @@
Bugfixes:
- Fixed display errors

View File

@@ -0,0 +1,5 @@
Improvements:
- Price comparison: Reset charging range by tapping on title above it
Bugfixes:
- Fixed display errors

View File

@@ -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