mirror of
https://github.com/ev-map/EVMap.git
synced 2025-12-28 17:47:44 -05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7770e1c1b | ||
|
|
fcd51307cb | ||
|
|
ba4a9c29b2 | ||
|
|
3463177ad2 | ||
|
|
09deaf5080 | ||
|
|
23f429bbea | ||
|
|
1184d3b6cc | ||
|
|
c95a60807b | ||
|
|
4b8cf82843 | ||
|
|
f33b9e8117 | ||
|
|
cbc3040807 | ||
|
|
92619ea95e | ||
|
|
a7007284ff | ||
|
|
7fce566052 | ||
|
|
0c44b4b074 | ||
|
|
a652d96f74 | ||
|
|
8a9b3ad948 |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,2 +1,2 @@
|
||||
github: johan12345
|
||||
custom: 'https://paypal.me/johan98'
|
||||
custom: ['https://paypal.me/johan98', 'http://ts.la/johan94494']
|
||||
|
||||
1
.github/workflows/apikeys-ci.xml
vendored
1
.github/workflows/apikeys-ci.xml
vendored
@@ -5,4 +5,5 @@
|
||||
<string name="chargeprice_key" translatable="false">ci</string>
|
||||
<string name="openchargemap_key" translatable="false">ci</string>
|
||||
<string name="fronyx_key" translatable="false">ci</string>
|
||||
<string name="acra_credentials" translatable="false">ci:ci</string>
|
||||
</resources>
|
||||
|
||||
1
.github/workflows/release.yml
vendored
1
.github/workflows/release.yml
vendored
@@ -32,6 +32,7 @@ jobs:
|
||||
MAPBOX_API_KEY: ${{ secrets.MAPBOX_API_KEY }}
|
||||
GOOGLE_MAPS_API_KEY: ${{ secrets.GOOGLE_MAPS_API_KEY }}
|
||||
FRONYX_API_KEY: ${{ secrets.FRONYX_API_KEY }}
|
||||
ACRA_CRASHREPORT_CREDENTIALS: ${{ secrets.ACRA_CRASHREPORT_CREDENTIALS }}
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEYSTORE_ALIAS: ${{ secrets.KEYSTORE_ALIAS }}
|
||||
KEYSTORE_ALIAS_PASSWORD: ${{ secrets.KEYSTORE_ALIAS_PASSWORD }}
|
||||
|
||||
@@ -13,16 +13,14 @@ apply plugin: 'pt.jcosta.resourceplaceholders'
|
||||
def supportedLocales = "en,de,fr,nb-rNO,nl,pt,ro"
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
buildToolsVersion "30.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "net.vonforst.evmap"
|
||||
compileSdk 34
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
targetSdkVersion 34
|
||||
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
|
||||
versionCode 194
|
||||
versionName "1.6.7"
|
||||
versionCode 198
|
||||
versionName "1.6.8"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
resConfigs supportedLocales.split(',')
|
||||
@@ -147,6 +145,13 @@ android {
|
||||
if (fronyxKey != null) {
|
||||
variant.resValue "string", "fronyx_key", fronyxKey
|
||||
}
|
||||
def acraKey = env.ACRA_CRASHREPORT_CREDENTIALS ?: project.findProperty("ACRA_CRASHREPORT_CREDENTIALS")
|
||||
if (acraKey == null && project.hasProperty("ACRA_CRASHREPORT_CREDENTIALS_ENCRYPTED")) {
|
||||
acraKey = decode(project.findProperty("ACRA_CRASHREPORT_CREDENTIALS_ENCRYPTED"), "FmK.d,-f*p+rD+WK!eds")
|
||||
}
|
||||
if (acraKey != null) {
|
||||
variant.resValue "string", "acra_credentials", acraKey
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
@@ -168,13 +173,13 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation "androidx.activity:activity-ktx:1.7.2"
|
||||
implementation "androidx.fragment:fragment-ktx:1.5.7"
|
||||
implementation "androidx.fragment:fragment-ktx:1.6.1"
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation 'androidx.browser:browser:1.5.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'androidx.browser:browser:1.6.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.security:security-crypto:1.1.0-alpha06'
|
||||
implementation "androidx.work:work-runtime-ktx:2.8.1"
|
||||
@@ -197,7 +202,7 @@ dependencies {
|
||||
implementation 'com.github.romandanylyk:PageIndicatorView:b1bad589b5'
|
||||
|
||||
// Android Auto
|
||||
def carAppVersion = '1.3.0-rc01'
|
||||
def carAppVersion = '1.4.0-beta01'
|
||||
implementation "androidx.car.app:app:$carAppVersion"
|
||||
normalImplementation "androidx.car.app:app-projected:$carAppVersion"
|
||||
automotiveImplementation "androidx.car.app:app-automotive:$carAppVersion"
|
||||
@@ -219,8 +224,8 @@ dependencies {
|
||||
fossImplementation 'com.github.ev-map:mapbox-events-android:a21c324501'
|
||||
|
||||
// Google Places
|
||||
googleImplementation 'com.google.android.libraries.places:places:3.1.0'
|
||||
googleImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.6.4'
|
||||
googleImplementation 'com.google.android.libraries.places:places:3.2.0'
|
||||
googleImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.1'
|
||||
|
||||
// Mapbox Geocoding
|
||||
implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:5.5.0'
|
||||
@@ -235,20 +240,21 @@ dependencies {
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
|
||||
|
||||
// room library
|
||||
def room_version = "2.5.1"
|
||||
def room_version = "2.6.0-beta01"
|
||||
implementation "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
implementation "androidx.room:room-ktx:$room_version"
|
||||
implementation 'com.github.anboralabs:spatia-room:0.2.7'
|
||||
|
||||
// billing library
|
||||
def billing_version = "6.0.0"
|
||||
def billing_version = "6.0.1"
|
||||
googleImplementation "com.android.billingclient:billing:$billing_version"
|
||||
googleImplementation "com.android.billingclient:billing-ktx:$billing_version"
|
||||
|
||||
// ACRA (crash reporting)
|
||||
def acraVersion = "5.8.4"
|
||||
def acraVersion = "5.11.1"
|
||||
implementation("ch.acra:acra-mail:$acraVersion")
|
||||
implementation("ch.acra:acra-http:$acraVersion")
|
||||
implementation("ch.acra:acra-dialog:$acraVersion")
|
||||
implementation("ch.acra:acra-limiter:$acraVersion")
|
||||
|
||||
@@ -262,13 +268,12 @@ dependencies {
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.11.0"
|
||||
//noinspection GradleDependency
|
||||
testImplementation 'org.json:json:20080701'
|
||||
testImplementation 'org.robolectric:robolectric:4.9.2'
|
||||
testImplementation 'org.robolectric:robolectric:4.10.3'
|
||||
testImplementation 'androidx.test:core:1.5.0'
|
||||
testImplementation 'androidx.arch.core:core-testing:2.2.0'
|
||||
|
||||
// testing for car app
|
||||
testGoogleImplementation "androidx.car.app:app-testing:$carAppVersion"
|
||||
testGoogleImplementation 'org.robolectric:robolectric:4.9.2'
|
||||
testGoogleImplementation 'androidx.test:core:1.5.0'
|
||||
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
|
||||
@@ -42,5 +42,9 @@ class DonateFragment : Fragment() {
|
||||
binding.btnDonate.setOnClickListener {
|
||||
(activity as? MapsActivity)?.openUrl(getString(R.string.paypal_link))
|
||||
}
|
||||
|
||||
binding.referrals.referralTesla.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.tesla_referral_link))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/linearLayout2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/toolbar_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fitsSystemWindows="true"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
@@ -21,31 +19,55 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDonate"
|
||||
style="@style/Widget.Material3.Button.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/donate_paypal"
|
||||
app:icon="@drawable/ic_paypal"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20" />
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/donations_info"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnDonate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_container"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="match_parent">
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDonate"
|
||||
style="@style/Widget.Material3.Button.Icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/donate_paypal"
|
||||
app:icon="@drawable/ic_paypal"
|
||||
app:layout_constraintBottom_toTopOf="@id/referrals"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/donations_info"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnDonate"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<include
|
||||
android:id="@+id/referrals"
|
||||
layout="@layout/fragment_donate_referral"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="36dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/btnDonate" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@@ -11,6 +11,7 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
@@ -18,12 +19,17 @@ import com.google.android.material.transition.MaterialSharedAxis
|
||||
import net.vonforst.evmap.MapsActivity
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.adapter.DonationAdapter
|
||||
import net.vonforst.evmap.adapter.SingleViewAdapter
|
||||
import net.vonforst.evmap.databinding.FragmentDonateBinding
|
||||
import net.vonforst.evmap.databinding.FragmentDonateHeaderBinding
|
||||
import net.vonforst.evmap.databinding.FragmentDonateReferralBinding
|
||||
import net.vonforst.evmap.viewmodel.DonateViewModel
|
||||
|
||||
class DonateFragment : Fragment() {
|
||||
private lateinit var binding: FragmentDonateBinding
|
||||
private val vm: DonateViewModel by viewModels()
|
||||
private lateinit var header: FragmentDonateHeaderBinding
|
||||
private lateinit var referrals: FragmentDonateReferralBinding
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -40,6 +46,9 @@ class DonateFragment : Fragment() {
|
||||
binding.lifecycleOwner = this
|
||||
binding.vm = vm
|
||||
|
||||
header = FragmentDonateHeaderBinding.inflate(inflater, container, false)
|
||||
referrals = FragmentDonateReferralBinding.inflate(inflater, container, false)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -51,25 +60,35 @@ class DonateFragment : Fragment() {
|
||||
(requireActivity() as MapsActivity).appBarConfiguration
|
||||
)
|
||||
|
||||
binding.productsList.apply {
|
||||
adapter = DonationAdapter().apply {
|
||||
onClickListener = {
|
||||
vm.startPurchase(it, requireActivity())
|
||||
}
|
||||
val donationAdapter = DonationAdapter().apply {
|
||||
onClickListener = {
|
||||
vm.startPurchase(it, requireActivity())
|
||||
}
|
||||
}
|
||||
binding.productsList.apply {
|
||||
val joinedAdapter = ConcatAdapter(
|
||||
SingleViewAdapter(header.root),
|
||||
donationAdapter,
|
||||
SingleViewAdapter(referrals.root)
|
||||
)
|
||||
adapter = joinedAdapter
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
}
|
||||
|
||||
vm.products.observe(viewLifecycleOwner) {
|
||||
print(it)
|
||||
donationAdapter.submitList(it.data)
|
||||
}
|
||||
|
||||
vm.purchaseSuccessful.observe(viewLifecycleOwner, Observer {
|
||||
vm.purchaseSuccessful.observe(viewLifecycleOwner) {
|
||||
Snackbar.make(view, R.string.donation_successful, Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
vm.purchaseFailed.observe(viewLifecycleOwner, Observer {
|
||||
}
|
||||
vm.purchaseFailed.observe(viewLifecycleOwner) {
|
||||
Snackbar.make(view, R.string.donation_failed, Snackbar.LENGTH_LONG).show()
|
||||
})
|
||||
}
|
||||
|
||||
referrals.referralTesla.setOnClickListener {
|
||||
(requireActivity() as MapsActivity).openUrl(getString(R.string.tesla_referral_link))
|
||||
}
|
||||
|
||||
// Workaround for AndroidX bug: https://github.com/material-components/material-components-android/issues/1984
|
||||
view.setBackgroundColor(MaterialColors.getColor(view, android.R.attr.windowBackground))
|
||||
|
||||
@@ -35,29 +35,16 @@
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/donations_info"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/toolbar_container" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/products_list"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:data="@{vm.products.data}"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/textView20"
|
||||
tools:listitem="@layout/item_donation" />
|
||||
app:layout_constraintTop_toBottomOf="@id/toolbar_container"
|
||||
tools:itemCount="1"
|
||||
tools:listitem="@layout/fragment_donate_preview" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressBar3"
|
||||
|
||||
10
app/src/google/res/layout/fragment_donate_header.xml
Normal file
10
app/src/google/res/layout/fragment_donate_header.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView android:id="@+id/textView20"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/donations_info"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||
16
app/src/google/res/layout/fragment_donate_preview.xml
Normal file
16
app/src/google/res/layout/fragment_donate_preview.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/fragment_donate_header" />
|
||||
|
||||
<include layout="@layout/item_donation" />
|
||||
|
||||
<include layout="@layout/item_donation" />
|
||||
|
||||
<include layout="@layout/item_donation" />
|
||||
|
||||
<include layout="@layout/fragment_donate_referral" />
|
||||
</LinearLayout>
|
||||
@@ -327,7 +327,8 @@
|
||||
android:name=".auto.CarAppService"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="location">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="androidx.car.app.CarAppService"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package net.vonforst.evmap
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import androidx.work.*
|
||||
@@ -8,10 +9,12 @@ import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.updateAppLocale
|
||||
import net.vonforst.evmap.ui.updateNightMode
|
||||
import org.acra.config.dialog
|
||||
import org.acra.config.httpSender
|
||||
import org.acra.config.limiter
|
||||
import org.acra.config.mailSender
|
||||
import org.acra.data.StringFormat
|
||||
import org.acra.ktx.initAcra
|
||||
import org.acra.sender.HttpSender
|
||||
import java.time.Duration
|
||||
|
||||
class EvMapApplication : Application(), Configuration.Provider {
|
||||
@@ -33,10 +36,22 @@ class EvMapApplication : Application(), Configuration.Provider {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
initAcra {
|
||||
buildConfigClass = BuildConfig::class.java
|
||||
reportFormat = StringFormat.KEY_VALUE_LIST
|
||||
|
||||
mailSender {
|
||||
mailTo = "evmap+crashreport@vonforst.net"
|
||||
if (BuildConfig.FLAVOR_automotive == "automotive") {
|
||||
// Vehicles often don't have an email app, so use HTTP to send instead
|
||||
reportFormat = StringFormat.JSON
|
||||
httpSender {
|
||||
uri = getString(R.string.acra_backend_url)
|
||||
val creds = getString(R.string.acra_credentials).split(":")
|
||||
basicAuthLogin = creds[0]
|
||||
basicAuthPassword = creds[1]
|
||||
httpMethod = HttpSender.Method.POST
|
||||
}
|
||||
} else {
|
||||
reportFormat = StringFormat.KEY_VALUE_LIST
|
||||
mailSender {
|
||||
mailTo = "evmap+crashreport@vonforst.net"
|
||||
}
|
||||
}
|
||||
|
||||
dialog {
|
||||
@@ -45,6 +60,10 @@ class EvMapApplication : Application(), Configuration.Provider {
|
||||
commentPrompt = getString(R.string.crash_report_comment_prompt)
|
||||
resIcon = R.drawable.ic_launcher_foreground
|
||||
resTheme = R.style.AppTheme
|
||||
if (BuildConfig.FLAVOR_automotive == "automotive") {
|
||||
reportDialogClass =
|
||||
Class.forName("androidx.car.app.activity.CarAppActivity") as Class<out Activity>?
|
||||
}
|
||||
}
|
||||
|
||||
limiter {
|
||||
|
||||
@@ -5,10 +5,13 @@ import android.content.pm.PackageInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Typeface
|
||||
import android.icu.util.LocaleData
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.*
|
||||
import android.text.style.StyleSpan
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import java.util.*
|
||||
|
||||
fun Bundle.optDouble(name: String): Double? {
|
||||
@@ -88,9 +91,25 @@ fun Context.isDarkMode() =
|
||||
|
||||
const val kmPerMile = 1.609344
|
||||
const val meterPerFt = 0.3048
|
||||
const val ftPerMile = 5280
|
||||
const val ydPerMile = 1760
|
||||
|
||||
fun shouldUseImperialUnits(ctx: Context): Boolean {
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
return when (prefs.units) {
|
||||
"metric" -> false
|
||||
"imperial" -> true
|
||||
else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
when (LocaleData.getMeasurementSystem(ULocale.getDefault())) {
|
||||
LocaleData.MeasurementSystem.US, LocaleData.MeasurementSystem.UK -> true
|
||||
LocaleData.MeasurementSystem.SI -> false
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
return Locale.getDefault().country in listOf("US", "GB", "MM", "LR")
|
||||
}
|
||||
}
|
||||
|
||||
fun shouldUseImperialUnits(): Boolean {
|
||||
return Locale.getDefault().country in listOf("US", "GB", "MM", "LR")
|
||||
}
|
||||
|
||||
fun PackageManager.getPackageInfoCompat(packageName: String, flags: Int = 0): PackageInfo =
|
||||
|
||||
@@ -105,20 +105,19 @@ class EnBwAvailabilityDetector(client: OkHttpClient, baseUrl: String? = null) :
|
||||
var markers =
|
||||
api.getMarkers(lng - coordRange, lng + coordRange, lat - coordRange, lat + coordRange)
|
||||
|
||||
while (markers.any { it.grouped }) {
|
||||
markers = markers.flatMap {
|
||||
if (it.grouped) {
|
||||
api.getMarkers(
|
||||
it.viewPort.lowerLeftLon,
|
||||
it.viewPort.upperRightLon,
|
||||
it.viewPort.lowerLeftLat,
|
||||
it.viewPort.upperRightLat
|
||||
)
|
||||
} else {
|
||||
listOf(it)
|
||||
}
|
||||
markers = markers.flatMap {
|
||||
if (it.grouped) {
|
||||
api.getMarkers(
|
||||
it.viewPort.lowerLeftLon,
|
||||
it.viewPort.upperRightLon,
|
||||
it.viewPort.lowerLeftLat,
|
||||
it.viewPort.upperRightLat
|
||||
)
|
||||
} else {
|
||||
listOf(it)
|
||||
}
|
||||
}
|
||||
if (markers.any { it.grouped }) throw AvailabilityDetectorException("markers still grouped")
|
||||
|
||||
val nearest = markers.minByOrNull { marker ->
|
||||
distanceBetween(marker.lat, marker.lon, lat, lng)
|
||||
|
||||
@@ -34,6 +34,7 @@ import net.vonforst.evmap.location.LocationEngine
|
||||
import net.vonforst.evmap.location.Priority
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.utils.checkFineLocationPermission
|
||||
import org.acra.interaction.DialogInteraction
|
||||
|
||||
|
||||
interface LocationAwareScreen {
|
||||
@@ -122,11 +123,13 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
}
|
||||
|
||||
override fun onCreateScreen(intent: Intent): Screen {
|
||||
handleActionsIntent(intent)
|
||||
|
||||
val mapScreen = MapScreen(carContext, this)
|
||||
val screens = mutableListOf<Screen>(mapScreen)
|
||||
|
||||
handleActionsIntent(intent)?.let {
|
||||
screens.add(it)
|
||||
}
|
||||
if (!prefs.dataSourceSet) {
|
||||
screens.add(
|
||||
ChooseDataSourceScreen(
|
||||
@@ -149,6 +152,14 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
)
|
||||
)
|
||||
}
|
||||
if (!prefs.privacyAccepted) {
|
||||
screens.add(
|
||||
AcceptPrivacyScreen(carContext)
|
||||
)
|
||||
}
|
||||
handleACRAIntent(intent)?.let {
|
||||
screens.add(it)
|
||||
}
|
||||
|
||||
if (screens.size > 1) {
|
||||
val screenManager = carContext.getCarService(ScreenManager::class.java)
|
||||
@@ -160,7 +171,13 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
return screens.last()
|
||||
}
|
||||
|
||||
private fun handleActionsIntent(intent: Intent): Boolean {
|
||||
private fun handleACRAIntent(intent: Intent): Screen? {
|
||||
return if (intent.hasExtra(DialogInteraction.EXTRA_REPORT_CONFIG)) {
|
||||
CrashReportScreen(carContext, intent)
|
||||
} else null
|
||||
}
|
||||
|
||||
private fun handleActionsIntent(intent: Intent): Screen? {
|
||||
intent.data?.let {
|
||||
if (it.host == "find_charger") {
|
||||
val lat = it.getQueryParameter("latitude")?.toDouble()
|
||||
@@ -169,15 +186,14 @@ class EVMapSession(val cas: CarAppService) : Session(), DefaultLifecycleObserver
|
||||
if (lat != null && lon != null) {
|
||||
prefs.placeSearchResultAndroidAuto = LatLng(lat, lon)
|
||||
prefs.placeSearchResultAndroidAutoName = name ?: "%.4f,%.4f".format(lat, lon)
|
||||
return true
|
||||
return null
|
||||
} else if (name != null) {
|
||||
val screenManager = carContext.getCarService(ScreenManager::class.java)
|
||||
screenManager.push(PlaceSearchScreen(carContext, this, name))
|
||||
return true
|
||||
val screen = PlaceSearchScreen(carContext, this, name)
|
||||
return screen
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return null
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
|
||||
@@ -290,6 +290,18 @@ class ChargerDetailScreen(ctx: CarContext, val chargerSparse: ChargeLocation) :
|
||||
}.build())
|
||||
}
|
||||
}
|
||||
if (rows.count() < maxRows && charger.generalInformation != null) {
|
||||
rows.add(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.general_info))
|
||||
addText(charger.generalInformation)
|
||||
}.build())
|
||||
}
|
||||
if (rows.count() < maxRows && charger.amenities != null) {
|
||||
rows.add(Row.Builder().apply {
|
||||
setTitle(carContext.getString(R.string.amenities))
|
||||
addText(charger.amenities)
|
||||
}.build())
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package net.vonforst.evmap.auto
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.Screen
|
||||
import androidx.car.app.model.Action
|
||||
import androidx.car.app.model.CarColor
|
||||
import androidx.car.app.model.CarIcon
|
||||
import androidx.car.app.model.MessageTemplate
|
||||
import androidx.car.app.model.Template
|
||||
import net.vonforst.evmap.R
|
||||
import org.acra.dialog.CrashReportDialogHelper
|
||||
|
||||
/**
|
||||
* ACRA-compatible crash reporting screen for the Car App Library
|
||||
*
|
||||
* only used on Android Automotive OS
|
||||
*/
|
||||
class CrashReportScreen(ctx: CarContext, intent: Intent) : Screen(ctx) {
|
||||
val helper = CrashReportDialogHelper(ctx, intent)
|
||||
override fun onGetTemplate(): Template {
|
||||
return MessageTemplate.Builder(carContext.getString(R.string.crash_report_text)).apply {
|
||||
setHeaderAction(Action.APP_ICON)
|
||||
setTitle(carContext.getString(R.string.app_name))
|
||||
addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.ok))
|
||||
.setFlags(Action.FLAG_PRIMARY)
|
||||
.setBackgroundColor(CarColor.PRIMARY)
|
||||
.setOnClickListener {
|
||||
helper.sendCrash(null, null)
|
||||
screenManager.pop()
|
||||
}.build()
|
||||
)
|
||||
addAction(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.cancel))
|
||||
.setOnClickListener {
|
||||
helper.cancelReports()
|
||||
screenManager.pop()
|
||||
}.build()
|
||||
)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
@@ -204,6 +204,37 @@ class FilterScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx) {
|
||||
Row.IMAGE_TYPE_ICON
|
||||
)
|
||||
setOnClickListener { onItemClick(it.id) }
|
||||
if (carContext.carAppApiLevel >= 6) {
|
||||
// Delete action
|
||||
addAction(Action.Builder().apply {
|
||||
setIcon(
|
||||
CarIcon.Builder(
|
||||
IconCompat.createWithResource(
|
||||
carContext,
|
||||
R.drawable.ic_delete
|
||||
)
|
||||
).build()
|
||||
|
||||
)
|
||||
setOnClickListener {
|
||||
lifecycleScope.launch {
|
||||
db.filterProfileDao().delete(it)
|
||||
if (prefs.filterStatus == it.id) {
|
||||
prefs.filterStatus = FILTERS_DISABLED
|
||||
}
|
||||
CarToast.makeText(
|
||||
carContext,
|
||||
carContext.getString(
|
||||
R.string.deleted_filterprofile,
|
||||
it.name
|
||||
),
|
||||
CarToast.LENGTH_SHORT
|
||||
).show()
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
}.build())
|
||||
}
|
||||
}.build())
|
||||
}
|
||||
if (page < paginatedProfiles.size - 1) {
|
||||
@@ -293,7 +324,8 @@ class EditFiltersScreen(ctx: CarContext) : Screen(ctx) {
|
||||
|
||||
setActionStrip(ActionStrip.Builder().apply {
|
||||
val currentProfile = vm.filterProfile.value
|
||||
if (currentProfile != null) {
|
||||
if (currentProfile != null && carContext.carAppApiLevel < 6) {
|
||||
// Delete action (when row actions are not available)
|
||||
addAction(Action.Builder().apply {
|
||||
setIcon(
|
||||
CarIcon.Builder(
|
||||
|
||||
@@ -348,7 +348,8 @@ class MapScreen(ctx: CarContext, val session: EVMapSession) :
|
||||
DistanceSpan.create(
|
||||
roundValueToDistance(
|
||||
distanceMeters,
|
||||
energyLevel?.distanceDisplayUnit?.value
|
||||
energyLevel?.distanceDisplayUnit?.value,
|
||||
carContext
|
||||
)
|
||||
),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
|
||||
@@ -23,6 +23,7 @@ class PermissionScreen(
|
||||
Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.grant_on_phone))
|
||||
.setBackgroundColor(CarColor.PRIMARY)
|
||||
.setFlags(Action.FLAG_PRIMARY)
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
requestPermissions()
|
||||
})
|
||||
|
||||
@@ -105,7 +105,8 @@ class PlaceSearchScreen(
|
||||
DistanceSpan.create(
|
||||
roundValueToDistance(
|
||||
it,
|
||||
energyLevel?.distanceDisplayUnit?.value
|
||||
energyLevel?.distanceDisplayUnit?.value,
|
||||
carContext
|
||||
)
|
||||
),
|
||||
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent
|
||||
import android.content.pm.PackageManager.NameNotFoundException
|
||||
import android.hardware.Sensor
|
||||
import android.hardware.SensorManager
|
||||
import android.text.Html
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.car.app.CarContext
|
||||
import androidx.car.app.CarToast
|
||||
@@ -13,6 +14,7 @@ import androidx.car.app.annotations.ExperimentalCarApi
|
||||
import androidx.car.app.constraints.ConstraintManager
|
||||
import androidx.car.app.model.*
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.vonforst.evmap.*
|
||||
@@ -703,6 +705,34 @@ class AboutScreen(ctx: CarContext) : Screen(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
class AcceptPrivacyScreen(ctx: CarContext) : Screen(ctx) {
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
override fun onGetTemplate(): Template {
|
||||
val textWithoutLink = HtmlCompat.fromHtml(
|
||||
carContext.getString(R.string.accept_privacy),
|
||||
HtmlCompat.FROM_HTML_MODE_LEGACY
|
||||
).toString()
|
||||
return MessageTemplate.Builder(textWithoutLink).apply {
|
||||
setTitle(carContext.getString(R.string.privacy))
|
||||
addAction(Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.ok))
|
||||
.setFlags(Action.FLAG_PRIMARY)
|
||||
.setBackgroundColor(CarColor.PRIMARY)
|
||||
.setOnClickListener {
|
||||
prefs.privacyAccepted = true
|
||||
screenManager.pop()
|
||||
}.build()
|
||||
)
|
||||
addAction(Action.Builder()
|
||||
.setTitle(carContext.getString(R.string.privacy))
|
||||
.setOnClickListener(ParkedOnlyOnClickListener.create {
|
||||
openUrl(carContext, carContext.getString(R.string.privacy_link))
|
||||
}).build()
|
||||
)
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
class DeveloperOptionsScreen(ctx: CarContext) : Screen(ctx) {
|
||||
val prefs = PreferenceDataSource(ctx)
|
||||
|
||||
|
||||
@@ -19,7 +19,11 @@ import androidx.core.graphics.drawable.IconCompat
|
||||
import net.vonforst.evmap.BuildConfig
|
||||
import net.vonforst.evmap.R
|
||||
import net.vonforst.evmap.api.availability.ChargepointStatus
|
||||
import net.vonforst.evmap.ftPerMile
|
||||
import net.vonforst.evmap.getPackageInfoCompat
|
||||
import net.vonforst.evmap.kmPerMile
|
||||
import net.vonforst.evmap.shouldUseImperialUnits
|
||||
import net.vonforst.evmap.ydPerMile
|
||||
import java.util.*
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@@ -69,33 +73,26 @@ val emptyCarIcon: CarIcon by lazy {
|
||||
).asCarIcon()
|
||||
}
|
||||
|
||||
private const val kmPerMile = 1.609344
|
||||
private const val ftPerMile = 5280
|
||||
private const val ydPerMile = 1760
|
||||
|
||||
fun getDefaultDistanceUnit(): Int {
|
||||
return if (usesImperialUnits(Locale.getDefault())) {
|
||||
fun getDefaultDistanceUnit(ctx: Context): Int {
|
||||
return if (shouldUseImperialUnits(ctx)) {
|
||||
CarUnit.MILE
|
||||
} else {
|
||||
CarUnit.KILOMETER
|
||||
}
|
||||
}
|
||||
|
||||
fun usesImperialUnits(locale: Locale): Boolean {
|
||||
return locale.country in listOf("US", "GB", "MM", "LR")
|
||||
|| locale.country == "" && locale.language == "en"
|
||||
}
|
||||
|
||||
fun getDefaultSpeedUnit(): Int {
|
||||
return when (Locale.getDefault().country) {
|
||||
"US", "GB", "MM", "LR" -> CarUnit.MILES_PER_HOUR
|
||||
else -> CarUnit.KILOMETERS_PER_HOUR
|
||||
fun getDefaultSpeedUnit(ctx: Context): Int {
|
||||
return if (shouldUseImperialUnits(ctx)) {
|
||||
CarUnit.MILES_PER_HOUR
|
||||
} else {
|
||||
CarUnit.KILOMETERS_PER_HOUR
|
||||
}
|
||||
}
|
||||
|
||||
fun formatCarUnitDistance(value: Float?, unit: Int?): String {
|
||||
fun formatCarUnitDistance(value: Float?, unit: Int?, ctx: Context): String {
|
||||
if (value == null) return ""
|
||||
return when (unit ?: getDefaultDistanceUnit()) {
|
||||
return when (unit ?: getDefaultDistanceUnit(ctx)) {
|
||||
// distance units: base unit is meters
|
||||
CarUnit.METER -> "%.0f m".format(value)
|
||||
CarUnit.KILOMETER -> "%.1f km".format(value / 1000)
|
||||
@@ -105,9 +102,9 @@ fun formatCarUnitDistance(value: Float?, unit: Int?): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun formatCarUnitSpeed(value: Float?, unit: Int?): String {
|
||||
fun formatCarUnitSpeed(value: Float?, unit: Int?, ctx: Context): String {
|
||||
if (value == null) return ""
|
||||
return when (unit ?: getDefaultSpeedUnit()) {
|
||||
return when (unit ?: getDefaultSpeedUnit(ctx)) {
|
||||
// speed units: base unit is meters per second
|
||||
CarUnit.METERS_PER_SEC -> "%.0f m/s".format(value)
|
||||
CarUnit.KILOMETERS_PER_HOUR -> "%.0f km/h".format(value * 3.6)
|
||||
@@ -116,9 +113,9 @@ fun formatCarUnitSpeed(value: Float?, unit: Int?): String {
|
||||
}
|
||||
}
|
||||
|
||||
fun roundValueToDistance(value: Double, unit: Int? = null): Distance {
|
||||
fun roundValueToDistance(value: Double, unit: Int? = null, ctx: Context): Distance {
|
||||
// value is in meters
|
||||
when (unit ?: getDefaultDistanceUnit()) {
|
||||
when (unit ?: getDefaultDistanceUnit(ctx)) {
|
||||
CarUnit.MILE -> {
|
||||
// imperial system
|
||||
val miles = value / 1000 / kmPerMile
|
||||
|
||||
@@ -139,7 +139,8 @@ class VehicleDataScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx
|
||||
setText(
|
||||
formatCarUnitDistance(
|
||||
energyLevel.rangeRemainingMeters.value,
|
||||
energyLevel.distanceDisplayUnit.value
|
||||
energyLevel.distanceDisplayUnit.value,
|
||||
carContext
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
@@ -173,7 +174,8 @@ class VehicleDataScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx
|
||||
setText(
|
||||
formatCarUnitSpeed(
|
||||
rawSpeed,
|
||||
speed.speedDisplayUnit.value
|
||||
speed.speedDisplayUnit.value,
|
||||
carContext
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
@@ -183,7 +185,8 @@ class VehicleDataScreen(ctx: CarContext, val session: EVMapSession) : Screen(ctx
|
||||
setText(
|
||||
formatCarUnitSpeed(
|
||||
speed.displaySpeedMetersPerSecond.value,
|
||||
speed.speedDisplayUnit.value
|
||||
speed.speedDisplayUnit.value,
|
||||
carContext
|
||||
)
|
||||
)
|
||||
setImage(
|
||||
|
||||
@@ -77,6 +77,7 @@ import net.vonforst.evmap.location.FusionEngine
|
||||
import net.vonforst.evmap.location.LocationEngine
|
||||
import net.vonforst.evmap.location.Priority
|
||||
import net.vonforst.evmap.model.*
|
||||
import net.vonforst.evmap.shouldUseImperialUnits
|
||||
import net.vonforst.evmap.storage.PreferenceDataSource
|
||||
import net.vonforst.evmap.ui.*
|
||||
import net.vonforst.evmap.utils.boundingBox
|
||||
@@ -910,23 +911,19 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
|
||||
}
|
||||
|
||||
binding.scaleView.apply {
|
||||
when (prefs.mapScale) {
|
||||
"both" -> {
|
||||
visibility = View.VISIBLE
|
||||
if (prefs.showMapScale) {
|
||||
visibility = View.VISIBLE
|
||||
if (prefs.mapScaleMetersAndMiles) {
|
||||
metersAndMiles()
|
||||
} else {
|
||||
if (shouldUseImperialUnits(requireContext())) {
|
||||
milesOnly()
|
||||
} else {
|
||||
metersOnly()
|
||||
}
|
||||
}
|
||||
|
||||
"meters" -> {
|
||||
visibility = View.VISIBLE
|
||||
metersOnly()
|
||||
}
|
||||
|
||||
"miles" -> {
|
||||
visibility = View.VISIBLE
|
||||
milesOnly()
|
||||
}
|
||||
|
||||
"off" -> visibility = View.GONE
|
||||
} else {
|
||||
visibility = View.GONE
|
||||
}
|
||||
}
|
||||
vm.mapPosition.observe(viewLifecycleOwner) {
|
||||
|
||||
@@ -28,7 +28,7 @@ class AndroidAutoSettingsFragment : BaseSettingsFragment() {
|
||||
setPreferencesFromResource(R.xml.settings_android_auto, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
"chargeprice_battery_range_android_auto_min", "chargeprice_battery_range_android_auto_max" -> {
|
||||
updateRangePreferenceSummary()
|
||||
|
||||
@@ -101,11 +101,12 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
|
||||
setPreferencesFromResource(R.xml.settings_chargeprice, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
"chargeprice_my_vehicle" -> {
|
||||
updateMyVehiclesSummary()
|
||||
}
|
||||
|
||||
"chargeprice_my_tariffs" -> {
|
||||
updateMyTariffsSummary()
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class DataSettingsFragment : BaseSettingsFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
"search_provider" -> {
|
||||
if (prefs.searchProvider == "google") {
|
||||
|
||||
@@ -25,7 +25,7 @@ class DeveloperSettingsFragment : BaseSettingsFragment() {
|
||||
setPreferencesFromResource(R.xml.settings_developer, rootKey)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class SettingsFragment : BaseSettingsFragment() {
|
||||
findPreference<Preference>("developer_options")?.isVisible = prefs.developerModeEnabled
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class UiSettingsFragment : BaseSettingsFragment() {
|
||||
langPref.value = getAppLocale()
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
|
||||
when (key) {
|
||||
"darkmode" -> {
|
||||
updateNightMode(prefs)
|
||||
|
||||
@@ -14,6 +14,24 @@ import java.time.Instant
|
||||
class PreferenceDataSource(val context: Context) {
|
||||
val sp = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
init {
|
||||
if (sp.contains("map_scale")) {
|
||||
// migration
|
||||
val mapScale = sp.getString("map_scale", null)
|
||||
sp.edit().putBoolean("map_scale_show", mapScale != "off")
|
||||
.putBoolean("map_scale_meters_and_miles", mapScale == "both")
|
||||
.putString(
|
||||
"units", when (mapScale) {
|
||||
"meters" -> "metric"
|
||||
"miles" -> "imperial"
|
||||
else -> "default"
|
||||
}
|
||||
)
|
||||
.remove("map_scale")
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
var dataSource: String
|
||||
get() = sp.getString("data_source", "goingelectric")!!
|
||||
set(value) {
|
||||
@@ -252,8 +270,14 @@ class PreferenceDataSource(val context: Context) {
|
||||
sp.edit().putBoolean("dev_mode_enabled", value).apply()
|
||||
}
|
||||
|
||||
val mapScale: String
|
||||
get() = sp.getString("map_scale", null) ?: "both"
|
||||
val showMapScale: Boolean
|
||||
get() = sp.getBoolean("map_scale_show", true)
|
||||
|
||||
val mapScaleMetersAndMiles: Boolean
|
||||
get() = sp.getBoolean("map_scale_meters_and_miles", true)
|
||||
|
||||
val units: String
|
||||
get() = sp.getString("units", null) ?: "default"
|
||||
|
||||
var currentMapLocation: LatLng
|
||||
get() = sp.getLatLng("current_map_location") ?: LatLng(50.113388, 9.252536)
|
||||
|
||||
@@ -309,9 +309,9 @@ fun time(value: Int): String {
|
||||
else "%d:%02d h".format(h, min)
|
||||
}
|
||||
|
||||
fun distance(meters: Number?): String? {
|
||||
fun distance(meters: Number?, ctx: Context): String? {
|
||||
if (meters == null) return null
|
||||
if (shouldUseImperialUnits()) {
|
||||
if (shouldUseImperialUnits(ctx)) {
|
||||
val ft = meters.toDouble() / meterPerFt
|
||||
val mi = meters.toDouble() / 1e3 / kmPerMile
|
||||
return when {
|
||||
|
||||
@@ -147,7 +147,7 @@
|
||||
android:gravity="end"
|
||||
android:maxLines="1"
|
||||
android:minWidth="50dp"
|
||||
android:text="@{BindingAdaptersKt.distance(distance)}"
|
||||
android:text="@{BindingAdaptersKt.distance(distance, context)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/txtConnectors"
|
||||
app:layout_constraintEnd_toStartOf="@+id/guideline2"
|
||||
|
||||
35
app/src/main/res/layout/fragment_donate_referral.xml
Normal file
35
app/src/main/res/layout/fragment_donate_referral.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView20"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/referrals"
|
||||
android:textAppearance="@style/TextAppearance.Material3.TitleSmall"
|
||||
android:textColor="?colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView21"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="@string/referrals_info" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/referral_tesla"
|
||||
style="@style/Widget.Material3.Button.TonalButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/referral_tesla"
|
||||
app:icon="@drawable/ic_tesla" />
|
||||
</LinearLayout>
|
||||
@@ -62,7 +62,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="@{BindingAdaptersKt.distance(item.distanceMeters)}"
|
||||
android:text="@{BindingAdaptersKt.distance(item.distanceMeters, context)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:goneUnless="@{item.distanceMeters != null}"
|
||||
app:layout_constraintEnd_toEndOf="@+id/icon"
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@{BindingAdaptersKt.distance(item.distance)}"
|
||||
android:text="@{BindingAdaptersKt.distance(item.distance, context)}"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
app:goneUnless="@{item.distance != null}"
|
||||
app:layout_constraintEnd_toStartOf="@id/btnDelete"
|
||||
|
||||
@@ -314,11 +314,12 @@
|
||||
<string name="tesla_pricing_blocking_fee">Blockiergebühr: %s</string>
|
||||
<string name="average_utilization">Durchschnittliche Auslastung</string>
|
||||
<string name="website">Website</string>
|
||||
<string name="pref_map_scale">Kartenmaßstab</string>
|
||||
<string name="pref_map_scale_both">Meter und Meilen</string>
|
||||
<string name="pref_map_scale_meters">Meter</string>
|
||||
<string name="pref_map_scale_miles">Meilen</string>
|
||||
<string name="pref_map_scale_off">aus</string>
|
||||
<string name="pref_map_scale">Kartenmaßstab zeigen</string>
|
||||
<string name="pref_map_scale_meters_and_miles">Meilen und Meter am Kartenmaßstab</string>
|
||||
<string name="pref_units">Einheiten</string>
|
||||
<string name="pref_units_default">Geräteeinstellung verwenden</string>
|
||||
<string name="pref_units_metric">Metrisch</string>
|
||||
<string name="pref_units_imperial">Imperial</string>
|
||||
<string name="data_retrieved_at">Daten abgerufen %s</string>
|
||||
<string name="settings_caching">Cache</string>
|
||||
<string name="settings_cache_count">Cache-Größe</string>
|
||||
@@ -361,4 +362,7 @@
|
||||
<string name="auto_multipage">(%d/%d)</string>
|
||||
<string name="reload">Neu laden</string>
|
||||
<string name="accept_privacy"><![CDATA[Ich habe die <a href=\"%s\">Datenschutzerklärung</a> von EVMap gelesen und bin damit einverstanden.]]></string>
|
||||
<string name="referrals">Empfehlungslinks</string>
|
||||
<string name="referrals_info">Du kannst auch einen der Empfehlungslinks unten benutzen, um den Entwickler mit deinem Kauf zu unterstützen.</string>
|
||||
<string name="referral_tesla">Tesla</string>
|
||||
</resources>
|
||||
@@ -319,11 +319,7 @@
|
||||
<string name="average_utilization">Utilização média</string>
|
||||
<string name="tesla_pricing_owners">Apenas veículos Tesla:</string>
|
||||
<string name="website">Website</string>
|
||||
<string name="pref_map_scale_off">desativar</string>
|
||||
<string name="pref_map_scale_both">metros e milhas</string>
|
||||
<string name="pref_map_scale_meters">metros</string>
|
||||
<string name="pref_map_scale_miles">milhas</string>
|
||||
<string name="pref_map_scale">Barra de escala do mapa</string>
|
||||
<string name="pref_map_scale">Mostrar barra de escala do mapa</string>
|
||||
<string name="data_retrieved_at">Informação atualizada %s</string>
|
||||
<string name="settings_cache_count">Tamanho da cache</string>
|
||||
<string name="settings_cache_clear">Limpar cache</string>
|
||||
|
||||
@@ -66,16 +66,14 @@
|
||||
<item>goingelectric</item>
|
||||
<item>openchargemap</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_scale_names">
|
||||
<item>@string/pref_map_scale_both</item>
|
||||
<item>@string/pref_map_scale_meters</item>
|
||||
<item>@string/pref_map_scale_miles</item>
|
||||
<item>@string/pref_map_scale_off</item>
|
||||
<string-array name="pref_units_names">
|
||||
<item>@string/pref_units_default</item>
|
||||
<item>@string/pref_units_metric</item>
|
||||
<item>@string/pref_units_imperial</item>
|
||||
</string-array>
|
||||
<string-array name="pref_map_scale_values" translatable="false">
|
||||
<item>both</item>
|
||||
<item>meters</item>
|
||||
<item>miles</item>
|
||||
<item>off</item>
|
||||
<string-array name="pref_units_values" translatable="false">
|
||||
<item>default</item>
|
||||
<item>metric</item>
|
||||
<item>imperial</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -33,5 +33,7 @@
|
||||
<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="copyright_summary">©2020–2023 Johan von Forstner and contributors</string>
|
||||
<string name="acra_backend_url" translatable="false">https://acra.muc.vonforst.net/report</string>
|
||||
</resources>
|
||||
@@ -314,11 +314,12 @@
|
||||
<string name="tesla_pricing_blocking_fee">Blocking fee: %s</string>
|
||||
<string name="average_utilization">Average Utilization</string>
|
||||
<string name="website">Website</string>
|
||||
<string name="pref_map_scale">Map scale bar</string>
|
||||
<string name="pref_map_scale_both">meters and miles</string>
|
||||
<string name="pref_map_scale_meters">meters</string>
|
||||
<string name="pref_map_scale_miles">miles</string>
|
||||
<string name="pref_map_scale_off">off</string>
|
||||
<string name="pref_map_scale">Show map scale bar</string>
|
||||
<string name="pref_map_scale_meters_and_miles">Miles and meters on map scale bar</string>
|
||||
<string name="pref_units">Units</string>
|
||||
<string name="pref_units_default">Device default</string>
|
||||
<string name="pref_units_metric">Metric</string>
|
||||
<string name="pref_units_imperial">Imperial</string>
|
||||
<string name="data_retrieved_at">Data retrieved %s</string>
|
||||
<string name="settings_caching">Caching</string>
|
||||
<string name="settings_cache_count">Cache size</string>
|
||||
@@ -361,4 +362,7 @@
|
||||
<string name="auto_multipage">(%d/%d)</string>
|
||||
<string name="reload">Reload</string>
|
||||
<string name="accept_privacy"><![CDATA[I have read and accepted EVMap\'s <a href=\"%s\">privacy policy</a>.]]></string>
|
||||
<string name="referrals">Referral links</string>
|
||||
<string name="referrals_info">You can also use one of the referral links below to support the developer with your purchase.</string>
|
||||
<string name="referral_tesla">Tesla</string>
|
||||
</resources>
|
||||
@@ -8,6 +8,13 @@
|
||||
android:entryValues="@array/pref_language_values"
|
||||
android:defaultValue="default"
|
||||
android:summary="%s" />
|
||||
<ListPreference
|
||||
android:key="units"
|
||||
android:title="@string/pref_units"
|
||||
android:entries="@array/pref_units_names"
|
||||
android:entryValues="@array/pref_units_values"
|
||||
android:defaultValue="default"
|
||||
android:summary="%s" />
|
||||
|
||||
<ListPreference
|
||||
android:key="darkmode"
|
||||
@@ -22,13 +29,15 @@
|
||||
android:summaryOn="@string/pref_map_rotate_gestures_on"
|
||||
android:summaryOff="@string/pref_map_rotate_gestures_off"
|
||||
android:defaultValue="true" />
|
||||
<ListPreference
|
||||
android:key="map_scale"
|
||||
<CheckBoxPreference
|
||||
android:key="map_scale_show"
|
||||
android:title="@string/pref_map_scale"
|
||||
android:entries="@array/pref_map_scale_names"
|
||||
android:entryValues="@array/pref_map_scale_values"
|
||||
android:defaultValue="both"
|
||||
android:summary="%s" />
|
||||
android:defaultValue="true" />
|
||||
<CheckBoxPreference
|
||||
android:key="map_scale_meters_and_miles"
|
||||
android:title="@string/pref_map_scale_meters_and_miles"
|
||||
android:defaultValue="true" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:key="navigate_use_maps"
|
||||
android:title="@string/pref_navigate_use_maps"
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import net.vonforst.evmap.FakeAndroidKeyStore
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.Robolectric
|
||||
@@ -18,6 +19,7 @@ import org.robolectric.annotation.internal.DoNotInstrument
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@DoNotInstrument
|
||||
@Ignore("Disabled because Robolectric does not yet support API 34")
|
||||
class CarAppTest {
|
||||
private val testCarContext =
|
||||
TestCarContext.createCarContext(ApplicationProvider.getApplicationContext()).apply {
|
||||
@@ -43,7 +45,7 @@ class CarAppTest {
|
||||
val screenCreated =
|
||||
testCarContext.getCarService(TestScreenManager::class.java).screensPushed.last()
|
||||
|
||||
// location permission required
|
||||
assert(screenCreated is PermissionScreen)
|
||||
// accept privacy required
|
||||
assert(screenCreated is AcceptPrivacyScreen)
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.8.21'
|
||||
ext.kotlin_version = '1.9.0'
|
||||
ext.about_libs_version = '8.9.4'
|
||||
ext.nav_version = '2.5.3'
|
||||
ext.nav_version = '2.7.1'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.0.2'
|
||||
classpath 'com.android.tools.build:gradle:8.1.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:$about_libs_version"
|
||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
|
||||
|
||||
@@ -29,6 +29,9 @@ be put into the app in the form of a resource file called `apikeys.xml` under
|
||||
<string name="fronyx_key" translatable="false">
|
||||
insert your Fronyx key here
|
||||
</string>
|
||||
<string name="acra_credentials" translatable="false">
|
||||
insert your ACRA crash reporting credentials here
|
||||
</string>
|
||||
</resources>
|
||||
```
|
||||
|
||||
@@ -186,4 +189,12 @@ The API is not publically available, contact [fronyx](https://fronyx.io/contact-
|
||||
key and documentation.
|
||||
|
||||
If you don't want to test this functionality, simply leave the API key blank.
|
||||
</details>
|
||||
</details>
|
||||
|
||||
Crash reporting
|
||||
---------------
|
||||
|
||||
Crash reporting for release builds is done using [ACRA](https://github.com/ACRA/acra).
|
||||
This should not be needed for debugging.
|
||||
If you still want to try it out, you can host any compatible backend such as
|
||||
[Acrarium](https://github.com/F43nd1r/Acrarium/) yourself.
|
||||
8
fastlane/metadata/android/de-DE/changelogs/196.txt
Normal file
8
fastlane/metadata/android/de-DE/changelogs/196.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Verbesserungen:
|
||||
- Neue Einstellung für Maßeinheiten
|
||||
- Anpassungen für Android 14
|
||||
- Android Auto: Weitere Detailbeschreibungen zu den Ladestationen
|
||||
- Android Auto: Löschbutton in der Filterliste
|
||||
|
||||
Fehler behoben:
|
||||
- Fehler beim Laden der EnBW Echtzeitdaten
|
||||
8
fastlane/metadata/android/en-US/changelogs/196.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/196.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
Improvements:
|
||||
- New setting for units of measurement
|
||||
- Adjustments for Android 14
|
||||
- Android Auto: More detailed descriptions of chargers
|
||||
- Android Auto: Delete button in filter list
|
||||
|
||||
Bugfixes:
|
||||
- Errors loading realtime data from EnBW
|
||||
Reference in New Issue
Block a user