Compare commits

..

11 Commits
1.6.0 ... 1.6.2

Author SHA1 Message Date
johan12345
074e0bf904 Release 1.6.2 2023-06-12 08:35:02 +02:00
johan12345
41ac223e97 properly escape strings in SQL queries 2023-06-12 08:33:33 +02:00
Hosted Weblate
f7196bcce0 Translated using Weblate (Portuguese)
Currently translated at 100.0% (313 of 313 strings)

Co-authored-by: Celso Azevedo <mail@celsoazevedo.com>
Translate-URL: https://hosted.weblate.org/projects/evmap/android/pt/
Translation: EVMap/Android
2023-06-11 21:56:23 +02:00
johan12345
4f6092e5dc remove extra spatialite library 2023-06-11 21:20:38 +02:00
johan12345
dfd42e1ffd upgrade spatia-room 2023-06-11 21:17:31 +02:00
johan12345
895b24d406 Release 1.6.1 2023-06-11 20:16:26 +02:00
johan12345
3dea7993f3 clear cache with next update 2023-06-11 19:54:36 +02:00
johan12345
ca90f1b37f GoingElectricApi: infer some details based on applied filters 2023-06-11 19:34:19 +02:00
johan12345
fe0843e653 fix bug in caching algorithm that caused chargers to disappear
some filters require details that we do not get in normal queries
2023-06-11 19:19:37 +02:00
johan12345
0f42ae84de fix NPE 2023-06-11 19:17:53 +02:00
johan12345
2748b0a3db make faultReport: true result in non-null value 2023-06-11 19:05:33 +02:00
15 changed files with 124 additions and 40 deletions

View File

