Compare commits

..

14 Commits

Author SHA1 Message Date
johan12345
23387ae371 Release 1.0.0 2021-10-02 14:08:44 +02:00
johan12345
25f466b6d7 Merge branch 'place-search-recents' 2021-10-02 13:49:25 +02:00
johan12345
6692b21bf9 add non-dark Mapbox logo 2021-10-02 13:48:50 +02:00
johan12345
5959fe8be4 limit number of autocomplete results 2021-10-02 13:40:22 +02:00
johan12345
00f4c13fcc add button to delete search history 2021-10-02 13:33:51 +02:00
johan12345
47054d470b replace deprecated method getAdapterPosition 2021-10-02 13:23:30 +02:00
Johan von Forstner
d10192cae1 save recent place search results
fixes #128
2021-10-02 13:19:33 +02:00
johan12345
e1b90955c3 handle back button correctly
fixes #115
2021-10-02 13:18:27 +02:00
johan12345
d249bf47c7 improve title of filter profiles editing 2021-10-02 13:15:58 +02:00
johan12345
738dcd5f8d do not allow blank names for filter profiles 2021-10-02 13:11:05 +02:00
johan12345
ad4f32ec32 fix missing filter name in title of FilterFragment 2021-10-02 12:47:22 +02:00
Johan von Forstner
4d03107ae7 add links to translated FAQ and privacy policy
#110
2021-09-26 16:17:44 +02:00
Johan von Forstner
0e93e310bf MultiSelectDialog: move selected entries to the top
fixes #126
2021-09-26 16:17:44 +02:00
Johan von Forstner
6cb8940696 Catch IllegalArgumentException in navigation library 2021-09-26 14:47:55 +02:00
25 changed files with 343 additions and 52 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId "net.vonforst.evmap"
minSdkVersion 21
targetSdkVersion 31
versionCode 62
versionName "1.0.0-beta04"
versionCode 63
versionName "1.0.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -28,6 +28,8 @@ class GooglePlacesAutocompleteProvider(val context: Context) : AutocompleteProvi
private val client = Places.createClient(context)
private val bold: CharacterStyle = StyleSpan(Typeface.BOLD)
override val id = "google"
override fun autocomplete(
query: String,
location: com.car2go.maps.model.LatLng?
@@ -48,7 +50,7 @@ class GooglePlacesAutocompleteProvider(val context: Context) : AutocompleteProvi
it.getPrimaryText(bold),
it.getSecondaryText(bold),
it.placeId,
it.distanceMeters,
it.distanceMeters?.toDouble(),
it.placeTypes.map { AutocompletePlaceType.valueOf(it.name) })
}
} catch (e: ExecutionException) {

View File

@@ -1,6 +1,8 @@
package net.vonforst.evmap.adapter
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -13,7 +15,10 @@ import net.vonforst.evmap.autocomplete.*
import net.vonforst.evmap.containsAny
import net.vonforst.evmap.databinding.ItemAutocompleteResultBinding
import net.vonforst.evmap.isDarkMode
import net.vonforst.evmap.storage.AppDatabase
import net.vonforst.evmap.storage.PreferenceDataSource
import net.vonforst.evmap.storage.RecentAutocompletePlace
import java.time.Instant
class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatLng>) :
BaseAdapter(), Filterable {
@@ -21,7 +26,10 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
private val providers = getAutocompleteProviders(context)
private val typeItem = 0
private val typeAttribution = 1
var currentProvider: AutocompleteProvider? = null
private val maxItems = 6
private var currentProvider: AutocompleteProvider? = null
private val recents = AppDatabase.getInstance(context).recentAutocompletePlaceDao()
private var recentResults = mutableListOf<RecentAutocompletePlace>()
data class ViewHolder(val binding: ItemAutocompleteResultBinding)
@@ -103,20 +111,40 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
}
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
val query = constraint.toString()
if (constraint != null) {
for (provider in providers) {
try {
resultList =
provider.autocomplete(constraint.toString(), location.value)
recentResults.clear()
currentProvider = provider
// first search in recent places
val recentPlaces = if (query.isEmpty()) {
recents.getAll(provider.id, limit = maxItems)
} else {
recents.search(query, provider.id, limit = maxItems)
}
recentResults.addAll(recentPlaces)
resultList = recentPlaces.map { it.asAutocompletePlace(location.value) }
Handler(Looper.getMainLooper()).post {
// publish intermediate results on main thread
publishResults(constraint, resultList.asFilterResults())
}
// if we already have enough results or the query is short, stop here
if (query.length < 3 || recentResults.size >= maxItems) break
// then search online
val recentIds = recentPlaces.map { it.id }
resultList =
(recentPlaces.map { it.asAutocompletePlace(location.value) } +
provider.autocomplete(query, location.value)
.filter { !recentIds.contains(it.id) }).take(maxItems)
break
} catch (e: ApiUnavailableException) {
e.printStackTrace()
}
}
filterResults.values = resultList
filterResults.count = resultList!!.size
}
@@ -125,16 +153,42 @@ class PlaceAutocompleteAdapter(val context: Context, val location: LiveData<LatL
this.setDelayer { 500L }
}
return filterResults
return resultList.asFilterResults()
}
private fun List<AutocompletePlace>?.asFilterResults(): FilterResults {
val result = FilterResults()
if (this != null) {
result.values = this
result.count = this.size
}
return result
}
}
}
suspend fun getDetails(id: String): PlaceWithBounds {
val provider = currentProvider!!
val result = resultList!!.find { it.id == id }!!
val recentPlace = recentResults.find { it.id == id }
if (recentPlace != null) return recentPlace.asPlaceWithBounds()
val details = provider.getDetails(id)
recents.insert(RecentAutocompletePlace(result, details, provider.id, Instant.now()))
return details
}
}
fun iconForPlaceType(types: List<AutocompletePlaceType>): Int =
when {
types.contains(
AutocompletePlaceType.RECENT
) -> R.drawable.ic_history
types.containsAny(
AutocompletePlaceType.LIGHT_RAIL_STATION,
AutocompletePlaceType.BUS_STATION,
@@ -153,4 +207,7 @@ fun iconForPlaceType(types: List<AutocompletePlaceType>): Int =
}
fun isSpecialPlace(types: List<AutocompletePlaceType>): Boolean =
iconForPlaceType(types) != R.drawable.ic_place_type_default
!setOf(
R.drawable.ic_place_type_default,
R.drawable.ic_history
).contains(iconForPlaceType(types))

View File

@@ -6,6 +6,8 @@ import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
interface AutocompleteProvider {
val id: String
fun autocomplete(query: String, location: LatLng?): List<AutocompletePlace>
suspend fun getDetails(id: String): PlaceWithBounds
@@ -20,7 +22,7 @@ data class AutocompletePlace(
val primaryText: CharSequence,
val secondaryText: CharSequence,
val id: String,
val distanceMeters: Int?,
val distanceMeters: Double?,
val types: List<AutocompletePlaceType>
)
@@ -167,7 +169,8 @@ enum class AutocompletePlaceType {
TRAVEL_AGENCY,
UNIVERSITY,
VETERINARY_CARE,
ZOO;
ZOO,
RECENT;
companion object {
fun valueOfOrNull(value: String): AutocompletePlaceType? {

View File

@@ -17,12 +17,13 @@ import com.mapbox.geojson.BoundingBox
import com.mapbox.geojson.Point
import net.vonforst.evmap.R
import java.io.IOException
import kotlin.math.roundToInt
class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
private val bold: CharacterStyle = StyleSpan(Typeface.BOLD)
private val results = HashMap<String, CarmenFeature>()
override val id = "mapbox"
override fun autocomplete(query: String, location: LatLng?): List<AutocompletePlace> {
val result = MapboxGeocoding.builder().apply {
location?.let {
@@ -58,7 +59,7 @@ class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
location?.let { location ->
SphericalUtil.computeDistanceBetween(
feature.center()!!.toLatLng(), location
).roundToInt()
)
},
getPlaceTypes(feature)
)
@@ -112,7 +113,8 @@ class MapboxAutocompleteProvider(val context: Context) : AutocompleteProvider {
override fun getAttributionString(): Int = R.string.powered_by_mapbox
override fun getAttributionImage(dark: Boolean): Int = R.drawable.mapbox_logo_icon
override fun getAttributionImage(dark: Boolean): Int =
if (dark) R.drawable.mapbox_logo_icon else R.drawable.mapbox_logo
}
private fun BoundingBox.toLatLngBounds(): LatLngBounds {

View File

@@ -43,12 +43,6 @@ class FilterFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
(requireActivity() as AppCompatActivity).setSupportActionBar(binding.toolbar)
vm.filterProfile.observe(viewLifecycleOwner) {
if (it != null) {
binding.toolbar.title = "${getString(R.string.menu_filter)}: ${it.name}"
}
}
binding.filtersList.apply {
adapter = FiltersAdapter()
layoutManager =
@@ -80,34 +74,52 @@ class FilterFragment : Fragment() {
true
}
R.id.menu_save_profile -> {
showEditTextDialog(requireContext()) { dialog, input ->
vm.filterProfile.value?.let { profile ->
input.setText(profile.name)
}
dialog.setTitle(R.string.save_as_profile)
.setMessage(R.string.save_profile_enter_name)
.setPositiveButton(R.string.ok) { di, button ->
lifecycleScope.launch {
vm.saveAsProfile(input.text.toString())
findNavController().popBackStack()
}
}
.setNegativeButton(R.string.cancel) { di, button ->
}
}
saveProfile()
true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun saveProfile(error: Boolean = false) {
showEditTextDialog(requireContext()) { dialog, input ->
vm.filterProfile.value?.let { profile ->
input.setText(profile.name)
}
if (error) {
input.error = getString(R.string.required)
}
dialog.setTitle(R.string.save_as_profile)
.setMessage(R.string.save_profile_enter_name)
.setPositiveButton(R.string.ok) { di, button ->
if (input.text.isBlank()) {
saveProfile(true)
} else {
lifecycleScope.launch {
vm.saveAsProfile(input.text.toString())
findNavController().popBackStack()
}
}
}
.setNegativeButton(R.string.cancel) { di, button ->
}
}
}
override fun onResume() {
super.onResume()
binding.toolbar.setupWithNavController(
findNavController(),
(requireActivity() as MapsActivity).appBarConfiguration
)
vm.filterProfile.observe(viewLifecycleOwner) {
if (it != null) {
binding.toolbar.title = getString(R.string.edit_filter_profile, it.name)
}
}
}
}

View File

@@ -67,8 +67,8 @@ class FilterProfilesFragment : Fragment() {
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val fromPos = viewHolder.adapterPosition;
val toPos = target.adapterPosition;
val fromPos = viewHolder.bindingAdapterPosition;
val toPos = target.bindingAdapterPosition;
val list = vm.filterProfiles.value?.toMutableList()
if (list != null) {

View File

@@ -242,6 +242,8 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
findNavController().navigate(R.id.action_map_to_opensource_donations)
} catch (ignored: IllegalArgumentException) {
// when there is already another navigation going on
} catch (ignored: IllegalStateException) {
// "no current navigation node"
}
}
/*if (!prefs.update060AndroidAutoDialogShown) {
@@ -368,7 +370,7 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
val place = adapter.getItem(position) ?: return@OnItemClickListener
lifecycleScope.launch {
try {
vm.searchResult.value = adapter.currentProvider!!.getDetails(place.id)
vm.searchResult.value = adapter.getDetails(place.id)
} catch (e: ApiUnavailableException) {
e.printStackTrace()
} catch (e: IOException) {
@@ -388,9 +390,6 @@ class MapFragment : Fragment(), OnMapReadyCallback, MapsActivity.FragmentCallbac
binding.search.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
binding.search.keyListener = searchKeyListener
if (binding.search.text.isNotEmpty() && isVisible) {
binding.search.showDropDown()
}
} else {
binding.search.keyListener = null
}

View File

@@ -79,7 +79,13 @@ class MultiSelectDialog : AppCompatDialogFragment() {
items = data.entries.toList()
.sortedBy { it.value.toLowerCase(Locale.getDefault()) }
.sortedByDescending { commonChoices?.contains(it.key) == true }
.sortedBy {
when {
selected.contains(it.key) -> 0
commonChoices?.contains(it.key) == true -> 1
else -> 2
}
}
.map { MultiSelectItem(it.key, it.value, it.key in selected) }
adapter.submitList(items)

View File

@@ -102,6 +102,12 @@ class SettingsFragment : PreferenceFragmentCompat(),
override fun onPreferenceTreeClick(preference: Preference?): Boolean {
return when (preference?.key) {
"search_delete_recent" -> {
Toast.makeText(context, R.string.deleted_recent_search_results, Toast.LENGTH_LONG)
.show()
vm.deleteRecentSearchResults()
true
}
else -> super.onPreferenceTreeClick(preference)
}
}

View File

@@ -24,19 +24,21 @@ import net.vonforst.evmap.model.*
MultipleChoiceFilterValue::class,
SliderFilterValue::class,
FilterProfile::class,
RecentAutocompletePlace::class,
GEPlug::class,
GENetwork::class,
GEChargeCard::class,
OCMConnectionType::class,
OCMCountry::class,
OCMOperator::class
], version = 13
], version = 14
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun chargeLocationsDao(): ChargeLocationsDao
abstract fun filterValueDao(): FilterValueDao
abstract fun filterProfileDao(): FilterProfileDao
abstract fun recentAutocompletePlaceDao(): RecentAutocompletePlaceDao
// GoingElectric API specific
abstract fun geReferenceDataDao(): GEReferenceDataDao
@@ -51,7 +53,7 @@ abstract class AppDatabase : RoomDatabase() {
.addMigrations(
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_12, MIGRATION_13, MIGRATION_14
)
.addCallback(object : Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
@@ -303,5 +305,12 @@ abstract class AppDatabase : RoomDatabase() {
}
}
}
private val MIGRATION_14 = object : Migration(13, 14) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS `RecentAutocompletePlace` (`id` TEXT NOT NULL, `dataSource` TEXT NOT NULL, `timestamp` INTEGER NOT NULL, `primaryText` TEXT NOT NULL, `secondaryText` TEXT NOT NULL, `latLng` TEXT NOT NULL, `viewport` TEXT, `types` TEXT NOT NULL, PRIMARY KEY(`id`, `dataSource`))");
}
}
}
}

View File

@@ -0,0 +1,70 @@
package net.vonforst.evmap.storage
import androidx.room.*
import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
import net.vonforst.evmap.autocomplete.AutocompletePlace
import net.vonforst.evmap.autocomplete.AutocompletePlaceType
import net.vonforst.evmap.autocomplete.PlaceWithBounds
import net.vonforst.evmap.utils.distanceBetween
import java.time.Instant
@Entity(primaryKeys = ["id", "dataSource"])
data class RecentAutocompletePlace(
val id: String,
val dataSource: String,
var timestamp: Instant,
val primaryText: String,
val secondaryText: String,
val latLng: LatLng,
val viewport: LatLngBounds?,
val types: List<AutocompletePlaceType>
) {
constructor(
place: AutocompletePlace,
details: PlaceWithBounds,
dataSource: String,
timestamp: Instant
) : this(
place.id, dataSource, timestamp, place.primaryText.toString(),
place.secondaryText.toString(), details.latLng, details.viewport, place.types
)
fun asAutocompletePlace(currentLocation: LatLng?): AutocompletePlace {
return AutocompletePlace(
primaryText,
secondaryText,
id,
currentLocation?.let {
distanceBetween(
latLng.latitude, latLng.longitude,
it.latitude, it.longitude
)
},
types + AutocompletePlaceType.RECENT
)
}
fun asPlaceWithBounds(): PlaceWithBounds {
return PlaceWithBounds(latLng, viewport)
}
}
@Dao
abstract class RecentAutocompletePlaceDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract suspend fun insert(vararg places: RecentAutocompletePlace)
@Query("DELETE FROM recentautocompleteplace")
abstract suspend fun deleteAll()
@Query("SELECT * FROM recentautocompleteplace WHERE dataSource = :dataSource AND primaryText LIKE '%' || :query || '%' ORDER BY timestamp DESC LIMIT :limit")
abstract fun search(
query: String,
dataSource: String,
limit: Int? = null
): List<RecentAutocompletePlace>
@Query("SELECT * FROM recentautocompleteplace WHERE dataSource = :dataSource ORDER BY timestamp DESC LIMIT :limit")
abstract fun getAll(dataSource: String, limit: Int? = null): List<RecentAutocompletePlace>
}

View File

@@ -1,11 +1,14 @@
package net.vonforst.evmap.storage
import androidx.room.TypeConverter
import com.car2go.maps.model.LatLng
import com.car2go.maps.model.LatLngBounds
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.squareup.moshi.adapters.PolymorphicJsonAdapterFactory
import net.vonforst.evmap.api.goingelectric.GEChargerPhotoAdapter
import net.vonforst.evmap.api.openchargemap.OCMChargerPhotoAdapter
import net.vonforst.evmap.autocomplete.AutocompletePlaceType
import net.vonforst.evmap.model.ChargeCardId
import net.vonforst.evmap.model.Chargepoint
import net.vonforst.evmap.model.ChargerPhoto
@@ -41,6 +44,12 @@ class Converters {
val type = Types.newParameterizedType(List::class.java, String::class.java)
moshi.adapter<List<String>>(type)
}
private val latLngAdapter by lazy {
moshi.adapter<LatLng>(LatLng::class.java)
}
private val latLngBoundsAdapter by lazy {
moshi.adapter<LatLngBounds>(LatLngBounds::class.java)
}
@TypeConverter
fun fromChargepointList(value: List<Chargepoint>?): String {
@@ -115,4 +124,34 @@ class Converters {
fun toStringList(value: String): List<String>? {
return stringListAdapter.fromJson(value)
}
@TypeConverter
fun fromLatLng(value: LatLng?): String {
return latLngAdapter.toJson(value)
}
@TypeConverter
fun toLatLng(value: String): LatLng? {
return latLngAdapter.fromJson(value)
}
@TypeConverter
fun fromLatLngBounds(value: LatLngBounds?): String {
return latLngBoundsAdapter.toJson(value)
}
@TypeConverter
fun toLatLngBounds(value: String): LatLngBounds? {
return latLngBoundsAdapter.fromJson(value)
}
@TypeConverter
fun fromAutocompletePlaceTypeList(value: List<AutocompletePlaceType>): String {
return value.joinToString(",") { it.name }
}
@TypeConverter
fun toAutocompletePlaceTypeList(value: String): List<AutocompletePlaceType> {
return value.split(",").map { AutocompletePlaceType.valueOf(it) }
}
}

View File

@@ -0,0 +1,20 @@
package net.vonforst.evmap.ui
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
class AutocompleteTextViewWithSuggestions(ctx: Context, args: AttributeSet) :
androidx.appcompat.widget.AppCompatAutoCompleteTextView(ctx, args) {
override fun enoughToFilter(): Boolean = true
override fun onFocusChanged(
focused: Boolean, direction: Int,
previouslyFocusedRect: Rect?
) {
super.onFocusChanged(focused, direction, previouslyFocusedRect)
if (focused && adapter != null) {
performFiltering(text, 0)
}
}
}

View File

@@ -8,11 +8,13 @@ import kotlinx.coroutines.launch
import net.vonforst.evmap.api.chargeprice.ChargepriceApi
import net.vonforst.evmap.api.chargeprice.ChargepriceCar
import net.vonforst.evmap.api.chargeprice.ChargepriceTariff
import net.vonforst.evmap.storage.AppDatabase
import java.io.IOException
class SettingsViewModel(application: Application, chargepriceApiKey: String) :
AndroidViewModel(application) {
private var api = ChargepriceApi.create(chargepriceApiKey)
private var db = AppDatabase.getInstance(application)
val vehicles: MutableLiveData<Resource<List<ChargepriceCar>>> by lazy {
MutableLiveData<Resource<List<ChargepriceCar>>>().apply {
@@ -49,4 +51,10 @@ class SettingsViewModel(application: Application, chargepriceApiKey: String) :
}
}
}
fun deleteRecentSearchResults() {
viewModelScope.launch {
db.recentAutocompletePlaceDao().deleteAll()
}
}
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="81dp"
android:height="20dp"
android:viewportWidth="81"
android:viewportHeight="20">
<path
android:fillColor="?colorControlNormal"
android:pathData="M9.1,0.9C4.1,0.9 0,5 0,10s4.1,9.1 9.1,9.1s9.1,-4.1 9.1,-9.1S14.1,0.9 9.1,0.9zM55.7,2.9c-0.1,0 -0.2,0.1 -0.2,0.2v10.5c0,0.1 0.1,0.2 0.2,0.2h1.4c0.1,0 0.2,-0.1 0.2,-0.2v-0.7c0.7,0.7 1.7,1.2 2.7,1.2c2.1,0 3.9,-1.8 3.9,-4.1S62.1,5.9 60,5.9c-1,0 -2,0.4 -2.7,1.2V3.1c0,-0.1 -0.1,-0.2 -0.2,-0.2H55.7zM10,4.4c1.2,0 2.3,0.5 3.2,1.4c1.8,1.8 1.9,4.7 0.1,6.4c-3.1,3.1 -8.6,2.1 -8.6,2.1s-1,-5.5 2.1,-8.6C7.7,4.8 8.9,4.4 10,4.4zM26.4,5.9c-0.8,0 -1.6,0.4 -2.1,1.1V6.3c0,-0.1 -0.1,-0.2 -0.2,-0.2h-1.4c-0.1,0 -0.2,0.1 -0.2,0.2v7.4c0,0.1 0.1,0.2 0.2,0.2H24c0.1,0 0.2,-0.1 0.2,-0.2V9.3c0.1,-1 0.7,-1.8 1.6,-1.8c0.9,0 1.6,0.7 1.6,1.7v4.5c0,0.1 0.1,0.2 0.2,0.2H29c0.1,0 0.2,-0.1 0.2,-0.2l0,-4.6c0.1,-0.9 0.8,-1.6 1.6,-1.6c0.9,0 1.6,0.7 1.6,1.7v4.5c0,0.1 0.1,0.2 0.2,0.2H34c0.1,0 0.2,-0.1 0.2,-0.2l0,-5.1c0,-1.5 -1.3,-2.7 -2.8,-2.7c-1,0 -2,0.6 -2.4,1.5C28.4,6.5 27.4,5.9 26.4,5.9L26.4,5.9zM39.4,5.9c-2.1,0 -3.9,1.8 -3.9,4.1s1.7,4.1 3.9,4.1c1,0 2,-0.4 2.7,-1.2v0.7c0,0.1 0.1,0.2 0.2,0.2h1.4c0.1,0 0.2,-0.1 0.2,-0.2V6.3c0,-0.1 -0.1,-0.2 -0.2,-0.2h-1.4c-0.1,0 -0.2,0.1 -0.2,0.2V7C41.4,6.3 40.4,5.9 39.4,5.9zM50.2,5.9c-1,0 -2,0.4 -2.7,1.2V6.3c0,-0.1 -0.1,-0.2 -0.2,-0.2h-1.4c-0.1,0 -0.2,0.1 -0.2,0.2v10.5c0,0.1 0.1,0.2 0.2,0.2h1.4c0.1,0 0.2,-0.1 0.2,-0.2v-3.9c0.7,0.7 1.7,1.2 2.7,1.2c2.1,0 3.9,-1.8 3.9,-4.1S52.3,5.9 50.2,5.9zM69.1,5.9c-2.3,0 -4.2,1.8 -4.2,4.1s1.9,4.1 4.2,4.1c2.3,0 4.2,-1.8 4.2,-4.1S71.4,5.9 69.1,5.9zM73.7,6.1c-0.1,0 -0.2,0.1 -0.2,0.2c0,0 0,0.1 0,0.1l2.3,3.6l-2.4,3.6c-0.1,0.1 0,0.2 0.1,0.3c0,0 0.1,0 0.1,0h1.6c0.1,0 0.2,-0.1 0.3,-0.2l1.4,-2.4l1.4,2.4c0.1,0.1 0.2,0.2 0.3,0.2h1.6c0.1,0 0.2,-0.1 0.2,-0.2c0,0 0,-0.1 0,-0.1L78.1,10l2.3,-3.6c0.1,-0.1 0,-0.2 -0.1,-0.3c0,0 -0.1,0 -0.1,0h-1.6c-0.1,0 -0.2,0.1 -0.3,0.2L77,8.6l-1.4,-2.3c-0.1,-0.1 -0.2,-0.2 -0.3,-0.2H73.7zM10.1,6.2L9.2,8.1L7.4,8.9l1.8,0.9l0.9,1.8L11,9.8l1.8,-0.9L11,8.1L10.1,6.2zM39.7,7.5c1.3,0 2.3,1.1 2.4,2.4V10c0,1.3 -1.1,2.4 -2.4,2.4c-1.3,0 -2.4,-1.1 -2.4,-2.5S38.4,7.5 39.7,7.5L39.7,7.5zM49.9,7.5c1.3,0 2.4,1.1 2.4,2.5s-1.1,2.5 -2.4,2.5c-1.3,0 -2.3,-1.1 -2.4,-2.4V10C47.5,8.6 48.6,7.5 49.9,7.5L49.9,7.5zM59.7,7.5C61,7.5 62,8.6 62,10s-1.1,2.5 -2.4,2.5c-1.3,0 -2.3,-1.1 -2.4,-2.4V10C57.3,8.6 58.4,7.5 59.7,7.5L59.7,7.5zM69.1,7.5c1.3,0 2.4,1.1 2.4,2.5s-1.1,2.5 -2.4,2.5c-1.3,0 -2.4,-1.1 -2.4,-2.5S67.8,7.5 69.1,7.5z" />
</vector>

View File

@@ -10,7 +10,8 @@
android:name="net.vonforst.evmap.navigation.NavHostFragment"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:fitsSystemWindows="true" />
android:fitsSystemWindows="true"
app:defaultNavHost="true" />
<com.google.android.material.navigation.NavigationView
android:id="@+id/nav_view"

View File

@@ -69,7 +69,7 @@
android:focusable="true"
android:focusableInTouchMode="true">
<AutoCompleteTextView
<net.vonforst.evmap.ui.AutocompleteTextViewWithSuggestions
android:id="@+id/search"
android:layout_width="0dp"
android:layout_weight="1"

View File

@@ -238,4 +238,10 @@
<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>
<string name="unnamed_filter_profile">Unbenanntes Filterprofil</string>
<string name="privacy_link">https://evmap.vonforst.net/de/privacy.html</string>
<string name="faq_link">https://evmap.vonforst.net/de/faq.html</string>
<string name="required">erforderlich</string>
<string name="edit_filter_profile">„%s“ bearbeiten</string>
<string name="pref_search_delete_recent">Suchverlauf löschen</string>
<string name="deleted_recent_search_results">Suchverlauf wurde gelöscht</string>
</resources>

View File

@@ -2,8 +2,6 @@
<resources>
<string name="shared_element_picture">picture</string>
<string name="github_link">https://github.com/johan12345/EVMap</string>
<string name="privacy_link">https://evmap.vonforst.net/privacy.html</string>
<string name="faq_link">https://evmap.vonforst.net/faq.html</string>
<string name="twitter_handle">\@ev_map</string>
<string name="twitter_url">https://twitter.com/ev_map</string>
<string name="goingelectric_forum_url"><![CDATA[https://www.goingelectric.de/forum/viewtopic.php?f=5&t=56342]]></string>

View File

@@ -223,4 +223,10 @@
<string name="donate_desc">Support EVMap\'s development with a one-time donation</string>
<string name="github_sponsors_desc">Support EVMap on GitHub Sponsors</string>
<string name="unnamed_filter_profile">Unnamed filter profile</string>
<string name="privacy_link">https://evmap.vonforst.net/en/privacy.html</string>
<string name="faq_link">https://evmap.vonforst.net/en/faq.html</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>
<string name="deleted_recent_search_results">Recent search results have been deleted</string>
</resources>

View File

@@ -47,6 +47,10 @@
android:defaultValue="@string/pref_search_provider_default"
android:summary="%s" />
<Preference
android:key="search_delete_recent"
android:title="@string/pref_search_delete_recent" />
<CheckBoxPreference
android:key="navigate_use_maps"
android:title="@string/pref_navigate_use_maps"

View File

@@ -0,0 +1,12 @@
Neue Funktionen:
- Android Auto: Preisvergleich mit Chargeprice
- Android Auto: Neuer Bildschirm zur Anzeige von Fahrzeugdaten (falls verfügbar)
- Kürzlich gesuchte Orte werden gespeichert und als Vorschläge angezeigt
- Unterstützung für Android 12
Verbesserungen:
- Android Auto: Nutzung des Fahrzeug-GPS (falls verfügbar, ab Android Auto 6.7)
- In Auswahldialogen werden bereits ausgewählte Einträge nach oben verschoben
- FAQ aktualisiert
- Verbesserte Behandlung des Zurück-Buttons
- Verschiedene Abstürze behoben

View File

@@ -0,0 +1,12 @@
New features:
- Android Auto: Price comparison with Chargeprice (only in certain countries)
- Android Auto: New screen displaying vehicle data (if available)
- Recently searched places are stored and shown as suggestions
- Android 12 support
Improvements:
- Android Auto: Use vehicle GPS (if available, starting with Android Auto 6.7)
- Move already selected entries to the top in selection dialogs
- FAQ and privacy policy updated and now also available in English
- Improved back button handling
- Fixed various crashes