Preview
This commit is contained in:
@@ -130,6 +130,8 @@ object Constants {
|
||||
|
||||
const val TRAFFIC_UPDATE = 300
|
||||
|
||||
const val ROUTE_UPDATE = 60
|
||||
|
||||
const val INSTRUCTION_DISTANCE = 50
|
||||
const val GMS_CAR_SPEED_PERMISSION = "com.google.android.gms.permission.CAR_SPEED"
|
||||
|
||||
|
||||
@@ -1,29 +1,7 @@
|
||||
/*
|
||||
* Copyright 2023 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.kouros.navigation.data
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.osrm.OsrmRepository
|
||||
import com.kouros.navigation.data.osrm.OsrmResponse
|
||||
import com.kouros.navigation.data.osrm.OsrmRoute
|
||||
import com.kouros.navigation.model.RouteModel
|
||||
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||
import java.net.Authenticator
|
||||
import java.net.HttpURLConnection
|
||||
@@ -41,7 +19,7 @@ abstract class NavigationRepository {
|
||||
abstract fun getRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
destination: Location,
|
||||
carOrientation: Float,
|
||||
searchFilter: SearchFilter
|
||||
): String
|
||||
@@ -59,7 +37,7 @@ abstract class NavigationRepository {
|
||||
}
|
||||
|
||||
fun searchPlaces(search: String, location: Location): String {
|
||||
val box = calculateSquareRadius(location.latitude, location.longitude, 100.0)
|
||||
val box = calculateSquareRadius(location.latitude, location.longitude, 800.0)
|
||||
val viewbox = "&bounded=1&viewbox=${box}"
|
||||
return fetchUrl(
|
||||
"${nominatimUrl}search?q=$search&format=jsonv2&addressdetails=true$viewbox",
|
||||
|
||||
@@ -53,6 +53,8 @@ class DataStoreManager(private val context: Context) {
|
||||
|
||||
val TRAFFIC = booleanPreferencesKey("Traffic")
|
||||
|
||||
val TRIP_SUGGESTION = booleanPreferencesKey("TripSuggestion")
|
||||
|
||||
}
|
||||
|
||||
// Read values
|
||||
@@ -129,6 +131,11 @@ class DataStoreManager(private val context: Context) {
|
||||
preferences[PreferencesKeys.TRAFFIC] == true
|
||||
}
|
||||
|
||||
val tripSuggestionFlow: Flow<Boolean> =
|
||||
context.dataStore.data.map { preferences ->
|
||||
preferences[PreferencesKeys.TRIP_SUGGESTION] == true
|
||||
}
|
||||
|
||||
// Save values
|
||||
suspend fun setShow3D(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
@@ -207,4 +214,10 @@ class DataStoreManager(private val context: Context) {
|
||||
preferences[PreferencesKeys.TRAFFIC] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun setTripSuggestion(enabled: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[PreferencesKeys.TRIP_SUGGESTION] = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,4 +12,5 @@ data class Step(
|
||||
val distance: Double = 0.0,
|
||||
val street : String = "",
|
||||
val intersection: List<Intersection> = mutableListOf(),
|
||||
val countryCode : String = ""
|
||||
)
|
||||
|
||||
@@ -19,9 +19,9 @@ const val tomtomTrafficUrl = "https://api.tomtom.com/traffic/services/5/incident
|
||||
private const val tomtomFields =
|
||||
"{incidents{type,geometry{type,coordinates},properties{iconCategory,events{description}}}}"
|
||||
|
||||
const val useAsset = false
|
||||
const val useLocal = false
|
||||
|
||||
const val useAssetTraffic = false
|
||||
const val useLocalTraffic = false
|
||||
|
||||
|
||||
class TomTomRepository : NavigationRepository() {
|
||||
@@ -32,12 +32,11 @@ class TomTomRepository : NavigationRepository() {
|
||||
carOrientation: Float,
|
||||
searchFilter: SearchFilter
|
||||
): String {
|
||||
if (useAsset) {
|
||||
val resourceId: Int = context.resources
|
||||
.getIdentifier("tomtom_routing", "raw", context.packageName)
|
||||
val routeJson = context.resources.openRawResource(resourceId)
|
||||
val routeJsonString = routeJson.bufferedReader().use { it.readText() }
|
||||
return routeJsonString
|
||||
if (useLocal) {
|
||||
return fetchUrl(
|
||||
"https://kouros-online.de/tomtom_routing.json",
|
||||
false
|
||||
)
|
||||
}
|
||||
var filter = ""
|
||||
if (searchFilter.avoidMotorway) {
|
||||
@@ -76,11 +75,11 @@ class TomTomRepository : NavigationRepository() {
|
||||
return ""
|
||||
}
|
||||
val bbox = calculateSquareRadius(location.latitude, location.longitude, 15.0)
|
||||
return if (useAssetTraffic) {
|
||||
val resourceId: Int = context.resources
|
||||
.getIdentifier("tomtom_traffic", "raw", context.packageName)
|
||||
val trafficJson = context.resources.openRawResource(resourceId)
|
||||
trafficJson.bufferedReader().use { it.readText() }
|
||||
return if (useLocalTraffic) {
|
||||
fetchUrl(
|
||||
"https://kouros-online.de/tomtom_traffic.json",
|
||||
false
|
||||
)
|
||||
} else {
|
||||
val trafficResult = fetchUrl(
|
||||
"$tomtomTrafficUrl?key=$tomtomApiKey&bbox=$bbox&fields=$tomtomFields&language=en-GB&timeValidityFilter=present",
|
||||
|
||||
@@ -62,7 +62,9 @@ class TomTomRoute {
|
||||
lastPointIndex = instruction.pointIndex
|
||||
val intersections = mutableListOf<Intersection>()
|
||||
route.sections?.forEach { section ->
|
||||
if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex && section.endPointIndex >= lastPointIndex) {
|
||||
if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex
|
||||
&& section.endPointIndex >= lastPointIndex
|
||||
) {
|
||||
val lanes = mutableListOf<Lane>()
|
||||
var startIndex = 0
|
||||
var lastLane: Lane? = null
|
||||
@@ -87,25 +89,25 @@ class TomTomRoute {
|
||||
lastLane = lane
|
||||
}
|
||||
intersections.add(Intersection(waypoints[startIndex], lanes))
|
||||
|
||||
}
|
||||
stepDistance =
|
||||
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
||||
stepDuration =
|
||||
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
street = street,
|
||||
distance = stepDistance,
|
||||
duration = stepDuration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections
|
||||
)
|
||||
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
|
||||
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
}
|
||||
stepDistance =
|
||||
route.guidance.instructions[index].routeOffsetInMeters - stepDistance
|
||||
stepDuration =
|
||||
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
|
||||
val step = Step(
|
||||
index = stepIndex,
|
||||
street = street,
|
||||
distance = stepDistance,
|
||||
duration = stepDuration,
|
||||
maneuver = maneuver,
|
||||
intersection = intersections,
|
||||
countryCode = lastInstruction.countryCode
|
||||
)
|
||||
stepDistance = route.guidance.instructions[index].routeOffsetInMeters.toDouble()
|
||||
stepDuration = route.guidance.instructions[index].travelTimeInSeconds.toDouble()
|
||||
steps.add(step)
|
||||
stepIndex += 1
|
||||
}
|
||||
legs.add(Leg(steps))
|
||||
val routeGeoJson = createLineStringCollection(waypoints)
|
||||
|
||||
@@ -16,7 +16,7 @@ import com.kouros.navigation.data.StepData
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
class IconMapper() {
|
||||
class IconMapper {
|
||||
|
||||
fun maneuverIcon(routeManeuverType: Int): Int {
|
||||
var currentTurnIcon = R.drawable.ic_turn_name_change
|
||||
|
||||
@@ -5,6 +5,8 @@ import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.runtime.toMutableStateList
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -60,11 +62,6 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing list of favorite saved places */
|
||||
val favorites: MutableLiveData<List<Place>> by lazy {
|
||||
MutableLiveData()
|
||||
}
|
||||
|
||||
/** LiveData containing search results from Nominatim geocoding */
|
||||
val searchPlaces: MutableLiveData<List<SearchResult>> by lazy {
|
||||
MutableLiveData()
|
||||
@@ -147,7 +144,8 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
var id: Long = 0
|
||||
if (rp.isNotEmpty()) {
|
||||
for (place in places.places) {
|
||||
if (place.category.equals(Constants.RECENT)) {
|
||||
if (place.category.equals(Constants.RECENT)
|
||||
|| place.category.equals(Constants.FAVORITES)) {
|
||||
val plLocation = location(place.longitude, place.latitude)
|
||||
if (place.latitude != 0.0) {
|
||||
val distance =
|
||||
@@ -172,43 +170,6 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads favorite places from Preferences and calculates distances.
|
||||
* Posts the sorted list to favorites LiveData.
|
||||
*/
|
||||
fun loadFavorites(context: Context, location: Location, carOrientation: Float) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val settingsRepository = getSettingsRepository(context)
|
||||
val rp = settingsRepository.recentPlacesFlow.first()
|
||||
val gson = GsonBuilder().serializeNulls().create()
|
||||
val recentPlaces = gson.fromJson(rp, Places::class.java)
|
||||
val pl = mutableListOf<Place>()
|
||||
if (rp.isNotEmpty()) {
|
||||
for (place in recentPlaces.places) {
|
||||
if (place.category.equals(Constants.FAVORITES)) {
|
||||
val plLocation = location(place.longitude, place.latitude)
|
||||
if (place.latitude != 0.0) {
|
||||
val distance =
|
||||
repository.getRouteDistance(
|
||||
location,
|
||||
plLocation,
|
||||
carOrientation,
|
||||
context
|
||||
)
|
||||
place.distance = distance.toFloat()
|
||||
}
|
||||
pl.add(place)
|
||||
}
|
||||
}
|
||||
}
|
||||
favorites.postValue(pl)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates a route between current location and destination.
|
||||
* Posts the route JSON to route LiveData.
|
||||
@@ -216,7 +177,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
fun loadRoute(
|
||||
context: Context,
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
destination: Location,
|
||||
carOrientation: Float
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
@@ -225,7 +186,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
repository.getRoute(
|
||||
context,
|
||||
currentLocation,
|
||||
location,
|
||||
destination,
|
||||
carOrientation,
|
||||
getSearchFilter(context)
|
||||
)
|
||||
@@ -296,7 +257,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
currentLocation: Location,
|
||||
location: Location,
|
||||
carOrientation: Float
|
||||
) {
|
||||
): String? {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
previewRoute.postValue(
|
||||
@@ -312,8 +273,10 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return previewRoute.value
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads device contacts with addresses and converts to Place objects.
|
||||
* Posts results to contactAddress LiveData.
|
||||
|
||||
@@ -13,7 +13,7 @@ import com.kouros.navigation.data.route.Leg
|
||||
import com.kouros.navigation.data.route.Routes
|
||||
import com.kouros.navigation.data.route.Step
|
||||
import com.kouros.navigation.utils.location
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
|
||||
open class RouteModel {
|
||||
|
||||
@@ -33,6 +33,9 @@ open class RouteModel {
|
||||
val currentStep: Step
|
||||
get() = navState.route.nextStep(0)
|
||||
|
||||
val steps: List<Step>
|
||||
get() = curLeg.steps
|
||||
|
||||
fun startNavigation(routeString: String) {
|
||||
navState = navState.copy(
|
||||
route = Route.Builder()
|
||||
@@ -145,7 +148,20 @@ open class RouteModel {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for navigating
|
||||
*/
|
||||
fun isNavigating(): Boolean {
|
||||
return navState.navigating
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for arrival
|
||||
*/
|
||||
fun isArrival(): Boolean {
|
||||
return navState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_LEFT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_RIGHT
|
||||
|| navState.maneuverType == Maneuver.TYPE_DESTINATION_STRAIGHT
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,12 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
||||
false
|
||||
)
|
||||
|
||||
val tripSuggestion = repository.tripSuggestionFlow.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(5_000),
|
||||
false
|
||||
)
|
||||
|
||||
fun onShow3DChanged(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setShow3D(enabled) }
|
||||
}
|
||||
@@ -138,4 +144,8 @@ class SettingsViewModel(private val repository: SettingsRepository) : ViewModel(
|
||||
fun onTraffic(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setTraffic(enabled) }
|
||||
}
|
||||
|
||||
fun onTripSuggestion(enabled: Boolean) {
|
||||
viewModelScope.launch { repository.setTripSuggestion(enabled) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ class SettingsRepository(
|
||||
val trafficFlow: Flow<Boolean> =
|
||||
dataStoreManager.trafficFlow
|
||||
|
||||
val tripSuggestionFlow: Flow<Boolean> =
|
||||
dataStoreManager.tripSuggestionFlow
|
||||
|
||||
suspend fun setShow3D(enabled: Boolean) {
|
||||
dataStoreManager.setShow3D(enabled)
|
||||
}
|
||||
@@ -95,4 +98,8 @@ class SettingsRepository(
|
||||
suspend fun setTraffic(enabled: Boolean) {
|
||||
dataStoreManager.setTraffic(enabled)
|
||||
}
|
||||
|
||||
suspend fun setTripSuggestion(enabled: Boolean) {
|
||||
dataStoreManager.setTripSuggestion(enabled)
|
||||
}
|
||||
}
|
||||
|
||||
11
common/data/src/main/res/drawable/chevron_right_24px.xml
Normal file
11
common/data/src/main/res/drawable/chevron_right_24px.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M504,480L320,296L376,240L616,480L376,720L320,664L504,480Z"/>
|
||||
</vector>
|
||||
10
common/data/src/main/res/drawable/traffic_jam_48px.xml
Normal file
10
common/data/src/main/res/drawable/traffic_jam_48px.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M70,880Q58,880 49,871.5Q40,863 40,850L40,517L128,308Q133,295 144,287.5Q155,280 169,280L552,280Q566,280 577,287.5Q588,295 593,308L680,517L680,850Q680,863 671.5,871.5Q663,880 650,880L609,880Q597,880 588,871.5Q579,863 579,850L579,800L141,800L141,850Q141,863 132.5,871.5Q124,880 111,880L70,880ZM130,458L591,458L542,340L179,340L130,458ZM246.5,664Q261,649 261,629Q261,608 246.5,593Q232,578 211,578Q190,578 175,593Q160,608 160,629Q160,649 175,664Q190,679 211,679Q232,679 246.5,664ZM545.5,664Q560,649 560,629Q560,608 545.5,593Q531,578 510,578Q489,578 474,593Q459,608 459,629Q459,649 474,664Q489,679 510,679Q531,679 545.5,664ZM740,757L740,413L659,220L237,220L251,188Q256,175 267,167.5Q278,160 292,160L669,160Q683,160 694.5,167.5Q706,175 711,188L800,401L800,727Q800,740 791.5,748.5Q783,757 770,757L740,757ZM860,634L860,290L780,100L360,100L374,68Q379,55 390,47.5Q401,40 415,40L790,40Q804,40 815.5,47.5Q827,55 832,68L920,278L920,604Q920,617 911.5,625.5Q903,634 890,634L860,634Z"/>
|
||||
</vector>
|
||||
@@ -65,4 +65,5 @@
|
||||
<string name="no_categories">Keine Kategorien</string>
|
||||
<string name="general">Allgemein</string>
|
||||
<string name="traffic">Verkehr anzeigen</string>
|
||||
<string name="trip_suggestion">Fahrten-Vorschläge</string>
|
||||
</resources>
|
||||
|
||||
@@ -49,4 +49,5 @@
|
||||
<string name="no_categories">Δεν υπάρχουν κατηγορίες</string>
|
||||
<string name="general">Γενικά</string>
|
||||
<string name="traffic">Εμφάνιση κίνησης</string>
|
||||
<string name="trip_suggestion">Προτάσεις διαδρομής</string>
|
||||
</resources>
|
||||
|
||||
@@ -49,4 +49,5 @@
|
||||
<string name="no_categories">Brak kategorii do wyświetlenia</string>
|
||||
<string name="general">Ogólne</string>
|
||||
<string name="traffic">Pokaż natężenie ruchu</string>
|
||||
<string name="trip_suggestion">Sugestie dotyczące podróży</string>
|
||||
</resources>
|
||||
|
||||
@@ -52,4 +52,5 @@
|
||||
<string name="no_categories">No categories to show</string>
|
||||
<string name="general">General</string>
|
||||
<string name="traffic">Show traffic</string>
|
||||
<string name="trip_suggestion">Trip suggestions</string>
|
||||
</resources>
|
||||
@@ -86,21 +86,6 @@ class RouteModelTest {
|
||||
assert(routeModel.navState.currentLocation.longitude == 11.57936)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `currentStep returns StepData `() {
|
||||
val stepData = routeModel.currentStep()
|
||||
assert(stepData.leftStepDistance == 0.0)
|
||||
assert(stepData.instruction == "Milbertshofener Straße")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nextStep returns StepData `() {
|
||||
routeModel.currentStep()
|
||||
val stepData = routeModel.nextStep()
|
||||
assert(stepData.leftStepDistance == 0.0)
|
||||
assert(stepData.instruction == "Bad-Soden-Straße")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `stopNavigation updates route and sets navigating to false `() {
|
||||
routeModel.stopNavigation()
|
||||
|
||||
Reference in New Issue
Block a user