@@ -21,8 +21,8 @@ android {
minSdkVersion 21
targetSdkVersion 33
// NOTE: always increase versionCode by 2 since automotive flavor uses versionCode + 1
versionCode 180
versionName "1.6.0"
versionCode 184
versionName "1.6.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resConfigs supportedLocales.split(',')
@@ -246,10 +246,7 @@ dependencies {
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation('com.github.anboralabs:spatia-room:0.2.6') {
exclude group: 'com.github.dalgarins', module: 'android-spatialite'
}
implementation 'com.github.ev-map:android-spatialite:654dca2365'
implementation 'com.github.anboralabs:spatia-room:0.2.7'
// billing library
def billing_version = "6.0.0"

View File

@@ -49,6 +49,8 @@ interface ChargepointApi<out T : ReferenceData> {
fun convertFiltersToSQL(filters: FilterValues, referenceData: ReferenceData): FiltersSQLQuery
fun filteringInSQLRequiresDetails(filters: FilterValues): Boolean
val name: String
val id: String

View File

@@ -96,7 +96,7 @@ internal class JsonObjectOrFalseAdapter<T> private constructor(
false -> null // Response was false
else -> {
if (this.clazz == GEFaultReport::class.java) {
GEFaultReport(null, null) as T
GEFaultReport(null, "") as T
} else {
throw IllegalStateException("Non-false boolean for @JsonObjectOrFalse field")
}

View File

@@ -1,6 +1,7 @@
package net.vonforst.evmap.api.goingelectric
import android.content.Context
import android.database.DatabaseUtils
import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
import com.squareup.moshi.Moshi
@@ -217,7 +218,7 @@ class GoingElectricApiWrapper(
}
} while (startkey != null && startkey < 10000)
val result = postprocessResult(data, minPower, connectorsVal, minConnectors)
val result = postprocessResult(data, filters)
return Resource.success(ChargepointList(result, startkey == null))
}
@@ -308,18 +309,24 @@ class GoingElectricApiWrapper(
}
} while (startkey != null && startkey < 10000)
val result = postprocessResult(data, minPower, connectorsVal, minConnectors)
val result = postprocessResult(data, filters)
return Resource.success(ChargepointList(result, startkey == null))
}
private fun postprocessResult(
chargers: List<GEChargepointListItem>,
minPower: Int?,
connectorsVal: MultipleChoiceFilterValue?,
minConnectors: Int?
filters: FilterValues?
): List<ChargepointListItem> {
// apply filters which GoingElectric does not support natively
val minPower = filters?.getSliderValue("min_power")
val minConnectors = filters?.getSliderValue("min_connectors")
val connectorsVal = filters?.getMultipleChoiceValue("connectors")
val freecharging = filters?.getBooleanValue("freecharging")
val freeparking = filters?.getBooleanValue("freeparking")
val open247 = filters?.getBooleanValue("open_247")
val barrierfree = filters?.getBooleanValue("barrierfree")
return chargers.filter { it ->
// apply filters which GoingElectric does not support natively
if (it is GEChargeLocation) {
it.chargepoints
.filter { it.power >= (minPower ?: 0) }
@@ -328,6 +335,34 @@ class GoingElectricApiWrapper(
} else {
true
}
}.map {
// infer some properties based on applied filters
if (it is GEChargeLocation) {
var inferred = it
if (freecharging == true) {
inferred = inferred.copy(
cost = inferred.cost?.copy(freecharging = true)
?: GECost(freecharging = true)
)
}
if (freeparking == true) {
inferred = inferred.copy(
cost = inferred.cost?.copy(freeparking = true) ?: GECost(freeparking = true)
)
}
if (open247 == true) {
inferred = inferred.copy(
openinghours = inferred.openinghours?.copy(twentyfourSeven = true)
?: GEOpeningHours(twentyfourSeven = true)
)
}
if (barrierfree == true) {
inferred = inferred.copy(barrierFree = true)
}
inferred
} else {
it
}
}.map { it.convert(apikey, false) }
}
@@ -505,7 +540,13 @@ class GoingElectricApiWrapper(
val connectorsList = if (connectors.values.size == 0) {
""
} else {
"'" + connectors.values.joinToString("', '") { GEChargepoint.convertTypeFromGE(it) } + "'"
connectors.values.joinToString(",") {
DatabaseUtils.sqlEscapeString(
GEChargepoint.convertTypeFromGE(
it
)
)
}
}
result.append(" AND json_extract(cp.value, '$.type') IN (${connectorsList})")
requiresChargepointQuery = true
@@ -516,7 +557,7 @@ class GoingElectricApiWrapper(
val networksList = if (networks.values.size == 0) {
""
} else {
"'" + networks.values.joinToString("', '") + "'"
networks.values.joinToString(",") { DatabaseUtils.sqlEscapeString(it) }
}
result.append(" AND network IN (${networksList})")
}
@@ -546,5 +587,14 @@ class GoingElectricApiWrapper(
return FiltersSQLQuery(result.toString(), requiresChargepointQuery, requiresChargeCardQuery)
}
override fun filteringInSQLRequiresDetails(filters: FilterValues): Boolean {
val chargecards = filters.getMultipleChoiceValue("chargecards")
return filters.getBooleanValue("freecharging") == true
|| filters.getBooleanValue("freeparking") == true
|| filters.getBooleanValue("open_247") == true
|| filters.getBooleanValue("barrierfree") == true
|| (chargecards != null && !chargecards.all)
}
}

View File

@@ -86,10 +86,10 @@ data class GEChargeLocation(
@JsonClass(generateAdapter = true)
data class GECost(
val freecharging: Boolean,
val freeparking: Boolean,
@JsonObjectOrFalse @Json(name = "description_short") val descriptionShort: String?,
@JsonObjectOrFalse @Json(name = "description_long") val descriptionLong: String?
val freecharging: Boolean = false,
val freeparking: Boolean = false,
@JsonObjectOrFalse @Json(name = "description_short") val descriptionShort: String? = null,
@JsonObjectOrFalse @Json(name = "description_long") val descriptionLong: String? = null
) {
fun convert() = Cost(
// In GE, freecharging = false can either mean "paid charging" or "no information
@@ -104,8 +104,8 @@ data class GECost(
@JsonClass(generateAdapter = true)
data class GEOpeningHours(
@Json(name = "24/7") val twentyfourSeven: Boolean,
@JsonObjectOrFalse val description: String?,
val days: GEOpeningHoursDays?
@JsonObjectOrFalse val description: String? = null,
val days: GEOpeningHoursDays? = null
) {
fun convert() = OpeningHours(twentyfourSeven, description, days?.convert())
}

View File

@@ -1,6 +1,7 @@
package net.vonforst.evmap.api.openchargemap
import android.content.Context
import android.database.DatabaseUtils
import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
import com.squareup.moshi.Moshi
@@ -347,12 +348,14 @@ class OpenChargeMapApiWrapper(
val connectorsList = if (connectors.values.size == 0) {
""
} else {
"'" + connectors.values.joinToString("', '") {
OCMConnection.convertConnectionTypeFromOCM(
it.toLong(),
refData
connectors.values.joinToString(",") {
DatabaseUtils.sqlEscapeString(
OCMConnection.convertConnectionTypeFromOCM(
it.toLong(),
refData
)
)
} + "'"
}
}
result.append(" AND json_extract(cp.value, '$.type') IN (${connectorsList})")
requiresChargepointQuery = true
@@ -363,9 +366,9 @@ class OpenChargeMapApiWrapper(
val networksList = if (operators.values.size == 0) {
""
} else {
"'" + operators.values.joinToString("', '") { opId ->
refData.operators.find { it.id == opId.toLong() }?.title.orEmpty()
} + "'"
operators.values.joinToString(",") { opId ->
DatabaseUtils.sqlEscapeString(refData.operators.find { it.id == opId.toLong() }?.title.orEmpty())
}
}
result.append(" AND network IN (${networksList})")
}
@@ -379,4 +382,10 @@ class OpenChargeMapApiWrapper(
return FiltersSQLQuery(result.toString(), requiresChargepointQuery, false)
}
override fun filteringInSQLRequiresDetails(filters: FilterValues): Boolean {
val operators = filters.getMultipleChoiceValue("operators")
return (operators != null && !operators.all)
// TODO: it would be possible to implement this without requiring details if we extended the data structure to also save the operator ID in the DB
}
}

View File

@@ -101,7 +101,7 @@ data class OCMChargepoint(
connections.first { it.statusType != null && it.statusTypeId in faultStatuses }.statusType!!.title
)
}
return FaultReport(null, null)
return FaultReport(null, "")
} else {
return null
}

View File

@@ -161,6 +161,7 @@ class ChargeLocationsRepository(
val filtersSerialized =
filters?.filter { it.value != it.filter.defaultValue() }?.takeIf { it.isNotEmpty() }
?.serialize()
val requiresDetail = filters?.let { api.filteringInSQLRequiresDetails(it) } ?: false
val savedRegionResult = savedRegionDao.savedRegionCovers(
bounds.southwest.latitude,
bounds.northeast.latitude,
@@ -168,7 +169,8 @@ class ChargeLocationsRepository(
bounds.northeast.longitude,
api.id,
cacheSoftLimitDate(api),
filtersSerialized
filtersSerialized,
requiresDetail
)
val useClustering = shouldUseServerSideClustering(zoom)
val apiResult = liveData {
@@ -224,13 +226,15 @@ class ChargeLocationsRepository(
val filtersSerialized =
filters?.filter { it.value != it.filter.defaultValue() }?.takeIf { it.isNotEmpty() }
?.serialize()
val requiresDetail = filters?.let { api.filteringInSQLRequiresDetails(it) } ?: false
val savedRegionResult = savedRegionDao.savedRegionCoversRadius(
location.latitude,
location.longitude,
radiusMeters * 0.999, // to account for float rounding errors
api.id,
cacheSoftLimitDate(api),
filtersSerialized
filtersSerialized,
requiresDetail
)
val useClustering = shouldUseServerSideClustering(zoom)
val apiResult = liveData {

View File

@@ -4,22 +4,20 @@ import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import androidx.lifecycle.map
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import co.anbora.labs.spatia.builder.SpatiaBuilder
import co.anbora.labs.spatia.builder.SpatiaRoom
import co.anbora.labs.spatia.geometry.GeometryConverters
import kotlinx.coroutines.runBlocking
import net.vonforst.evmap.api.goingelectric.GEChargeCard
import net.vonforst.evmap.api.goingelectric.GEChargepoint
import net.vonforst.evmap.api.openchargemap.OCMConnectionType
import net.vonforst.evmap.api.openchargemap.OCMCountry
import net.vonforst.evmap.api.openchargemap.OCMOperator
import net.vonforst.evmap.model.*
import net.vonforst.evmap.viewmodel.await
@Database(
entities = [
@@ -37,7 +35,7 @@ import net.vonforst.evmap.viewmodel.await
OCMCountry::class,
OCMOperator::class,
SavedRegion::class
], version = 20
], version = 21
)
@TypeConverters(Converters::class, GeometryConverters::class)
abstract class AppDatabase : RoomDatabase() {
@@ -77,7 +75,7 @@ abstract class AppDatabase : RoomDatabase() {
MIGRATION_2, MIGRATION_3, MIGRATION_4, MIGRATION_5, MIGRATION_6,
MIGRATION_7, MIGRATION_8, MIGRATION_9, MIGRATION_10, MIGRATION_11,
MIGRATION_12, MIGRATION_13, MIGRATION_14, MIGRATION_15, MIGRATION_16,
MIGRATION_17, MIGRATION_18, MIGRATION_19, MIGRATION_20
MIGRATION_17, MIGRATION_18, MIGRATION_19, MIGRATION_20, MIGRATION_21
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
@@ -447,6 +445,13 @@ abstract class AppDatabase : RoomDatabase() {
}
}
}
private val MIGRATION_21 = object : Migration(20, 21) {
override fun migrate(db: SupportSQLiteDatabase) {
// clear cache with this update
db.execSQL("DELETE FROM savedregion")
}
}
}
/**

View File

@@ -548,7 +548,8 @@ class MapViewModel(application: Application, private val state: SavedStateHandle
val apiId = apiId.value
when (apiId) {
"goingelectric" -> {
val chargeCardsVal = filters.getMultipleChoiceValue("chargecards")!!
val chargeCardsVal =
filters.getMultipleChoiceValue("chargecards") ?: return@addSource
filteredChargeCards.value =
if (chargeCardsVal.all) null else chargeCardsVal.values.map { it.toLong() }
.toSet()

View File

@@ -58,7 +58,7 @@
<string name="fault_report_date">Com problemas (atualizado: %s)</string>
<string name="filter_chargecards">Formas de pagamento</string>
<string name="pref_language">Língua da app</string>
<string name="all_selected">Todos selecionados</string>
<string name="all_selected">Todas selecionadas</string>
<string name="edit">editar</string>
<string name="pref_darkmode">Modo escuro</string>
<string name="connection_error">Não foi possível carregar a lista de carregadores</string>
@@ -302,7 +302,7 @@
<string name="charger_website">Website</string>
<string name="realtime_data_login_needed">Conta Tesla necessária para informação em tempo real</string>
<string name="charge_price_minute_format">%2$s%1$.2f/min</string>
<string name="pref_tesla_account_disabled">Faça o login para ver os dados em tempo real dos Superchargers da Tesla. Não é necessário possuir um veículo da Tesla</string>
<string name="pref_tesla_account_disabled">Faça o login para ver informação em tempo real sobre os Tesla Superchargers. Não é necessário possuir um veículo Tesla</string>
<string name="login">Login</string>
<string name="login_error">Falha no login</string>
<string name="pricing_up_to">até %s</string>
@@ -324,4 +324,10 @@
<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="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>
<string name="settings_cache_count_summary">%d carregadores na base de dados, %.1f MB</string>
<string name="settings_caching">Caching (base de dados local)</string>
<string name="settings_cache_clear_summary">Elimina todos os carregadores guardados na base de dados local, com a exceção dos seus favoritos</string>
</resources>

View File

@@ -0,0 +1,3 @@
Fehler behoben:
- Abstürze behoben
- Fehler im Caching-Algorithmus im Zusammenspiel mit bestimmten Filtern behoben

View File

@@ -0,0 +1,2 @@
Fehler behoben:
- Abstürze im Zusammenspiel mit bestimmten Filtern behoben

View File

@@ -0,0 +1,3 @@
Bugfixes:
- Fixed crashes
- Fixed error in caching algorithm when some filters are active

View File

@@ -0,0 +1,2 @@
Bugfixes:
- Fixed crashes when some filters are active