Compare commits

..

11 Commits
1.4.8 ... 1.4.9

Author SHA1 Message Date
johan12345
8405f4f4fa Release 1.4.9 2023-03-24 20:14:43 +01:00
johan12345
f435180c03 Chargeprice vehicle selection: add battery size and charging power to distinguish vehicle models
same as ee354d2cd1, but now also for Android Auto
2023-03-24 20:10:14 +01:00
Hosted Weblate
c2c3e96e97 Update translation files
Updated by "Squash Git commits" hook in Weblate.

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/evmap/android-google/
Translation: EVMap/Android (strings specific to Google Play variant)
2023-03-21 21:49:07 +01:00
Hosted Weblate
9100a6f442 Translated using Weblate (French)
Currently translated at 99.6% (282 of 283 strings)

Translated using Weblate (French)

Currently translated at 92.2% (261 of 283 strings)

Translated using Weblate (French)

Currently translated at 97.2% (35 of 36 strings)

Co-authored-by: Altons <marsupilami450@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/evmap/android-google/fr/
Translate-URL: https://hosted.weblate.org/projects/evmap/android/fr/
Translation: EVMap/Android
Translation: EVMap/Android (strings specific to Google Play variant)
2023-03-21 21:49:06 +01:00
johan12345
5403549e0a gallery image loading improvements
refs #271
2023-03-21 21:48:08 +01:00
johan12345
c95f1e7c24 Coil: Allow hardware renderer
this didn't work previously as we used shared element transitions, but should be fine now that we switched to StfalconImageViewer in 0e1e3ba
2023-03-21 21:48:08 +01:00
johan12345
f8d5b78112 Remove SizeResolver(OriginalSize) for Coil
(determining the size automatically based on the actual ImageView size seems to work well now)
fixes #271
2023-03-21 21:48:08 +01:00
Hosted Weblate
246d456851 Translated using Weblate (German)
Currently translated at 100.0% (283 of 283 strings)

Co-authored-by: nautilusx <translate@disroot.org>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/de/
Translation: EVMap/Android
2023-03-05 21:19:01 +01:00
Johan von Forstner
3d303b6535 GoingElectric HoursAdapter: catch parsing exception 2023-03-05 13:31:54 +01:00
johan12345
135fce43c3 CarModels: add Renault BCB -> Megane E-Tech
see #269
2023-02-18 21:09:56 +01:00
johan12345
ee354d2cd1 Chargeprice vehicle selection: Add battery size and charging power to distinguish vehicle models
fixes #268
2023-02-18 21:05:45 +01:00
20 changed files with 152 additions and 40 deletions

View File

