ApplicationConfig, Favorites

This commit is contained in:
Dimitris
2026-05-08 11:08:40 +02:00
parent 72b3185280
commit 29e58f6a24
15 changed files with 110 additions and 113 deletions
+15
View File
@@ -1,3 +1,7 @@
import com.android.build.gradle.internal.tasks.AarMetadataReader.Companion.load
import java.util.Properties
import kotlin.apply
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.compose)
@@ -10,10 +14,21 @@ android {
namespace = "com.kouros.data"
compileSdk = 36
val properties = Properties().apply {
val localPropertiesFile = project.rootProject.file("local.properties")
if (localPropertiesFile.exists()) {
load(localPropertiesFile.inputStream())
}
}
defaultConfig {
minSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
buildConfigField("String", "USER", "\"${properties.getProperty("USER") ?: ""}\"")
buildConfigField("String", "PASSWORD", "\"${properties.getProperty("PASSWORD") ?: ""}\"")
}
buildFeatures {
@@ -1,7 +1,6 @@
package com.kouros.navigation.data
import android.util.Log
import java.io.File
import com.kouros.data.BuildConfig
data class ApplicationConfig(
val user: String,
@@ -10,35 +9,10 @@ data class ApplicationConfig(
companion object {
fun load(): ApplicationConfig {
fun Map<String, String>.envOrLookup(key: String): String {
return System.getenv(key) ?: this[key]!!
}
val envVars: Map<String, String> = envFile().let { envFile ->
if (envFile.exists()) {
envFile.readLines()
.map { it.split("=") }
.filter { it.size == 2 }
.associate { it.first().trim() to it.last().trim() }
} else emptyMap()
}
return ApplicationConfig(
user = envVars.envOrLookup("USER"),
password = envVars.envOrLookup("PASSWORD"),
user = BuildConfig.USER,
password = BuildConfig.PASSWORD
)
}
}
}
fun envFile(): File {
//val path = context.filesDir.absolutePath
//val config = ApplicationConfig.load(context)
return listOf(".env").map {
Log.d("Overpass", it)
File(it)
}.first { it.exists() }
}
@@ -55,6 +55,8 @@ data class Place(
var route: String = "",
@Transient
var stopOver: Boolean = false,
@Transient
var favorite: Boolean = false
)
data class ContactData(
@@ -12,6 +12,7 @@ import java.net.URL
abstract class NavigationRepository {
private val config by lazy { ApplicationConfig.load() }
private val nominatimUrl = "https://nominatim.openstreetmap.org/"
//private val nominatimUrl = "https://kouros-online.de/nominatim/"
@@ -61,11 +62,15 @@ abstract class NavigationRepository {
try {
if (authenticator) {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(
"kouros",
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
)
override fun getPasswordAuthentication(): PasswordAuthentication? {
return if (config.user.isEmpty() || config.password.isEmpty()) {
null
} else {
PasswordAuthentication(
config.user,
config.password.toCharArray()
)
}
}
})
}
@@ -3,7 +3,7 @@ package com.kouros.navigation.data.overpass
data class Elements(
val bounds: Bounds,
val geometry: List<Geometry>,
val geometry: List<Geometry> = emptyList(),
val id: Long = 0,
val lat: Double= 0.0,
val lon: Double = 0.0,
@@ -13,6 +13,7 @@ import java.net.URL
class Overpass {
private val config by lazy { com.kouros.navigation.data.ApplicationConfig.load() }
private val gson = GsonBuilder().serializeNulls().create()
var overpassUrl = if (BuildConfig.DEBUG)
@@ -121,11 +122,15 @@ class Overpass {
return try {
Authenticator.setDefault(object : Authenticator() {
override fun getPasswordAuthentication(): PasswordAuthentication {
return PasswordAuthentication(
"kouros",
"eo7sbjyWpmjSVFyELgbfrryqJ6ddNeq9".toCharArray()
)
override fun getPasswordAuthentication(): PasswordAuthentication? {
return if (config.user.isEmpty() || config.password.isEmpty()) {
null
} else {
PasswordAuthentication(
config.user,
config.password.toCharArray()
)
}
}
})
@@ -11,6 +11,7 @@ import androidx.lifecycle.viewModelScope
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.kouros.navigation.data.Constants
import com.kouros.navigation.data.Constants.FAVORITES
import com.kouros.navigation.data.Constants.SPEED_BEARING_DEVIATION
import com.kouros.navigation.data.Constants.SPEED_UPDATE_DISTANCE
import com.kouros.navigation.data.Constants.TAG
@@ -163,8 +164,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
if (rp.isNotEmpty()) {
for (place in places.places) {
if (place.category == Constants.RECENT
|| place.category == Constants.FAVORITES
|| place.category == FAVORITES
) {
if (place.category == FAVORITES) {
place.favorite = true
}
val plLocation = location(place.longitude, place.latitude)
if (place.latitude != 0.0) {
val distance =
@@ -399,11 +403,16 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
* Queries Overpass API for nearby amenities of a specific category.
* Posts sorted results to elements LiveData.
*/
fun getAmenities(carContext: Context, category: String, location: Location, lastFuelUpdate: Long = 0) {
fun getAmenities(
carContext: Context,
category: String,
location: Location,
lastFuelUpdate: Long = 0
) {
viewModelScope.launch(Dispatchers.IO) {
val repository = getSettingsRepository(carContext)
val amenities = Overpass().getAmenities("amenity", category, location, 5.0)
val fuelPrices = fuelStations(category, lastFuelUpdate, location, repository)
val fuelPrices = fuelStations(category, lastFuelUpdate, location, repository)
val distAmenities = mutableListOf<Elements>()
amenities.forEach {
val plLocation =
@@ -594,7 +603,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
synchronized(this) {
val stations =
FuelPrices().getFuelPrices(location, 3)
Log.d(TAG, "FuelPrices $stations")
Log.d(TAG, "FuelPrices $stations")
}
}
}
@@ -649,6 +658,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
place.lastDate = current.atZone(ZoneOffset.UTC).toEpochSecond()
place.route = ""
places.add(place)
recentPlaces.postValue(places)
settingsRepository.setRecentPlaces(gson.toJson(Places(places)))
} catch (e: Exception) {
e.printStackTrace()
@@ -683,7 +693,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
}
}
settingsRepository.setRecentPlaces(gson.toJson(Places(places)))
recentPlaces.value = places
recentPlaces.postValue(places)
}
} catch (e: Exception) {
e.printStackTrace()
@@ -23,10 +23,8 @@ class RouteCalculator(var routeModel: RouteModel) {
fun findStep(location: Location) {
val route = routeModel.navState.route
val steps = routeModel.curLeg.steps
val startIndex = route.currentStepIndex
// Windowed search for performance (current + 2 steps)
//val endIndex = (startIndex + 2).coerceAtMost(steps.size - 1)
// Search from one step prior to handle deviations/U-turns
val startIndex = (route.currentStepIndex - 1).coerceAtLeast(0)
val endIndex = steps.size - 1
var nearestDistance = MAXIMUM_LOCATION_DISTANCE
@@ -35,7 +33,8 @@ class RouteCalculator(var routeModel: RouteModel) {
for (i in startIndex..endIndex) {
val step = steps[i]
val waypoints = step.maneuver.waypoints
val startWayIndex = if (i == startIndex) step.waypointIndex else 0
// Only offset search if we are exactly on the current stored step index
val startWayIndex = if (i == route.currentStepIndex) step.waypointIndex else 0
var lastDistance = Float.MAX_VALUE
var increaseCount = 0
@@ -50,13 +49,12 @@ class RouteCalculator(var routeModel: RouteModel) {
bestMatch = StepMatch(step.index, j, waypoint)
}
// Track if we are getting further away
if (distance > lastDistance) {
increaseCount++
} else {
increaseCount = 0
}
// Absolute early exit: found a close point and now moved far away
if (stopSearch(nearestDistance, distance, increaseCount)) {
bestMatch?.let { match ->
route.currentStepIndex = match.stepIndex
@@ -73,14 +71,14 @@ class RouteCalculator(var routeModel: RouteModel) {
route.currentStepIndex = match.stepIndex
steps[match.stepIndex].waypointIndex = match.waypointIndex
steps[match.stepIndex].wayPointLocation = match.location
}
Log.d(TAG, "FindStep not optimized: count=$searchCount, dist=$nearestDistance")
}
private fun stopSearch(nearestDistance: Float, distance: Float, increaseCount: Int): Boolean {
return (nearestDistance < NEAREST_LOCATION_DISTANCE && distance > NEAREST_LOCATION_DISTANCE * 10)
|| increaseCount > 10
// Increased multiplier from 10 to 20 to be less aggressive in sharp curves
return (nearestDistance < NEAREST_LOCATION_DISTANCE && distance > NEAREST_LOCATION_DISTANCE * 20)
|| increaseCount > 15
}
fun travelLeftTime(): Double {
@@ -101,7 +101,7 @@ class RouteCalculatorTest {
}
@Test
fun `findStep skips all steps before currentStepIndex`() {
fun `findStep considers previous step when searching`() {
val step0 = createStep(index = 0, numWaypoints = 2)
val step1 = createStep(index = 1, numWaypoints = 2)
routeModel.navState = routeModel.navState.copy(
@@ -109,17 +109,17 @@ class RouteCalculatorTest {
)
val mockLocation: Location = mock()
whenever(mockLocation.distanceTo(any())).thenReturn(200F, 50F)
// Distance to step0 waypoints is very small, distance to step1 waypoints is large
whenever(mockLocation.distanceTo(any())).thenReturn(5F, 5F, 500F, 500F)
routeCalculator.findStep(mockLocation)
// step0 is skipped, so distanceTo is only called for step1's 2 waypoints
verify(mockLocation, times(2)).distanceTo(any())
assertEquals(1, routeModel.navState.route.currentStepIndex)
assertEquals(0, routeModel.navState.route.currentStepIndex)
}
@Test
fun `findStep breaks early once nearestDistance drops below NEAREST_LOCATION_DISTANCE`() {
fun `findStep breaks later with relaxed distance threshold`() {
val step0 = createStep(index = 0, numWaypoints = 2)
val step1 = createStep(index = 1, numWaypoints = 2)
val step2 = createStep(index = 2, numWaypoints = 2)
@@ -128,17 +128,18 @@ class RouteCalculatorTest {
)
val mockLocation: Location = mock()
// step0/wp0: 500F, step0/wp1: 5F, step1/wp0: 200F
// 5F < NEAREST_LOCATION_DISTANCE (10F) AND 200F > 10 * 10F → break
whenever(mockLocation.distanceTo(any())).thenReturn(500F, 5F, 200F)
// step0/wp0: 500F, step0/wp1: 5F, step1/wp0: 150F, step1/wp1: 160F, step2/wp0: 210F, step2/wp1: 220F
// Here we purposefully exceed the 20 * 10F threshold at the end of step 1 or start of step 2
whenever(mockLocation.distanceTo(any())).thenReturn(500F, 5F, 150F, 160F, 210F, 220F)
routeCalculator.findStep(mockLocation)
// should stop after the 3rd call (step1/wp0 triggers stopSearch)
verify(mockLocation, times(3)).distanceTo(any())
// It should haveChecked step 0 (2), step 1 (2), and the first point of step 2 (1) where it finally breaks.
verify(mockLocation, times(5)).distanceTo(any())
assertEquals(0, routeModel.navState.route.currentStepIndex)
}
// ----------------------------------------------------------
// travelLeftTime
// ----------------------------------------------------------