@@ -20,8 +20,8 @@ android {
minSdkVersion 21
targetSdkVersion 33
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
versionCode 164
versionName "1.4.8"
versionCode 166
versionName "1.4.9"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs supportedLocales.split(',')

View File

@@ -8,6 +8,9 @@ package net.vonforst.evmap.auto
private val models = mapOf(
"Audi" to mapOf(
"516 (G4x)" to "e-tron"
),
"Renault" to mapOf(
"BCB" to "Megane E-Tech"
)
)

View File

@@ -352,7 +352,7 @@ class ChargepriceScreen(ctx: CarContext, val charger: ChargeLocation) : Screen(c
} else if (vehicles.size > 1) {
if (modelName != null) {
vehicles = vehicles.filter {
it.name.startsWith(modelName)
it.name.lowercase().startsWith(modelName.lowercase())
}
if (vehicles.isEmpty()) {
throw VehicleUnknownException()

View File

@@ -86,6 +86,7 @@ abstract class MultiSelectSearchScreen<T>(ctx: CarContext) : Screen(ctx),
addItem(
Row.Builder()
.setTitle(getLabel(item))
.apply { getDetails(item)?.let { addText(it) } }
.setImage(if (isSelected(item)) checkedIcon else uncheckedIcon)
.setOnClickListener {
toggleSelected(item)
@@ -130,5 +131,7 @@ abstract class MultiSelectSearchScreen<T>(ctx: CarContext) : Screen(ctx),
abstract fun getLabel(it: T): String
open fun getDetails(it: T): String? = null
abstract suspend fun loadData(): List<T>
}

View File

@@ -390,6 +390,8 @@ class SelectVehiclesScreen(ctx: CarContext) : MultiSelectSearchScreen<Chargepric
override fun getLabel(it: ChargepriceCar) = "${it.brand} ${it.name}"
override fun getDetails(it: ChargepriceCar) = it.formatSpecs()
override suspend fun loadData(): List<ChargepriceCar> {
return api.getVehicles()
}

View File

@@ -35,4 +35,6 @@
<string name="settings_android_auto_chargeprice_range">Plage de charge pour la comparaison des prix</string>
<string name="welcome_android_auto_detail">Vous pouvez également utiliser EVMap à partir d\'Android Auto sur les voitures prises en charge. Il suffit de sélectionner l\'application EVMap dans le menu Android Auto.</string>
<string name="loading">Chargement…</string>
<string name="auto_multipage_goto">Page %d</string>
<string name="auto_multipage">(%d/%d)</string>
</resources>

View File

@@ -5,14 +5,13 @@ import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.ImageView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.memory.MemoryCache
import coil.size.OriginalSize
import coil.size.SizeResolver
import net.vonforst.evmap.R
import net.vonforst.evmap.model.ChargerPhoto
@@ -37,27 +36,42 @@ class GalleryAdapter(context: Context, val itemClickListener: ItemClickListener?
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val id = getItem(position).id
val url = getItem(position).getUrl(height = holder.view.height)
val item = getItem(position)
holder.view.load(
url
) {
size(SizeResolver(OriginalSize))
allowHardware(false)
listener(
onSuccess = { _, metadata ->
memoryKeys[id] = metadata.memoryCacheKey
if (holder.view.height == 0) {
holder.view.viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
holder.view.viewTreeObserver.removeOnGlobalLayoutListener(this)
loadImage(item, holder)
}
)
})
} else {
loadImage(item, holder)
}
if (itemClickListener != null) {
holder.view.setOnClickListener {
itemClickListener.onItemClick(holder.view, position, memoryKeys[id])
itemClickListener.onItemClick(holder.view, position, memoryKeys[item.id])
}
}
}
private fun loadImage(
item: ChargerPhoto,
holder: ViewHolder
) {
val url = item.getUrl(height = holder.view.height)
holder.view.load(
url
) {
listener(
onSuccess = { _, metadata ->
memoryKeys[item.id] = metadata.memoryCacheKey
}
)
}
}
}
class ChargerPhotoDiffCallback : DiffUtil.ItemCallback<ChargerPhoto>() {

View File

@@ -114,8 +114,26 @@ data class ChargepriceCar(
val brand: String,
@Json(name = "dc_charge_ports")
val dcChargePorts: List<String>
val dcChargePorts: List<String>,
@Json(name = "usable_battery_size")
val usableBatterySize: Float,
@Json(name = "ac_max_power")
val acMaxPower: Float,
@Json(name = "dc_max_power")
val dcMaxPower: Float?
) : Equatable, Parcelable {
fun formatSpecs(): String = buildString {
append("%.0f kWh".format(usableBatterySize))
append(" | ")
append("AC %.0f kW".format(acMaxPower))
dcMaxPower?.let {
append(" | ")
append("DC %.0f kW".format(it))
}
}
companion object {
private val acConnectors = listOf(

View File

@@ -5,6 +5,7 @@ import com.squareup.moshi.*
import java.lang.reflect.Type
import java.time.Instant
import java.time.LocalTime
import java.time.format.DateTimeParseException
internal class ChargepointListItemJsonAdapterFactory : JsonAdapter.Factory {
@@ -138,7 +139,12 @@ internal class HoursAdapter {
val end = if (match.groupValues[2] == "24:00") {
LocalTime.MAX
} else {
LocalTime.parse(match.groupValues[2])
try {
LocalTime.parse(match.groupValues[2])
} catch (e: DateTimeParseException) {
// got a rare bug report where the value is 24:0000
LocalTime.MIN
}
}
return GEHours(start, end)
} else {

View File

@@ -147,7 +147,7 @@ data class GEChargerPhoto(val id: String) {
@JsonClass(generateAdapter = true)
class GEChargerPhotoAdapter(override val id: String, val apikey: String) :
ChargerPhoto(id) {
override fun getUrl(height: Int?, width: Int?, size: Int?): String {
override fun getUrl(height: Int?, width: Int?, size: Int?, allowOriginal: Boolean): String {
return "https://api.goingelectric.de/chargepoints/photo/?key=${apikey}&id=$id" +
when {
size != null -> "&size=$size"

View File

@@ -251,14 +251,13 @@ class OCMChargerPhotoAdapter(
val largeUrl: String,
val thumbUrl: String
) : ChargerPhoto(id) {
override fun getUrl(height: Int?, width: Int?, size: Int?): String {
override fun getUrl(height: Int?, width: Int?, size: Int?, allowOriginal: Boolean): String {
val maxSize = size ?: max(height, width)
val mediumUrl = thumbUrl.replace(".thmb.", ".medi.")
return when (maxSize) {
0 -> mediumUrl
in 1..100 -> thumbUrl
in 0..100 -> thumbUrl
in 101..400 -> mediumUrl
else -> largeUrl
else -> if (allowOriginal) largeUrl else mediumUrl
}
}
}

View File

@@ -753,12 +753,10 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
val photos = vm.charger.value?.data?.photos ?: return
viewer = StfalconImageViewer.Builder(context, photos) { imageView, photo ->
imageView.load(photo.getUrl(size = 1000)) {
imageView.load(photo.getUrl(size = 1000, allowOriginal = true)) {
if (photo == photos[position] && imageCacheKey != null) {
placeholderMemoryCacheKey(imageCacheKey)
}
size(SizeResolver(OriginalSize))
allowHardware(false)
}
}
.withTransitionFrom(view as ImageView)

View File

@@ -17,7 +17,7 @@ class MultiSelectDialog : MaterialDialogFragment() {
companion object {
fun getInstance(
title: String,
data: Map<String, String>,
data: Map<String, CharSequence>,
selected: Set<String>,
commonChoices: Set<String>?,
showAllButton: Boolean = true
@@ -55,7 +55,7 @@ class MultiSelectDialog : MaterialDialogFragment() {
override fun initView(view: View, savedInstanceState: Bundle?) {
val args = requireArguments()
val data = args.getSerializable("data") as HashMap<String, String>
val data = args.getSerializable("data") as HashMap<String, CharSequence>
val selected = args.getSerializable("selected") as HashSet<String>
val title = args.getString("title")
val commonChoices = if (args.containsKey("commonChoices")) {
@@ -71,7 +71,7 @@ class MultiSelectDialog : MaterialDialogFragment() {
binding.btnAll.visibility = if (showAllButton) View.VISIBLE else View.INVISIBLE
items = data.entries.toList()
.sortedBy { it.value.lowercase(Locale.getDefault()) }
.sortedBy { it.value.toString().lowercase(Locale.getDefault()) }
.sortedBy {
when {
selected.contains(it.key) && commonChoices?.contains(it.key) == true -> 0
@@ -117,7 +117,7 @@ private fun search(
): List<MultiSelectItem> {
return items.filter { item ->
// search for string within name
text.lowercase(Locale.getDefault()) in item.name.lowercase(Locale.getDefault())
text.lowercase(Locale.getDefault()) in item.name.toString().lowercase(Locale.getDefault())
}
}
@@ -125,4 +125,5 @@ class Adapter() : DataBindingAdapter<MultiSelectItem>({ it.key }) {
override fun getItemViewType(position: Int) = R.layout.dialog_multi_select_item
}
data class MultiSelectItem(val key: String, val name: String, var selected: Boolean) : Equatable
data class MultiSelectItem(val key: String, val name: CharSequence, var selected: Boolean) :
Equatable

View File

@@ -2,13 +2,19 @@ package net.vonforst.evmap.fragment.preference
import android.content.SharedPreferences
import android.os.Bundle
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.style.ImageSpan
import android.text.style.RelativeSizeSpan
import android.view.View
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.viewModels
import androidx.preference.MultiSelectListPreference
import net.vonforst.evmap.R
import net.vonforst.evmap.ui.MultiSelectDialogPreference
import net.vonforst.evmap.viewmodel.SettingsViewModel
import net.vonforst.evmap.viewmodel.viewModelFactory
class ChargepriceSettingsFragment : BaseSettingsFragment() {
override val isTopLevel = false
@@ -22,8 +28,8 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
}
})
private lateinit var myVehiclePreference: MultiSelectListPreference
private lateinit var myTariffsPreference: MultiSelectListPreference
private lateinit var myVehiclePreference: MultiSelectDialogPreference
private lateinit var myTariffsPreference: MultiSelectDialogPreference
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -34,8 +40,16 @@ class ChargepriceSettingsFragment : BaseSettingsFragment() {
res.data?.let { cars ->
val sortedCars = cars.sortedBy { it.brand }
myVehiclePreference.entryValues = sortedCars.map { it.id }.toTypedArray()
myVehiclePreference.entries =
sortedCars.map { "${it.brand} ${it.name}" }.toTypedArray()
myVehiclePreference.entries = sortedCars.map {
SpannableStringBuilder().apply {
appendLine("${it.brand} ${it.name}")
append(
it.formatSpecs(),
RelativeSizeSpan(0.86f),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE
)
}
}.toTypedArray()
myVehiclePreference.isEnabled = true
updateMyVehiclesSummary()
}

View File

@@ -332,7 +332,19 @@ data class Hours(
}
abstract class ChargerPhoto(open val id: String) : Parcelable {
abstract fun getUrl(height: Int? = null, width: Int? = null, size: Int? = null): String
/**
* Gets a URL of the image corresponding to a given size.
*
* If the data source supports accessing the image in its original (potentially unlimited) size,
* this size will only be returned if allowOriginal is set to true. Otherwise, only scaled
* versions of the images will be returned.
*/
abstract fun getUrl(
height: Int? = null,
width: Int? = null,
size: Int? = null,
allowOriginal: Boolean = false
): String
}
data class ChargeLocationCluster(

View File

@@ -38,7 +38,7 @@ class MultiSelectDialogPreference(ctx: Context, attrs: AttributeSet) :
val dialog =
MultiSelectDialog.getInstance(
title.toString(),
entryValues.map { it.toString() }.zip(entries.map { it.toString() }).toMap(),
entryValues.map { it.toString() }.zip(entries).toMap(),
if (all) entryValues.map { it.toString() }.toSet() else values,
emptySet(),
showAllButton

View File

@@ -158,7 +158,7 @@
<string name="welcome_2">Die Farbe einer Ladestation zeigt dir die maximale Ladeleistung</string>
<string name="welcome_2_detail">Die Farben kannst du unter “Über EVMap → Häufig gestellte Fragen” erneut ansehen</string>
<string name="donation_dialog_title">Danke, dass du EVMap nutzt!</string>
<string name="donation_dialog_detail">EVMap ist kostenlos und Open Source. Über GitHub kann jeder zur Weiterentwicklung der App beitragen. Um die laufenden Kosten für den für die Datenquellen zu decken, freue ich mich auch über Spenden in der App oder über GitHub Sponsors.</string>
<string name="donation_dialog_detail">EVMap ist kostenlos und Open Source. Über GitHub kann jeder zur Weiterentwicklung der App beitragen. Um die laufenden Kosten für die Datenquellen zu decken, freut sich der Entwickler über Spenden mit einem Betrag deiner Wahl.</string>
<string name="chargeprice_donation_dialog_title">Du bist ein richtiger Sparfuchs!</string>
<string name="chargeprice_donation_dialog_detail">Anscheinend nutzt du den Preisvergleich sehr gern. Mit einer Spende für EVMap kannst du helfen, die Kosten für den Datenzugriff zu decken.</string>
<string name="deleted_filterprofile">„%s” gelöscht</string>

View File

@@ -273,4 +273,30 @@
<string name="charging_free">gratuite</string>
<string name="about_contributors">Contributeurs</string>
<string name="about_contributors_text">Merci à tous les contributeurs pour leur contribution au codage et à la traduction d\'EVMap :</string>
<string name="location_error">Localisation non détectée. Veuillez vérifier les paramètres du système</string>
<string name="developer_mode_enabled">Mode développeur activé</string>
<string name="pref_prediction_enabled">Afficher les prévisions d\'utilisation</string>
<string name="pref_prediction_enabled_summary">pour les chargeurs pris en charge
\n(actuellement seulement chargeurs rapides en Allemagne)</string>
<string name="pref_applink_associate">Ouvrir les liens pris en charge</string>
<string name="pref_applink_associate_summary">de goingelectric.de et openchargemap.org</string>
<string name="chargeprice_header_my_tariffs">Mes tarifs</string>
<string name="chargeprice_header_other_tariffs">Autres tarifs</string>
<string name="disable_developer_mode">Désactiver le mode développeur</string>
<string name="developer_mode_disabled">Mode développeur désactivé</string>
<string name="gps">GPS</string>
<string name="compass">Boussole</string>
<string name="prediction_dc_plugs_only">Prises DC</string>
<string name="data_source_switched_to">Source de données basculée vers %s</string>
<string name="menu_reset">Réinitialiser le filtre</string>
<string name="chargeprice_price_not_available">Prix non disponible</string>
<string name="utilization_prediction">Prévision d\'utilisation</string>
<string name="prediction_help">La prédiction est basée sur des facteurs tels que le jour de la semaine, l\'heure de la journée et l\'utilisation passée, ce qui vous permet d\'éviter les chargeurs surchargés. Pas de garantie.</string>
<plurals name="prediction_number_available">
<item quantity="one">%1$d/%2$d disponible</item>
<item quantity="many">%1$d/%2$d disponibles</item>
<item quantity="other">%1$d/%2$d disponibles</item>
</plurals>
<string name="developer_options">Paramètres développeur</string>
<string name="prediction_time_colon">%s :</string>
</resources>

View File

@@ -0,0 +1,7 @@
Verbesserungen:
- Fahrzeugauswahl für Preisvergleich: Details der Fahrzeuge hinzugefügt
- Übersetzungen aktualisiert
Fehler behoben:
- Abstürze behoben
- Open Charge Map: Laden von Bildern verbessert

View File

@@ -0,0 +1,7 @@
Improvements:
- Vehicle selection for price comparison: Added vehicle details
- Updated translations
Bugfixes:
- Fixed crashes
- Open Charge Map: Improved loading of images