Refactoring
This commit is contained in:
@@ -14,8 +14,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 7
|
versionCode = 8
|
||||||
versionName = "0.1.3.7"
|
versionName = "0.1.3.8"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,28 +15,36 @@ import androidx.annotation.RequiresPermission
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.material3.BottomSheetScaffold
|
import androidx.compose.material3.BottomSheetScaffold
|
||||||
|
import androidx.compose.material3.BottomSheetScaffoldState
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.SnackbarHost
|
import androidx.compose.material3.SnackbarHost
|
||||||
import androidx.compose.material3.SnackbarHostState
|
import androidx.compose.material3.SnackbarHostState
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableDoubleStateOf
|
import androidx.compose.runtime.mutableDoubleStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.google.android.gms.location.FusedLocationProviderClient
|
import com.google.android.gms.location.FusedLocationProviderClient
|
||||||
import com.google.android.gms.location.LocationServices
|
import com.google.android.gms.location.LocationServices
|
||||||
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants.homeLocation
|
import com.kouros.navigation.data.Constants.homeLocation
|
||||||
import com.kouros.navigation.data.NavigationRepository
|
import com.kouros.navigation.data.NavigationRepository
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
@@ -74,6 +82,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
MutableLiveData<StepData>()
|
MutableLiveData<StepData>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nextStepData: MutableLiveData<StepData> by lazy {
|
||||||
|
MutableLiveData<StepData>()
|
||||||
|
}
|
||||||
|
|
||||||
var lastLocation = location(0.0, 0.0)
|
var lastLocation = location(0.0, 0.0)
|
||||||
|
|
||||||
val observer = Observer<String> { newRoute ->
|
val observer = Observer<String> { newRoute ->
|
||||||
@@ -147,8 +159,9 @@ class MainActivity : ComponentActivity() {
|
|||||||
fun Content() {
|
fun Content() {
|
||||||
val scaffoldState = rememberBottomSheetScaffoldState()
|
val scaffoldState = rememberBottomSheetScaffoldState()
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val sheetPeekHeightState = remember { mutableStateOf(256.dp) }
|
||||||
|
|
||||||
val locationProvider = rememberDefaultLocationProvider(
|
val locationProvider = rememberDefaultLocationProvider(
|
||||||
updateInterval = 0.5.seconds,
|
updateInterval = 0.5.seconds,
|
||||||
desiredAccuracy = DesiredAccuracy.Highest
|
desiredAccuracy = DesiredAccuracy.Highest
|
||||||
@@ -161,24 +174,27 @@ class MainActivity : ComponentActivity() {
|
|||||||
latitude = locationState.value!!.position.latitude
|
latitude = locationState.value!!.position.latitude
|
||||||
}
|
}
|
||||||
val step: StepData? by stepData.observeAsState()
|
val step: StepData? by stepData.observeAsState()
|
||||||
|
val nextStep: StepData? by nextStepData.observeAsState()
|
||||||
|
|
||||||
fun openSheet() {
|
fun openSheet() {
|
||||||
scope.launch { scaffoldState.bottomSheetState.expand() }
|
scope.launch { scaffoldState.bottomSheetState.expand() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun closeSheet() {
|
fun closeSheet() {
|
||||||
scope.launch { scaffoldState.bottomSheetState.partialExpand() }
|
scope.launch {
|
||||||
|
scaffoldState.bottomSheetState.partialExpand()
|
||||||
|
sheetPeekHeightState.value = 128.dp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
NavigationTheme() {
|
||||||
NavigationTheme {
|
|
||||||
BottomSheetScaffold(
|
BottomSheetScaffold(
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
SnackbarHost(hostState = snackbarHostState)
|
SnackbarHost(hostState = snackbarHostState)
|
||||||
},
|
},
|
||||||
scaffoldState = scaffoldState,
|
scaffoldState = scaffoldState,
|
||||||
sheetPeekHeight = 128.dp,
|
sheetPeekHeight = sheetPeekHeightState.value,
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
SheetContent(latitude, step) { closeSheet() }
|
SheetContent(latitude, step, nextStep) { closeSheet() }
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Box(
|
Box(
|
||||||
@@ -187,19 +203,31 @@ class MainActivity : ComponentActivity() {
|
|||||||
.padding(innerPadding),
|
.padding(innerPadding),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
MapView(applicationContext,userLocationState, step, cameraPosition, routeData, tilt)
|
MapView(
|
||||||
|
applicationContext,
|
||||||
|
userLocationState,
|
||||||
|
step,
|
||||||
|
cameraPosition,
|
||||||
|
routeData,
|
||||||
|
tilt
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SheetContent(locationState: Double, step: StepData?, closeSheet: () -> Unit) {
|
fun SheetContent(
|
||||||
|
locationState: Double,
|
||||||
|
step: StepData?,
|
||||||
|
nextStep: StepData?,
|
||||||
|
closeSheet: () -> Unit
|
||||||
|
) {
|
||||||
if (!routeModel.isNavigating()) {
|
if (!routeModel.isNavigating()) {
|
||||||
SearchSheet(applicationContext, viewModel, lastLocation) { closeSheet() }
|
SearchSheet(applicationContext, viewModel, lastLocation) { closeSheet() }
|
||||||
} else {
|
} else {
|
||||||
NavigationSheet(
|
NavigationSheet(
|
||||||
routeModel, step!!,
|
routeModel, step!!, nextStep!!,
|
||||||
{ stopNavigation { closeSheet() } },
|
{ stopNavigation { closeSheet() } },
|
||||||
{ simulateNavigation() }
|
{ simulateNavigation() }
|
||||||
)
|
)
|
||||||
@@ -213,11 +241,16 @@ class MainActivity : ComponentActivity() {
|
|||||||
&& lastLocation.latitude != location.position.latitude
|
&& lastLocation.latitude != location.position.latitude
|
||||||
&& lastLocation.longitude != location.position.longitude
|
&& lastLocation.longitude != location.position.longitude
|
||||||
) {
|
) {
|
||||||
if (routeModel.isNavigating()) {
|
|
||||||
routeModel.updateLocation(lastLocation)
|
|
||||||
stepData.value = routeModel.currentStep()
|
|
||||||
}
|
|
||||||
val currentLocation = location(location.position.longitude, location.position.latitude)
|
val currentLocation = location(location.position.longitude, location.position.latitude)
|
||||||
|
with(routeModel) {
|
||||||
|
if (isNavigating()) {
|
||||||
|
updateLocation(currentLocation)
|
||||||
|
stepData.value = currentStep()
|
||||||
|
if (route.currentManeuverIndex + 1 <= route.maneuvers.size) {
|
||||||
|
nextStepData.value = nextStep()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
val bearing = bearing(lastLocation, currentLocation, cameraPosition.value!!.bearing)
|
||||||
val zoom = calculateZoom(location.speed)
|
val zoom = calculateZoom(location.speed)
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
@@ -252,7 +285,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
val appOpsManager =
|
val appOpsManager =
|
||||||
getSystemService(APP_OPS_SERVICE) as AppOpsManager
|
getSystemService(APP_OPS_SERVICE) as AppOpsManager
|
||||||
val mode =
|
val mode =
|
||||||
appOpsManager.unsafeCheckOp(
|
appOpsManager.checkOp(
|
||||||
AppOpsManager.OPSTR_MOCK_LOCATION,
|
AppOpsManager.OPSTR_MOCK_LOCATION,
|
||||||
Process.myUid(),
|
Process.myUid(),
|
||||||
packageName
|
packageName
|
||||||
@@ -275,7 +308,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
for ((_, loc) in routeModel.route.waypoints.withIndex()) {
|
for ((_, loc) in routeModel.route.waypoints.withIndex()) {
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
mock.setMockLocation(loc[1], loc[0])
|
mock.setMockLocation(loc[1], loc[0])
|
||||||
delay(1000L) //
|
delay(500L) //
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,43 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
|
import android.R.attr.x
|
||||||
|
import android.R.attr.y
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.absoluteOffset
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.DpOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.window.layout.WindowMetricsCalculator
|
import androidx.window.layout.WindowMetricsCalculator
|
||||||
import com.kouros.navigation.car.map.BuildingLayer
|
import com.kouros.navigation.car.map.DarkMode
|
||||||
|
import com.kouros.navigation.car.map.MapLibre
|
||||||
import com.kouros.navigation.car.map.NavigationImage
|
import com.kouros.navigation.car.map.NavigationImage
|
||||||
import com.kouros.navigation.car.map.RouteLayer
|
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.utils.NavigationUtils
|
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
import org.maplibre.compose.location.LocationTrackingEffect
|
import org.maplibre.compose.location.LocationTrackingEffect
|
||||||
import org.maplibre.compose.location.UserLocationState
|
import org.maplibre.compose.location.UserLocationState
|
||||||
import org.maplibre.compose.map.MapOptions
|
|
||||||
import org.maplibre.compose.map.MaplibreMap
|
|
||||||
import org.maplibre.compose.map.OrnamentOptions
|
|
||||||
import org.maplibre.compose.sources.getBaseSource
|
|
||||||
import org.maplibre.compose.style.BaseStyle
|
import org.maplibre.compose.style.BaseStyle
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
@@ -57,32 +70,14 @@ fun MapView(
|
|||||||
zoom = 15.0,
|
zoom = 15.0,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
val baseStyle = remember {
|
||||||
|
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
|
||||||
|
}
|
||||||
|
DarkMode(applicationContext, baseStyle)
|
||||||
Column {
|
Column {
|
||||||
NavigationInfo(step)
|
NavigationInfo(step)
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
MaplibreMap(
|
MapLibre(applicationContext, cameraState, baseStyle, route, "", position)
|
||||||
options = MapOptions(
|
|
||||||
ornamentOptions =
|
|
||||||
OrnamentOptions(isScaleBarEnabled = false)
|
|
||||||
),
|
|
||||||
cameraState = cameraState,
|
|
||||||
baseStyle = BaseStyle.Uri(Constants.STYLE),
|
|
||||||
) {
|
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
|
||||||
if (!NavigationUtils.getBooleanKeyValue(
|
|
||||||
context = applicationContext,
|
|
||||||
Constants.SHOW_THREED_BUILDING
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
BuildingLayer(tiles)
|
|
||||||
}
|
|
||||||
RouteLayer(route, "", position!!.zoom)
|
|
||||||
}
|
|
||||||
if (userLocationState.location != null) {
|
|
||||||
///PuckState(cameraState, userLocationState)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LocationTrackingEffect(
|
LocationTrackingEffect(
|
||||||
locationState = userLocationState,
|
locationState = userLocationState,
|
||||||
) {
|
) {
|
||||||
@@ -97,7 +92,8 @@ fun MapView(
|
|||||||
duration = 1.seconds
|
duration = 1.seconds
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
NavigationImage(paddingValues, width, height / 6, "")
|
NavigationImage(paddingValues, width, height / 6)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ import com.kouros.navigation.utils.round
|
|||||||
fun NavigationSheet(
|
fun NavigationSheet(
|
||||||
routeModel: RouteModel,
|
routeModel: RouteModel,
|
||||||
step: StepData,
|
step: StepData,
|
||||||
|
nextStep: StepData,
|
||||||
stopNavigation: () -> Unit,
|
stopNavigation: () -> Unit,
|
||||||
simulateNavigation: () -> Unit,
|
simulateNavigation: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val distance = step.leftDistance.round(1)
|
val distance = step.leftDistance.round(1)
|
||||||
Column {
|
Column {
|
||||||
FlowRow(horizontalArrangement= Arrangement.SpaceEvenly) {
|
FlowRow(horizontalArrangement = Arrangement.SpaceEvenly) {
|
||||||
Text(formatDateTime(step.arrivalTime), fontSize = 22.sp)
|
Text(formatDateTime(step.arrivalTime), fontSize = 22.sp)
|
||||||
Spacer(Modifier.size(30.dp))
|
Spacer(Modifier.size(30.dp))
|
||||||
Text("$distance km", fontSize = 22.sp)
|
Text("$distance km", fontSize = 22.sp)
|
||||||
@@ -47,7 +48,9 @@ fun NavigationSheet(
|
|||||||
modifier = Modifier.size(24.dp, 24.dp),
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(Modifier.size(30.dp))
|
}
|
||||||
|
Spacer(Modifier.size(30.dp))
|
||||||
|
if (!routeModel.isNavigating()) {
|
||||||
Button(onClick = {
|
Button(onClick = {
|
||||||
simulateNavigation()
|
simulateNavigation()
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ fun PermissionScreen(
|
|||||||
errorText = if (rejectedPermissions.none { it in requiredPermissions }) {
|
errorText = if (rejectedPermissions.none { it in requiredPermissions }) {
|
||||||
""
|
""
|
||||||
} else {
|
} else {
|
||||||
"${rejectedPermissions.joinToString()} required for the sample"
|
"${rejectedPermissions.joinToString()} required for the app"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val allRequiredPermissionsGranted =
|
val allRequiredPermissionsGranted =
|
||||||
@@ -128,7 +128,7 @@ private fun PermissionScreen(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Sample requires permission/s:",
|
text = "Navigation requires permission/s:",
|
||||||
style = MaterialTheme.typography.titleLarge,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier = Modifier.padding(16.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@@ -56,23 +57,25 @@ fun SearchSheet(
|
|||||||
if (search.value != null) {
|
if (search.value != null) {
|
||||||
searchResults.addAll(search.value!!)
|
searchResults.addAll(search.value!!)
|
||||||
}
|
}
|
||||||
|
Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
|
||||||
if (searchResults.isNotEmpty()) {
|
if (searchResults.isNotEmpty()) {
|
||||||
val textFieldState = rememberTextFieldState()
|
val textFieldState = rememberTextFieldState()
|
||||||
val items = listOf(searchResults)
|
val items = listOf(searchResults)
|
||||||
if (items.isNotEmpty()) {
|
if (items.isNotEmpty()) {
|
||||||
SearchBar(
|
SearchBar(
|
||||||
textFieldState = textFieldState,
|
textFieldState = textFieldState,
|
||||||
searchPlaces = recentPlaces.value!!,
|
searchPlaces = recentPlaces.value!!,
|
||||||
searchResults = searchResults,
|
searchResults = searchResults,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
context = applicationContext,
|
context = applicationContext,
|
||||||
location = location,
|
location = location,
|
||||||
closeSheet = {closeSheet()}
|
closeSheet = { closeSheet() }
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (recentPlaces.value != null) {
|
if (recentPlaces.value != null) {
|
||||||
|
println("Recent Places ${recentPlaces.value}")
|
||||||
val textFieldState = rememberTextFieldState()
|
val textFieldState = rememberTextFieldState()
|
||||||
val items = listOf(recentPlaces)
|
val items = listOf(recentPlaces)
|
||||||
if (items.isNotEmpty()) {
|
if (items.isNotEmpty()) {
|
||||||
@@ -83,10 +86,44 @@ fun SearchSheet(
|
|||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
context = applicationContext,
|
context = applicationContext,
|
||||||
location = location,
|
location = location,
|
||||||
closeSheet = {closeSheet()}
|
closeSheet = { closeSheet() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Home(
|
||||||
|
applicationContext: Context,
|
||||||
|
viewModel: ViewModel,
|
||||||
|
location: Location,
|
||||||
|
closeSheet: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Button(onClick = {
|
||||||
|
val places = viewModel.loadRecentPlace()
|
||||||
|
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
|
||||||
|
viewModel.loadRoute(applicationContext, location, toLocation)
|
||||||
|
closeSheet()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = com.google.android.gms.base.R.drawable.common_full_open_on_phone),
|
||||||
|
"Home",
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
|
Text("Home")
|
||||||
|
}
|
||||||
|
Button(onClick = {
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_favorite_white_24dp),
|
||||||
|
"Work",
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
|
Text("Arbeit")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@@ -176,6 +213,16 @@ private fun SearchPlaces(
|
|||||||
headlineContent = { Text("${place.address.road} ${place.address.postcode}") },
|
headlineContent = { Text("${place.address.road} ${place.address.postcode}") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
|
val pl = Place(
|
||||||
|
name = place.name,
|
||||||
|
longitude = place.lon.toDouble(),
|
||||||
|
latitude = place.lat.toDouble(),
|
||||||
|
postalCode = place.address.postcode,
|
||||||
|
city = place.address.city,
|
||||||
|
street = place.address.road
|
||||||
|
)
|
||||||
|
viewModel.saveRecent(pl)
|
||||||
|
println("Save $pl")
|
||||||
val toLocation =
|
val toLocation =
|
||||||
location(place.lon.toDouble(), place.lat.toDouble())
|
location(place.lon.toDouble(), place.lat.toDouble())
|
||||||
viewModel.loadRoute(context, location, toLocation)
|
viewModel.loadRoute(context, location, toLocation)
|
||||||
@@ -212,7 +259,7 @@ private fun RecentPlaces(
|
|||||||
modifier = Modifier.size(24.dp, 24.dp),
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
)
|
)
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text("${place.street!!} ${place.postalCode}") },
|
headlineContent = { Text("${place.name} ${place.postalCode}") },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clickable {
|
.clickable {
|
||||||
val toLocation = location(place.longitude, place.latitude)
|
val toLocation = location(place.longitude, place.latitude)
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ class NavigationSession : Session(), NavigationScreen.Listener {
|
|||||||
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
val snapedLocation = snapLocation(location, routeModel.route.maneuverLocations())
|
||||||
val distance = location.distanceTo(snapedLocation)
|
val distance = location.distanceTo(snapedLocation)
|
||||||
if (distance > MAXIMAL_ROUTE_DEVIATION) {
|
if (distance > MAXIMAL_ROUTE_DEVIATION) {
|
||||||
navigationScreen.calculateNewRoute(routeModel.destination)
|
navigationScreen.calculateNewRoute(routeModel.routeState.destination)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
navigationScreen.updateTrip(location)
|
navigationScreen.updateTrip(location)
|
||||||
|
|||||||
@@ -5,14 +5,14 @@ import android.graphics.Rect
|
|||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.VirtualDisplay
|
import android.hardware.display.VirtualDisplay
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.os.CountDownTimer
|
||||||
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.car.app.AppManager
|
import androidx.car.app.AppManager
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.SurfaceCallback
|
import androidx.car.app.SurfaceCallback
|
||||||
import androidx.car.app.SurfaceContainer
|
import androidx.car.app.SurfaceContainer
|
||||||
import androidx.car.app.connection.CarConnection
|
import androidx.car.app.connection.CarConnection
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -27,31 +27,24 @@ import androidx.lifecycle.LifecycleOwner
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||||
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
|
||||||
import com.kouros.navigation.car.map.BuildingLayer
|
import com.kouros.navigation.car.map.DarkMode
|
||||||
import com.kouros.navigation.car.map.DrawImage
|
import com.kouros.navigation.car.map.DrawImage
|
||||||
import com.kouros.navigation.car.map.RouteLayer
|
import com.kouros.navigation.car.map.MapLibre
|
||||||
import com.kouros.navigation.car.map.cameraState
|
import com.kouros.navigation.car.map.cameraState
|
||||||
import com.kouros.navigation.car.map.getPaddingValues
|
import com.kouros.navigation.car.map.getPaddingValues
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants
|
||||||
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
|
||||||
import com.kouros.navigation.data.ObjectBox
|
import com.kouros.navigation.data.ObjectBox
|
||||||
import com.kouros.navigation.model.RouteModel
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
|
import com.kouros.navigation.utils.duration
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
import com.kouros.navigation.utils.previewZoom
|
import com.kouros.navigation.utils.previewZoom
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.map.MapOptions
|
|
||||||
import org.maplibre.compose.map.MaplibreMap
|
|
||||||
import org.maplibre.compose.map.OrnamentOptions
|
|
||||||
import org.maplibre.compose.sources.getBaseSource
|
|
||||||
import org.maplibre.compose.style.BaseStyle
|
import org.maplibre.compose.style.BaseStyle
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
|
||||||
|
|
||||||
|
|
||||||
class SurfaceRenderer(
|
class SurfaceRenderer(
|
||||||
@@ -59,14 +52,14 @@ class SurfaceRenderer(
|
|||||||
private var routeModel: RouteCarModel
|
private var routeModel: RouteCarModel
|
||||||
) : DefaultLifecycleObserver {
|
) : DefaultLifecycleObserver {
|
||||||
|
|
||||||
var lastLocation = Location(LocationManager.GPS_PROVIDER)
|
var lastLocation = location(0.0, 0.0)
|
||||||
val cameraPosition = MutableLiveData(
|
private val cameraPosition = MutableLiveData(
|
||||||
CameraPosition(
|
CameraPosition(
|
||||||
zoom = 15.0,
|
zoom = 15.0,
|
||||||
target = Position(latitude = 48.1857475, longitude = 11.5793627)
|
target = Position(latitude = 48.1857475, longitude = 11.5793627)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
var visibleArea = MutableLiveData(
|
private var visibleArea = MutableLiveData(
|
||||||
Rect(0, 0, 0, 0)
|
Rect(0, 0, 0, 0)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,6 +74,7 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
val previewRouteData = MutableLiveData("")
|
val previewRouteData = MutableLiveData("")
|
||||||
|
|
||||||
|
val speed = MutableLiveData(0F)
|
||||||
lateinit var centerLocation: Location
|
lateinit var centerLocation: Location
|
||||||
var preview = false
|
var preview = false
|
||||||
|
|
||||||
@@ -90,6 +84,8 @@ class SurfaceRenderer(
|
|||||||
var tilt = 55.0
|
var tilt = 55.0
|
||||||
|
|
||||||
var previewDistance = 0.0
|
var previewDistance = 0.0
|
||||||
|
|
||||||
|
var countDownTimerActive = false
|
||||||
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
|
val mSurfaceCallback: SurfaceCallback = object : SurfaceCallback {
|
||||||
|
|
||||||
lateinit var lifecycleOwner: CustomLifecycleOwner
|
lateinit var lifecycleOwner: CustomLifecycleOwner
|
||||||
@@ -171,6 +167,7 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
lifecycle.addObserver(this)
|
lifecycle.addObserver(this)
|
||||||
|
speed.value = 0F
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onConnectionStateUpdated(connectionState: Int) {
|
fun onConnectionStateUpdated(connectionState: Int) {
|
||||||
@@ -192,26 +189,8 @@ class SurfaceRenderer(
|
|||||||
val baseStyle = remember {
|
val baseStyle = remember {
|
||||||
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
|
mutableStateOf(BaseStyle.Uri(Constants.STYLE))
|
||||||
}
|
}
|
||||||
baseStyle.value =
|
DarkMode(carContext, baseStyle)
|
||||||
(if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
|
MapLibre(carContext, cameraState, baseStyle, route, previewRoute, position)
|
||||||
Constants.STYLE
|
|
||||||
))
|
|
||||||
|
|
||||||
MaplibreMap(
|
|
||||||
options = MapOptions(
|
|
||||||
ornamentOptions =
|
|
||||||
OrnamentOptions(isScaleBarEnabled = false)),
|
|
||||||
cameraState = cameraState,
|
|
||||||
baseStyle = baseStyle.value
|
|
||||||
) {
|
|
||||||
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
|
||||||
if (!getBooleanKeyValue(context = carContext, SHOW_THREED_BUILDING)) {
|
|
||||||
BuildingLayer(tiles)
|
|
||||||
}
|
|
||||||
RouteLayer(route, previewRoute, position!!.zoom)
|
|
||||||
}
|
|
||||||
//Puck(cameraState, lastLocation)
|
|
||||||
}
|
|
||||||
ShowPosition(cameraState, position, paddingValues)
|
ShowPosition(cameraState, position, paddingValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,16 +200,17 @@ class SurfaceRenderer(
|
|||||||
position: CameraPosition?,
|
position: CameraPosition?,
|
||||||
paddingValues: PaddingValues
|
paddingValues: PaddingValues
|
||||||
) {
|
) {
|
||||||
val cameraDuration = duration(position)
|
val cameraDuration = duration(preview, position!!.bearing, lastBearing)
|
||||||
var bearing = position!!.bearing
|
var bearing = position.bearing
|
||||||
var zoom = position.zoom
|
var zoom = position.zoom
|
||||||
var target = position.target
|
var target = position.target
|
||||||
var localTilt = tilt
|
var localTilt = tilt
|
||||||
|
val currentSpeed: Float? by speed.observeAsState()
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
if (routeModel.isNavigating()) {
|
if (routeModel.isNavigating()) {
|
||||||
DrawImage(paddingValues, lastLocation, width, height, "")
|
DrawImage(paddingValues, currentSpeed, width, height)
|
||||||
} else {
|
} else {
|
||||||
DrawImage(paddingValues, lastLocation, width, height, "")
|
DrawImage(paddingValues, currentSpeed, width, height)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bearing = 0.0
|
bearing = 0.0
|
||||||
@@ -259,18 +239,6 @@ class SurfaceRenderer(
|
|||||||
.setSurfaceCallback(mSurfaceCallback)
|
.setSurfaceCallback(mSurfaceCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun duration(position: CameraPosition?): Duration {
|
|
||||||
if (preview) {
|
|
||||||
return 3.seconds
|
|
||||||
}
|
|
||||||
val cameraDuration = if ((lastBearing - position!!.bearing).absoluteValue > 20.0) {
|
|
||||||
2.seconds
|
|
||||||
} else {
|
|
||||||
1.seconds
|
|
||||||
}
|
|
||||||
return cameraDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handles the map zoom-in and zoom-out events. */
|
/** Handles the map zoom-in and zoom-out events. */
|
||||||
fun handleScale(zoomSign: Int) {
|
fun handleScale(zoomSign: Int) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
@@ -313,6 +281,13 @@ class SurfaceRenderer(
|
|||||||
)
|
)
|
||||||
lastBearing = cameraPosition.value!!.bearing
|
lastBearing = cameraPosition.value!!.bearing
|
||||||
lastLocation = location
|
lastLocation = location
|
||||||
|
speed.value = location.speed
|
||||||
|
if (!countDownTimerActive) {
|
||||||
|
countDownTimerActive = true
|
||||||
|
val mainThreadHandler = Handler(carContext.mainLooper)
|
||||||
|
val lastLocationTimer = lastLocation
|
||||||
|
checkUpdate(mainThreadHandler, lastLocationTimer)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
0.0,
|
0.0,
|
||||||
@@ -323,6 +298,23 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkUpdate(
|
||||||
|
mainThreadHandler: Handler,
|
||||||
|
lastLocationTimer: Location
|
||||||
|
) {
|
||||||
|
mainThreadHandler.post {
|
||||||
|
object : CountDownTimer(5000, 1000) {
|
||||||
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
|
override fun onFinish() {
|
||||||
|
countDownTimerActive = false
|
||||||
|
if (lastLocation.time - lastLocationTimer.time < 1500) {
|
||||||
|
speed.postValue(0F)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
@@ -344,7 +336,7 @@ class SurfaceRenderer(
|
|||||||
|
|
||||||
fun setPreviewRouteData(routeModel: RouteModel) {
|
fun setPreviewRouteData(routeModel: RouteModel) {
|
||||||
previewRouteData.value = routeModel.route.routeGeoJson
|
previewRouteData.value = routeModel.route.routeGeoJson
|
||||||
centerLocation = routeModel.centerLocation
|
centerLocation = routeModel.route.centerLocation
|
||||||
preview = true
|
preview = true
|
||||||
previewDistance = routeModel.route.distance
|
previewDistance = routeModel.route.distance
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.kouros.navigation.car.map
|
package com.kouros.navigation.car.map
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
|
import android.content.Context
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -12,6 +14,7 @@ import androidx.compose.material3.BadgedBox
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -25,9 +28,13 @@ import androidx.compose.ui.text.rememberTextMeasurer
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.data.Constants
|
||||||
|
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||||
import com.kouros.navigation.data.NavigationColor
|
import com.kouros.navigation.data.NavigationColor
|
||||||
import com.kouros.navigation.data.RouteColor
|
import com.kouros.navigation.data.RouteColor
|
||||||
import com.kouros.navigation.data.SpeedColor
|
import com.kouros.navigation.data.SpeedColor
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.CameraState
|
import org.maplibre.compose.camera.CameraState
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
@@ -39,9 +46,14 @@ import org.maplibre.compose.location.LocationPuck
|
|||||||
import org.maplibre.compose.location.LocationPuckColors
|
import org.maplibre.compose.location.LocationPuckColors
|
||||||
import org.maplibre.compose.location.LocationPuckSizes
|
import org.maplibre.compose.location.LocationPuckSizes
|
||||||
import org.maplibre.compose.location.UserLocationState
|
import org.maplibre.compose.location.UserLocationState
|
||||||
|
import org.maplibre.compose.map.MapOptions
|
||||||
|
import org.maplibre.compose.map.MaplibreMap
|
||||||
|
import org.maplibre.compose.map.OrnamentOptions
|
||||||
import org.maplibre.compose.sources.GeoJsonData
|
import org.maplibre.compose.sources.GeoJsonData
|
||||||
import org.maplibre.compose.sources.Source
|
import org.maplibre.compose.sources.Source
|
||||||
|
import org.maplibre.compose.sources.getBaseSource
|
||||||
import org.maplibre.compose.sources.rememberGeoJsonSource
|
import org.maplibre.compose.sources.rememberGeoJsonSource
|
||||||
|
import org.maplibre.compose.style.BaseStyle
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
|
|
||||||
|
|
||||||
@@ -65,6 +77,32 @@ fun cameraState(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MapLibre(
|
||||||
|
context: Context,
|
||||||
|
cameraState: CameraState,
|
||||||
|
baseStyle: MutableState<BaseStyle.Uri>,
|
||||||
|
route: String?,
|
||||||
|
previewRoute: String?,
|
||||||
|
position: CameraPosition?
|
||||||
|
) {
|
||||||
|
MaplibreMap(
|
||||||
|
options = MapOptions(
|
||||||
|
ornamentOptions =
|
||||||
|
OrnamentOptions(isScaleBarEnabled = false)
|
||||||
|
),
|
||||||
|
cameraState = cameraState,
|
||||||
|
baseStyle = baseStyle.value
|
||||||
|
) {
|
||||||
|
getBaseSource(id = "openmaptiles")?.let { tiles ->
|
||||||
|
if (!getBooleanKeyValue(context = context, SHOW_THREED_BUILDING)) {
|
||||||
|
BuildingLayer(tiles)
|
||||||
|
}
|
||||||
|
RouteLayer(route, previewRoute, position!!.zoom)
|
||||||
|
}
|
||||||
|
//Puck(cameraState, lastLocation)
|
||||||
|
}
|
||||||
|
}
|
||||||
@Composable
|
@Composable
|
||||||
fun RouteLayer(routeData: String?, previewRoute: String?, zoom: Double) {
|
fun RouteLayer(routeData: String?, previewRoute: String?, zoom: Double) {
|
||||||
val width = zoom - 2
|
val width = zoom - 2
|
||||||
@@ -115,26 +153,20 @@ fun BuildingLayer(tiles: Source) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawImage(padding: PaddingValues, location: Location, width: Int, height: Int, street: String) {
|
fun DrawImage(padding: PaddingValues, speed: Float?, width: Int, height: Int) {
|
||||||
NavigationImage(padding, width,height, street)
|
NavigationImage(padding, width,height)
|
||||||
Speed(width, height, location)
|
Speed(width, height, speed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun NavigationImage(padding: PaddingValues, width: Int, height: Int, street: String) {
|
fun NavigationImage(padding: PaddingValues, width: Int, height: Int) {
|
||||||
val imageSize = (height/6)
|
val imageSize = (height/6)
|
||||||
val color = remember { NavigationColor }
|
val color = remember { NavigationColor }
|
||||||
BadgedBox(
|
Box(contentAlignment = Alignment.Center, modifier = Modifier.padding(padding)) {
|
||||||
modifier = Modifier
|
|
||||||
.padding(padding),
|
|
||||||
badge = {
|
|
||||||
Badge()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Canvas(modifier =Modifier
|
Canvas(modifier =Modifier
|
||||||
.size(imageSize.dp, imageSize.dp)) {
|
.size(imageSize.dp, imageSize.dp)) {
|
||||||
scale(scaleX = 1f, scaleY = 0.7f) {
|
scale(scaleX = 1f, scaleY = 0.7f) {
|
||||||
drawCircle(Color.DarkGray.copy(alpha = 0.2f))
|
drawCircle(Color.DarkGray.copy(alpha = 0.4f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Icon(
|
Icon(
|
||||||
@@ -143,8 +175,6 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int, street: Str
|
|||||||
tint = color.copy(alpha = 1f),
|
tint = color.copy(alpha = 1f),
|
||||||
modifier = Modifier.size(imageSize.dp, imageSize.dp),
|
modifier = Modifier.size(imageSize.dp, imageSize.dp),
|
||||||
)
|
)
|
||||||
if (street.isNotEmpty())
|
|
||||||
Text(text = street)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,7 +182,7 @@ fun NavigationImage(padding: PaddingValues, width: Int, height: Int, street: Str
|
|||||||
private fun Speed(
|
private fun Speed(
|
||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
location: Location
|
speed: Float?
|
||||||
) {
|
) {
|
||||||
val radius = 32
|
val radius = 32
|
||||||
Box(
|
Box(
|
||||||
@@ -165,7 +195,7 @@ private fun Speed(
|
|||||||
) {
|
) {
|
||||||
val textMeasurerSpeed = rememberTextMeasurer()
|
val textMeasurerSpeed = rememberTextMeasurer()
|
||||||
val textMeasurerKm = rememberTextMeasurer()
|
val textMeasurerKm = rememberTextMeasurer()
|
||||||
val speed = (location.speed * 3.6).toInt().toString()
|
val speed = (speed!! * 3.6).toInt().toString()
|
||||||
val kmh = "km/h"
|
val kmh = "km/h"
|
||||||
val styleSpeed = TextStyle(
|
val styleSpeed = TextStyle(
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
@@ -212,6 +242,23 @@ private fun Speed(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DarkMode(context: Context, baseStyle: MutableState<BaseStyle.Uri>) {
|
||||||
|
val darkMode = getIntKeyValue(context, Constants.DARK_MODE_SETTINGS)
|
||||||
|
if (darkMode == 0) {
|
||||||
|
baseStyle.value = BaseStyle.Uri(Constants.STYLE)
|
||||||
|
}
|
||||||
|
if (darkMode == 1) {
|
||||||
|
baseStyle.value = BaseStyle.Uri(Constants.STYLE_DARK)
|
||||||
|
}
|
||||||
|
if (darkMode == 2) {
|
||||||
|
baseStyle.value =
|
||||||
|
(if (isSystemInDarkTheme()) BaseStyle.Uri(Constants.STYLE_DARK) else BaseStyle.Uri(
|
||||||
|
Constants.STYLE
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
|
fun getPaddingValues(width: Int, height: Int, preView: Boolean): PaddingValues {
|
||||||
return if (preView) {
|
return if (preView) {
|
||||||
PaddingValues(start = 150.dp, bottom = 0.dp)
|
PaddingValues(start = 150.dp, bottom = 0.dp)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class RouteCarModel() : RouteModel() {
|
|||||||
.setIcon(createCarIcon(carContext, stepData.icon))
|
.setIcon(createCarIcon(carContext, stepData.icon))
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
.setRoad(destination.street!!)
|
.setRoad(routeState.destination.street!!)
|
||||||
.build()
|
.build()
|
||||||
return step
|
return step
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
|
import androidx.car.app.CarContext
|
||||||
|
import androidx.car.app.CarToast
|
||||||
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.model.Action
|
||||||
|
import androidx.car.app.model.Header
|
||||||
|
import androidx.car.app.model.ItemList
|
||||||
|
import androidx.car.app.model.ListTemplate
|
||||||
|
import androidx.car.app.model.Row
|
||||||
|
import androidx.car.app.model.SectionedItemList
|
||||||
|
import androidx.car.app.model.Template
|
||||||
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.data.Constants.DARK_MODE_SETTINGS
|
||||||
|
import com.kouros.navigation.data.Constants.SHOW_THREED_BUILDING
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getBooleanKeyValue
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.getIntKeyValue
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.setBooleanKeyValue
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.setIntKeyValue
|
||||||
|
|
||||||
|
class DarkModeSettings(private val carContext: CarContext) : Screen(carContext) {
|
||||||
|
|
||||||
|
private var darkModeSettings = 0
|
||||||
|
|
||||||
|
init {
|
||||||
|
darkModeSettings = getIntKeyValue(carContext, DARK_MODE_SETTINGS)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetTemplate(): Template {
|
||||||
|
val templateBuilder = ListTemplate.Builder()
|
||||||
|
val radioList =
|
||||||
|
ItemList.Builder()
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.off_action_title,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.on_action_title,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.addItem(
|
||||||
|
buildRowForTemplate(
|
||||||
|
R.string.use_telephon_settings,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.setOnSelectedListener { index: Int ->
|
||||||
|
this.onSelected(index)
|
||||||
|
}
|
||||||
|
.setSelectedIndex(darkModeSettings)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return templateBuilder
|
||||||
|
.addSectionedList(SectionedItemList.create(
|
||||||
|
radioList,
|
||||||
|
carContext.getString(R.string.dark_mode)
|
||||||
|
))
|
||||||
|
.setHeader(
|
||||||
|
Header.Builder()
|
||||||
|
.setTitle(carContext.getString(R.string.dark_mode))
|
||||||
|
.setStartHeaderAction(Action.BACK)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun onSelected(index: Int) {
|
||||||
|
setIntKeyValue(carContext, index, DARK_MODE_SETTINGS)
|
||||||
|
CarToast.makeText(
|
||||||
|
carContext,
|
||||||
|
(carContext
|
||||||
|
.getString(R.string.display_settings)
|
||||||
|
+ ":"
|
||||||
|
+ " " + index), CarToast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildRowForTemplate(title: Int): Row {
|
||||||
|
return Row.Builder()
|
||||||
|
.setTitle(carContext.getString(title))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package com.kouros.navigation.car.screen
|
package com.kouros.navigation.car.screen
|
||||||
|
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.CarToast
|
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.Header
|
import androidx.car.app.model.Header
|
||||||
import androidx.car.app.model.ItemList
|
import androidx.car.app.model.ItemList
|
||||||
import androidx.car.app.model.ListTemplate
|
import androidx.car.app.model.ListTemplate
|
||||||
|
import androidx.car.app.model.OnClickListener
|
||||||
import androidx.car.app.model.Row
|
import androidx.car.app.model.Row
|
||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import androidx.car.app.model.Toggle
|
import androidx.car.app.model.Toggle
|
||||||
@@ -19,6 +19,7 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
|
|||||||
|
|
||||||
private var buildingToggleState = false
|
private var buildingToggleState = false
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
buildingToggleState = getBooleanKeyValue(carContext, SHOW_THREED_BUILDING)
|
buildingToggleState = getBooleanKeyValue(carContext, SHOW_THREED_BUILDING)
|
||||||
}
|
}
|
||||||
@@ -35,7 +36,12 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
|
|||||||
buildingToggleState = !buildingToggleState
|
buildingToggleState = !buildingToggleState
|
||||||
}.setChecked(buildingToggleState).build()
|
}.setChecked(buildingToggleState).build()
|
||||||
listBuilder.addItem(buildRowForTemplate(R.string.threed_building, buildingToggle))
|
listBuilder.addItem(buildRowForTemplate(R.string.threed_building, buildingToggle))
|
||||||
|
listBuilder.addItem(
|
||||||
|
buildRowForScreenTemplate(
|
||||||
|
DarkModeSettings(carContext),
|
||||||
|
R.string.dark_mode
|
||||||
|
)
|
||||||
|
)
|
||||||
return ListTemplate.Builder()
|
return ListTemplate.Builder()
|
||||||
.setSingleList(listBuilder.build())
|
.setSingleList(listBuilder.build())
|
||||||
.setHeader(
|
.setHeader(
|
||||||
@@ -54,4 +60,12 @@ class DisplaySettings(private val carContext: CarContext) : Screen(carContext) {
|
|||||||
.setToggle(toggle)
|
.setToggle(toggle)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildRowForScreenTemplate(screen: Screen, title: Int): Row {
|
||||||
|
return Row.Builder()
|
||||||
|
.setTitle(carContext.getString(title))
|
||||||
|
.setOnClickListener { screenManager.push(screen) }
|
||||||
|
.setBrowsable(true)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -111,11 +111,11 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
private fun navigationEndTemplate(actionStripBuilder: ActionStrip.Builder): Template {
|
||||||
if (routeModel.isArrived()) {
|
if (routeModel.routeState.arrived) {
|
||||||
val timer = object : CountDownTimer(10000, 10000) {
|
val timer = object : CountDownTimer(10000, 10000) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
routeModel.arrived = false
|
routeModel.routeState = routeModel.routeState.copy(arrived = false)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,12 +135,16 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
fun navigationArrivedTemplate(actionStripBuilder: ActionStrip.Builder): NavigationTemplate {
|
||||||
|
var street = ""
|
||||||
|
if (routeModel.routeState.destination.street != null) {
|
||||||
|
street = routeModel.routeState.destination.street!!
|
||||||
|
}
|
||||||
return NavigationTemplate.Builder()
|
return NavigationTemplate.Builder()
|
||||||
.setNavigationInfo(
|
.setNavigationInfo(
|
||||||
MessageInfo.Builder(
|
MessageInfo.Builder(
|
||||||
carContext.getString(R.string.arrived_exclamation_msg)
|
carContext.getString(R.string.arrived_exclamation_msg)
|
||||||
)
|
)
|
||||||
.setText(routeModel.destination.street!!)
|
.setText(street)
|
||||||
.setImage(
|
.setImage(
|
||||||
CarIcon.Builder(
|
CarIcon.Builder(
|
||||||
IconCompat.createWithResource(
|
IconCompat.createWithResource(
|
||||||
@@ -192,7 +196,7 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getRoutingInfo(): RoutingInfo {
|
fun getRoutingInfo(): RoutingInfo {
|
||||||
var currentDistance = routeModel.currentDistance
|
var currentDistance = routeModel.leftStepDistance()
|
||||||
val displayUnit = if (currentDistance > 1000.0) {
|
val displayUnit = if (currentDistance > 1000.0) {
|
||||||
currentDistance /= 1000.0
|
currentDistance /= 1000.0
|
||||||
Distance.UNIT_KILOMETERS
|
Distance.UNIT_KILOMETERS
|
||||||
@@ -274,7 +278,7 @@ class NavigationScreen(
|
|||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
|
val navigateTo = location(recentPlace.longitude, recentPlace.latitude)
|
||||||
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo)
|
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, navigateTo)
|
||||||
routeModel.destination = recentPlace
|
routeModel.routeState.destination = recentPlace
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
@@ -398,7 +402,7 @@ class NavigationScreen(
|
|||||||
viewModel.saveRecent(place)
|
viewModel.saveRecent(place)
|
||||||
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
|
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, location)
|
||||||
currentNavigationLocation = location
|
currentNavigationLocation = location
|
||||||
routeModel.destination = place
|
routeModel.routeState.destination = place
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +420,7 @@ class NavigationScreen(
|
|||||||
invalidate()
|
invalidate()
|
||||||
val mainThreadHandler = Handler(carContext.mainLooper)
|
val mainThreadHandler = Handler(carContext.mainLooper)
|
||||||
mainThreadHandler.post {
|
mainThreadHandler.post {
|
||||||
object : CountDownTimer(5000, 1000) {
|
object : CountDownTimer(3000, 1000) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
calculateNewRoute = false
|
calculateNewRoute = false
|
||||||
@@ -427,19 +431,20 @@ class NavigationScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun reRoute(destination: Place) {
|
fun reRoute(destination: Place) {
|
||||||
val dest = location( destination.longitude, destination.latitude)
|
val dest = location(destination.longitude, destination.latitude)
|
||||||
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, dest)
|
viewModel.loadRoute(carContext, surfaceRenderer.lastLocation, dest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateTrip(location: Location) {
|
fun updateTrip(location: Location) {
|
||||||
val start = System.currentTimeMillis()
|
with(routeModel) {
|
||||||
routeModel.updateLocation(location)
|
updateLocation(location)
|
||||||
val end = System.currentTimeMillis()
|
if (routeState.maneuverType == Maneuver.TYPE_DESTINATION
|
||||||
println("Time ${end-start}")
|
&& leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE
|
||||||
if (routeModel.maneuverType == Maneuver.TYPE_DESTINATION
|
) {
|
||||||
&& routeModel.leftStepDistance() < DESTINATION_ARRIVAL_DISTANCE) {
|
stopNavigation()
|
||||||
routeModel.arrived = true
|
routeState = routeState.copy(arrived = true)
|
||||||
routeModel.stopNavigation()
|
surfaceRenderer.routeData.value = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.kouros.navigation.data
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val NavigationColor = Color(0xFF052186)
|
val NavigationColor = Color(0xFF0730B2)
|
||||||
|
|
||||||
val RouteColor = Color(0xFF5582D0)
|
val RouteColor = Color(0xFF5582D0)
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,8 @@ object Constants {
|
|||||||
|
|
||||||
const val SHOW_THREED_BUILDING = "Show3D"
|
const val SHOW_THREED_BUILDING = "Show3D"
|
||||||
|
|
||||||
|
const val DARK_MODE_SETTINGS = "DarkMode"
|
||||||
|
|
||||||
const val AVOID_MOTORWAY = "AvoidMotorway"
|
const val AVOID_MOTORWAY = "AvoidMotorway"
|
||||||
|
|
||||||
const val AVOID_TOLLWAY = "AvoidTollway"
|
const val AVOID_TOLLWAY = "AvoidTollway"
|
||||||
|
|||||||
@@ -1,22 +1,28 @@
|
|||||||
package com.kouros.navigation.data
|
package com.kouros.navigation.data
|
||||||
|
|
||||||
|
import android.location.Location
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import com.kouros.navigation.data.valhalla.Maneuvers
|
import com.kouros.navigation.data.valhalla.Maneuvers
|
||||||
import com.kouros.navigation.data.valhalla.Summary
|
import com.kouros.navigation.data.valhalla.Summary
|
||||||
import com.kouros.navigation.data.valhalla.Trip
|
import com.kouros.navigation.data.valhalla.Trip
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaJson
|
import com.kouros.navigation.data.valhalla.ValhallaJson
|
||||||
|
import com.kouros.navigation.utils.NavigationUtils.createCenterLocation
|
||||||
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
|
import com.kouros.navigation.utils.NavigationUtils.createGeoJson
|
||||||
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
|
import com.kouros.navigation.utils.NavigationUtils.decodePolyline
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
|
import org.maplibre.geojson.FeatureCollection
|
||||||
import org.maplibre.geojson.Point
|
import org.maplibre.geojson.Point
|
||||||
|
import org.maplibre.turf.TurfMeasurement
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
data class Route (
|
data class Route(
|
||||||
/**
|
/**
|
||||||
* A Leg is a route between only two waypoints.
|
* A Leg is a route between only two waypoints.
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
var maneuvers: List<Maneuvers>,
|
val maneuvers: List<Maneuvers>,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The distance traveled from origin to destination.
|
* The distance traveled from origin to destination.
|
||||||
@@ -33,9 +39,16 @@ data class Route (
|
|||||||
*
|
*
|
||||||
* @since 1.0.0
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
var waypoints: List<List<Double>>,
|
val waypoints: List<List<Double>>,
|
||||||
|
|
||||||
val pointLocations : List<Point>,
|
/**
|
||||||
|
* List of [List<Point>] objects. Each `Point` is an input coordinate
|
||||||
|
* snapped to the road and path network. The `waypoint` appear in the list in the order of
|
||||||
|
* the input coordinates.
|
||||||
|
*
|
||||||
|
* @since 1.0.0
|
||||||
|
*/
|
||||||
|
val pointLocations: List<Point>,
|
||||||
|
|
||||||
val summary: Summary,
|
val summary: Summary,
|
||||||
|
|
||||||
@@ -43,27 +56,26 @@ data class Route (
|
|||||||
|
|
||||||
val time: Double,
|
val time: Double,
|
||||||
|
|
||||||
var routeGeoJson : String,
|
val routeGeoJson: String,
|
||||||
|
|
||||||
var currentManeuverIndex: Int
|
val currentManeuverIndex : Int,
|
||||||
|
|
||||||
|
val centerLocation: Location
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
private lateinit var maneuvers: List<Maneuvers>
|
private lateinit var maneuvers: List<Maneuvers>
|
||||||
private var distance: Double = 0.0
|
private var distance: Double = 0.0
|
||||||
|
|
||||||
private var time: Double = 0.0
|
private var time: Double = 0.0
|
||||||
private lateinit var waypoints: List<List<Double>>
|
private lateinit var waypoints: List<List<Double>>
|
||||||
private lateinit var pointLocations: List<Point>
|
private lateinit var pointLocations: List<Point>
|
||||||
|
private lateinit var summary: Summary
|
||||||
private lateinit var summary : Summary
|
private lateinit var trip: Trip
|
||||||
|
|
||||||
private lateinit var trip : Trip
|
|
||||||
|
|
||||||
private var routeGeoJson = ""
|
private var routeGeoJson = ""
|
||||||
|
private var centerLocation = location(0.0, 0.0)
|
||||||
|
|
||||||
fun route (route: String ) = apply {
|
fun route(route: String) = apply {
|
||||||
if (route.isNotEmpty() && route != "[]") {
|
if (route.isNotEmpty() && route != "[]") {
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
val valhalla = gson.fromJson(route, ValhallaJson::class.java)
|
val valhalla = gson.fromJson(route, ValhallaJson::class.java)
|
||||||
@@ -83,35 +95,37 @@ data class Route (
|
|||||||
points.add(point)
|
points.add(point)
|
||||||
}
|
}
|
||||||
pointLocations = points
|
pointLocations = points
|
||||||
this.routeGeoJson = createGeoJson(waypoints)
|
routeGeoJson = createGeoJson(waypoints)
|
||||||
|
centerLocation = createCenterLocation(routeGeoJson)
|
||||||
return Route(
|
return Route(
|
||||||
maneuvers, distance, waypoints, pointLocations, summary, trip, time, routeGeoJson, 0
|
maneuvers,
|
||||||
|
distance,
|
||||||
|
waypoints,
|
||||||
|
pointLocations,
|
||||||
|
summary,
|
||||||
|
trip,
|
||||||
|
time,
|
||||||
|
routeGeoJson,
|
||||||
|
0,
|
||||||
|
centerLocation
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun maneuverLocations(): List<Point> {
|
fun maneuverLocations(): List<Point> {
|
||||||
val beginShapeIndex = currentManeuver().beginShapeIndex
|
|
||||||
val endShapeIndex = if (currentManeuver().endShapeIndex >= waypoints.size) {
|
|
||||||
waypoints.size
|
|
||||||
} else {
|
|
||||||
currentManeuver().endShapeIndex + 1
|
|
||||||
}
|
|
||||||
//return pointLocations.subList(beginShapeIndex, endShapeIndex)
|
|
||||||
return pointLocations
|
return pointLocations
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun currentManeuver(): Maneuvers {
|
||||||
waypoints = mutableListOf()
|
|
||||||
maneuvers = mutableListOf()
|
|
||||||
routeGeoJson = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
fun currentManeuver() : Maneuvers {
|
|
||||||
return maneuvers[currentManeuverIndex]
|
return maneuvers[currentManeuverIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nextManeuver() : Maneuvers {
|
fun nextManeuver(): Maneuvers {
|
||||||
return maneuvers[currentManeuverIndex+1]
|
val nextIndex = currentManeuverIndex + 1
|
||||||
|
return if (nextIndex < maneuvers.size) {
|
||||||
|
maneuvers[nextIndex]
|
||||||
|
} else {
|
||||||
|
throw IndexOutOfBoundsException("No next maneuver available.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"id": "background",
|
"id": "background",
|
||||||
"type": "background",
|
"type": "background",
|
||||||
"layout": {"visibility": "visible"},
|
"layout": {"visibility": "visible"},
|
||||||
"paint": {"background-color": "rgba(146, 146, 142, 1)"}
|
"paint": {"background-color": "rgba(28, 28, 35, 1)"}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "natural_earth",
|
"id": "natural_earth",
|
||||||
|
|||||||
@@ -1,139 +1,99 @@
|
|||||||
package com.kouros.navigation.model
|
package com.kouros.navigation.model
|
||||||
|
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
|
||||||
import androidx.car.app.navigation.model.Maneuver
|
import androidx.car.app.navigation.model.Maneuver
|
||||||
import androidx.car.app.navigation.model.Step
|
import androidx.car.app.navigation.model.Step
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Constants
|
|
||||||
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
|
||||||
import com.kouros.navigation.data.ManeuverType
|
import com.kouros.navigation.data.ManeuverType
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.Route
|
import com.kouros.navigation.data.Route
|
||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
import org.maplibre.geojson.FeatureCollection
|
|
||||||
import org.maplibre.geojson.Point
|
|
||||||
import org.maplibre.turf.TurfMeasurement
|
import org.maplibre.turf.TurfMeasurement
|
||||||
|
import org.maplibre.turf.TurfMisc
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
open class RouteModel() {
|
open class RouteModel() {
|
||||||
lateinit var centerLocation: Location
|
data class RouteState(
|
||||||
|
val route: Route? = null,
|
||||||
|
val isNavigating: Boolean = false,
|
||||||
|
var destination: Place = Place(),
|
||||||
|
val arrived: Boolean = false,
|
||||||
|
var maneuverType: Int = 0,
|
||||||
|
var currentShapeIndex: Int = 0,
|
||||||
|
var distanceToStepEnd: Float = 0F,
|
||||||
|
var beginIndex: Int = 0,
|
||||||
|
var endIndex: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
lateinit var destination: Place
|
var routeState = RouteState()
|
||||||
|
|
||||||
var navigating = false
|
var route: Route
|
||||||
|
get() = routeState.route!!
|
||||||
var arrived = false
|
set(value) {
|
||||||
|
routeState = routeState.copy(route = value)
|
||||||
var maneuverType = 0
|
}
|
||||||
|
fun startNavigation(routeString: String) {
|
||||||
/*
|
val newRoute = Route.Builder()
|
||||||
current shapeIndex
|
.route(routeString)
|
||||||
*/
|
|
||||||
var currentShapeIndex = 0
|
|
||||||
|
|
||||||
var distanceToStepEnd = 0F
|
|
||||||
|
|
||||||
var beginIndex = 0
|
|
||||||
|
|
||||||
var endIndex = 0
|
|
||||||
|
|
||||||
lateinit var route: Route
|
|
||||||
|
|
||||||
fun startNavigation(valhallaRoute: String) {
|
|
||||||
route = Route.Builder()
|
|
||||||
.route(valhallaRoute)
|
|
||||||
.build()
|
.build()
|
||||||
centerLocation = createCenterLocation()
|
this.routeState = routeState.copy(
|
||||||
navigating = true
|
route = newRoute,
|
||||||
|
isNavigating = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
route.clear()
|
this.routeState = routeState.copy(
|
||||||
navigating = false
|
route = null,
|
||||||
currentShapeIndex = 0
|
isNavigating = false,
|
||||||
distanceToStepEnd = 0F
|
// destination = Place(),
|
||||||
beginIndex = 0
|
arrived = false,
|
||||||
endIndex = 0
|
maneuverType = 0,
|
||||||
|
currentShapeIndex = 0,
|
||||||
|
distanceToStepEnd = 0F,
|
||||||
|
beginIndex = 0,
|
||||||
|
endIndex = 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the geographic center of the route's GeoJSON data.
|
|
||||||
*
|
|
||||||
* @return A [Location] object representing the center point.
|
|
||||||
* @throws IllegalStateException if the calculated center does not have valid Point geometry.
|
|
||||||
*/
|
|
||||||
private fun createCenterLocation(): Location {
|
|
||||||
// 1. Create a FeatureCollection from the raw GeoJSON string.
|
|
||||||
val featureCollection = FeatureCollection.fromJson(route.routeGeoJson)
|
|
||||||
|
|
||||||
// 2. Calculate the center feature of the collection.
|
|
||||||
val centerFeature = TurfMeasurement.center(featureCollection)
|
|
||||||
|
|
||||||
// 3. Safely access and cast the geometry, throwing an informative error if it fails.
|
|
||||||
val centerPoint = centerFeature.geometry() as? Point
|
|
||||||
?: throw IllegalStateException("Center of GeoJSON is not a valid Point.")
|
|
||||||
|
|
||||||
// 4. Create and return the Location object.
|
|
||||||
return location(centerPoint.longitude(), centerPoint.latitude())
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The remaining distance to the step, rounded to the nearest 10 units.
|
|
||||||
*/
|
|
||||||
val currentDistance: Double
|
|
||||||
get() {
|
|
||||||
// This is a more direct way to round to the nearest multiple of 10.
|
|
||||||
return (leftStepDistance() / 10.0).roundToInt() * 10.0
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun updateLocation(location: Location) {
|
fun updateLocation(location: Location) {
|
||||||
var nearestDistance = 100000.0f
|
var nearestDistance = 100000.0f
|
||||||
for (i in route.currentManeuverIndex..<route.maneuvers.size) {
|
var newShapeIndex = -1
|
||||||
val maneuver = route.maneuvers[i]
|
// find nearest waypoint and current shape index
|
||||||
val beginShapeIndex = maneuver.beginShapeIndex
|
// start search at last shape index
|
||||||
val endShapeIndex = maneuver.endShapeIndex
|
for (i in routeState.currentShapeIndex..<route.waypoints.size) {
|
||||||
val distance = calculateDistance(beginShapeIndex, endShapeIndex, location)
|
val waypoint = route.waypoints[i]
|
||||||
|
val distance = location.distanceTo(location(waypoint[0], waypoint[1]))
|
||||||
if (distance < nearestDistance) {
|
if (distance < nearestDistance) {
|
||||||
nearestDistance = distance
|
nearestDistance = distance
|
||||||
route.currentManeuverIndex = i
|
newShapeIndex = i
|
||||||
calculateCurrentShapeIndex(beginShapeIndex, endShapeIndex, location)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// find maneuver
|
||||||
|
// calculate distance to step end
|
||||||
|
findManeuver(newShapeIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calculates the index in a maneuver. */
|
private fun findManeuver(newShapeIndex: Int) {
|
||||||
private fun calculateCurrentShapeIndex(
|
for (i in route.currentManeuverIndex..<route.maneuvers.size) {
|
||||||
beginShapeIndex: Int,
|
val maneuver = route.maneuvers[i]
|
||||||
endShapeIndex: Int,
|
if (maneuver.beginShapeIndex <= newShapeIndex && maneuver.endShapeIndex >= newShapeIndex) {
|
||||||
location: Location
|
route = route.copy(currentManeuverIndex = i)
|
||||||
) {
|
routeState.apply {
|
||||||
var nearestLocation = 100000.0f
|
currentShapeIndex = newShapeIndex
|
||||||
for (i in currentShapeIndex..endShapeIndex) {
|
beginIndex = maneuver.beginShapeIndex
|
||||||
val waypoint = Location(LocationManager.GPS_PROVIDER)
|
endIndex = maneuver.endShapeIndex
|
||||||
waypoint.longitude = route.waypoints[i][0]
|
distanceToStepEnd = 0F
|
||||||
waypoint.latitude = route.waypoints[i][1]
|
// calculate shape distance to step end
|
||||||
val distance: Float = location.distanceTo(waypoint)
|
for (j in newShapeIndex + 1..maneuver.endShapeIndex) {
|
||||||
if (distance < nearestLocation) {
|
val loc1 = location(route!!.waypoints[j - 1][0], route.waypoints[j - 1][1])
|
||||||
nearestLocation = distance
|
val loc2 = location(route.waypoints[j][0], route.waypoints[j][1])
|
||||||
currentShapeIndex = i
|
|
||||||
beginIndex = beginShapeIndex
|
|
||||||
endIndex = endShapeIndex
|
|
||||||
distanceToStepEnd = 0F
|
|
||||||
val loc1 = Location(LocationManager.GPS_PROVIDER)
|
|
||||||
val loc2 = Location(LocationManager.GPS_PROVIDER)
|
|
||||||
if (i + 1 < route.waypoints.size) {
|
|
||||||
for (j in i + 1..endShapeIndex) {
|
|
||||||
loc1.longitude = route.waypoints[j - 1][0]
|
|
||||||
loc1.latitude = route.waypoints[j - 1][1]
|
|
||||||
loc2.longitude = route.waypoints[j][0]
|
|
||||||
loc2.latitude = route.waypoints[j][1]
|
|
||||||
distanceToStepEnd += loc1.distanceTo(loc2)
|
distanceToStepEnd += loc1.distanceTo(loc2)
|
||||||
}
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,42 +135,6 @@ open class RouteModel() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun currentStepx(): StepData {
|
|
||||||
val maneuver = route.currentManeuver()
|
|
||||||
var text = ""
|
|
||||||
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
|
||||||
text = maneuver.streetNames[0]
|
|
||||||
}
|
|
||||||
val distanceStepLeft = leftStepDistance()
|
|
||||||
when (distanceStepLeft) {
|
|
||||||
in 0.0..Constants.NEXT_STEP_THRESHOLD -> {
|
|
||||||
if (route.currentManeuverIndex < route.maneuvers.size) {
|
|
||||||
val maneuver = route.nextManeuver()
|
|
||||||
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
|
||||||
text = maneuver.streetNames[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val type = if (hasArrived(maneuverType)) {
|
|
||||||
maneuver.type
|
|
||||||
} else {
|
|
||||||
ManeuverType.None.value
|
|
||||||
}
|
|
||||||
var routing: (Pair<Int, Int>) = maneuverIcon(type)
|
|
||||||
when (distanceStepLeft) {
|
|
||||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
|
||||||
if (route.currentManeuverIndex < route.maneuvers.size) {
|
|
||||||
val maneuver = route.nextManeuver()
|
|
||||||
val maneuverType = maneuver.type
|
|
||||||
routing = maneuverIcon(maneuverType)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return StepData(text, distanceStepLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun nextStep(): StepData {
|
fun nextStep(): StepData {
|
||||||
val maneuver = route.nextManeuver()
|
val maneuver = route.nextManeuver()
|
||||||
val maneuverType = maneuver.type
|
val maneuverType = maneuver.type
|
||||||
@@ -220,30 +144,24 @@ open class RouteModel() {
|
|||||||
when (distanceLeft) {
|
when (distanceLeft) {
|
||||||
in 0.0..NEXT_STEP_THRESHOLD -> {
|
in 0.0..NEXT_STEP_THRESHOLD -> {
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (maneuver.streetNames != null && maneuver.streetNames!!.isNotEmpty()) {
|
if (maneuver.streetNames != null && maneuver.streetNames.isNotEmpty()) {
|
||||||
text = maneuver.streetNames[0]
|
text = maneuver.streetNames[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val routing: (Pair<Int, Int>) = maneuverIcon(maneuverType)
|
|
||||||
return StepData(text, distanceLeft, routing.first, routing.second, arrivalTime(), travelLeftDistance())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun calculateDistance(
|
val routing: (Pair<Int, Int>) = maneuverIcon(maneuverType)
|
||||||
beginShapeIndex: Int,
|
// Construct and return the final StepData object
|
||||||
endShapeIndex: Int,
|
return StepData(
|
||||||
location: Location
|
text,
|
||||||
): Float {
|
distanceLeft,
|
||||||
var nearestLocation = 100000.0f
|
routing.first,
|
||||||
for (i in beginShapeIndex..endShapeIndex) {
|
routing.second,
|
||||||
val polylineLocation = location(route.waypoints[i][0], route.waypoints[i][1])
|
arrivalTime(),
|
||||||
val distance: Float = location.distanceTo(polylineLocation)
|
travelLeftDistance()
|
||||||
if (distance < nearestLocation) {
|
)
|
||||||
nearestLocation = distance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nearestLocation
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun travelLeftTime(): Double {
|
fun travelLeftTime(): Double {
|
||||||
@@ -252,10 +170,11 @@ open class RouteModel() {
|
|||||||
val maneuver = route.maneuvers[i]
|
val maneuver = route.maneuvers[i]
|
||||||
timeLeft += maneuver.time
|
timeLeft += maneuver.time
|
||||||
}
|
}
|
||||||
if (endIndex > 0) {
|
if (routeState.endIndex > 0) {
|
||||||
val maneuver = route.currentManeuver()
|
val maneuver = route.currentManeuver()
|
||||||
val curTime = maneuver.time
|
val curTime = maneuver.time
|
||||||
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
|
val percent =
|
||||||
|
100 * (routeState.endIndex - routeState.currentShapeIndex) / (routeState.endIndex - routeState.beginIndex)
|
||||||
val time = curTime * percent / 100
|
val time = curTime * percent / 100
|
||||||
timeLeft += time
|
timeLeft += time
|
||||||
}
|
}
|
||||||
@@ -275,10 +194,12 @@ open class RouteModel() {
|
|||||||
fun leftStepDistance(): Double {
|
fun leftStepDistance(): Double {
|
||||||
val maneuver = route.currentManeuver()
|
val maneuver = route.currentManeuver()
|
||||||
var leftDistance = maneuver.length
|
var leftDistance = maneuver.length
|
||||||
if (endIndex > 0) {
|
if (routeState.endIndex > 0) {
|
||||||
leftDistance = (distanceToStepEnd / 1000).toDouble()
|
leftDistance = (routeState.distanceToStepEnd / 1000).toDouble()
|
||||||
}
|
}
|
||||||
return leftDistance * 1000
|
// The remaining distance to the step, rounded to the nearest 10 units.
|
||||||
|
return (leftDistance * 1000 / 10.0).roundToInt() * 10.0
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the left distance in km. */
|
/** Returns the left distance in km. */
|
||||||
@@ -288,10 +209,11 @@ open class RouteModel() {
|
|||||||
val maneuver = route.maneuvers[i]
|
val maneuver = route.maneuvers[i]
|
||||||
leftDistance += maneuver.length
|
leftDistance += maneuver.length
|
||||||
}
|
}
|
||||||
if (endIndex > 0) {
|
if (routeState.endIndex > 0) {
|
||||||
val maneuver = route.currentManeuver()
|
val maneuver = route.currentManeuver()
|
||||||
val curDistance = maneuver.length
|
val curDistance = maneuver.length
|
||||||
val percent = 100 * (endIndex - currentShapeIndex) / (endIndex - beginIndex)
|
val percent =
|
||||||
|
100 * (routeState.endIndex - routeState.currentShapeIndex) / (routeState.endIndex - routeState.beginIndex)
|
||||||
val time = curDistance * percent / 100
|
val time = curDistance * percent / 100
|
||||||
leftDistance += time
|
leftDistance += time
|
||||||
}
|
}
|
||||||
@@ -360,21 +282,21 @@ open class RouteModel() {
|
|||||||
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
currentTurnIcon = R.drawable.ic_roundabout_ccw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maneuverType = type
|
routeState.maneuverType = type
|
||||||
return Pair(type, currentTurnIcon)
|
return Pair(type, currentTurnIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isNavigating(): Boolean {
|
fun isNavigating(): Boolean {
|
||||||
return navigating
|
return routeState.isNavigating
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isArrived(): Boolean {
|
fun isArrived(): Boolean {
|
||||||
return arrived
|
return routeState.arrived
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasArrived(type: Int): Boolean {
|
fun hasArrived(type: Int): Boolean {
|
||||||
return type == ManeuverType.DestinationRight.value
|
return type == ManeuverType.DestinationRight.value
|
||||||
|| maneuverType == ManeuverType.Destination.value
|
|| routeState.maneuverType == ManeuverType.Destination.value
|
||||||
|| maneuverType == ManeuverType.DestinationLeft.value
|
|| routeState.maneuverType == ManeuverType.DestinationLeft.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
savePlace(place)
|
savePlace(place)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun savePlace(place: Place) {
|
private fun savePlace(place: Place) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val placeBox = boxStore.boxFor(Place::class)
|
val placeBox = boxStore.boxFor(Place::class)
|
||||||
@@ -258,6 +258,7 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
val current = LocalDateTime.now(ZoneOffset.UTC)
|
val current = LocalDateTime.now(ZoneOffset.UTC)
|
||||||
place.lastDate = current.atZone(ZoneOffset.UTC).toEpochSecond()
|
place.lastDate = current.atZone(ZoneOffset.UTC).toEpochSecond()
|
||||||
placeBox.put(place)
|
placeBox.put(place)
|
||||||
|
println("Save Recent $place")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -333,4 +334,21 @@ class ViewModel(private val repository: NavigationRepository) : ViewModel() {
|
|||||||
return results.toMutableStateList()
|
return results.toMutableStateList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadRecentPlace(): SnapshotStateList<Place?> {
|
||||||
|
val results = listOf<Place>()
|
||||||
|
try {
|
||||||
|
val placeBox = boxStore.boxFor(Place::class)
|
||||||
|
val query = placeBox
|
||||||
|
.query(Place_.name.notEqual("").and(Place_.category.equal(Constants.RECENT)))
|
||||||
|
.orderDesc(Place_.lastDate)
|
||||||
|
.build()
|
||||||
|
val results = query.find()
|
||||||
|
query.close()
|
||||||
|
return results.toMutableStateList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
return results.toMutableStateList()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,9 @@ import com.kouros.navigation.data.GeoJsonFeature
|
|||||||
import com.kouros.navigation.data.GeoJsonFeatureCollection
|
import com.kouros.navigation.data.GeoJsonFeatureCollection
|
||||||
import com.kouros.navigation.data.GeoJsonLineString
|
import com.kouros.navigation.data.GeoJsonLineString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import org.maplibre.geojson.FeatureCollection
|
||||||
import org.maplibre.geojson.Point
|
import org.maplibre.geojson.Point
|
||||||
|
import org.maplibre.turf.TurfMeasurement
|
||||||
import org.maplibre.turf.TurfMisc
|
import org.maplibre.turf.TurfMisc
|
||||||
import java.lang.Math.toDegrees
|
import java.lang.Math.toDegrees
|
||||||
import java.lang.Math.toRadians
|
import java.lang.Math.toRadians
|
||||||
@@ -19,13 +21,15 @@ import java.time.ZoneOffset
|
|||||||
import java.time.ZonedDateTime
|
import java.time.ZonedDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.math.asin
|
import kotlin.math.asin
|
||||||
import kotlin.math.atan2
|
import kotlin.math.atan2
|
||||||
import kotlin.math.cos
|
import kotlin.math.cos
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.sin
|
import kotlin.math.sin
|
||||||
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
|
|
||||||
object NavigationUtils {
|
object NavigationUtils {
|
||||||
@@ -52,6 +56,29 @@ object NavigationUtils {
|
|||||||
apply()
|
apply()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getIntKeyValue(context: Context, key: String) : Int {
|
||||||
|
return context
|
||||||
|
.getSharedPreferences(
|
||||||
|
SHARED_PREF_KEY,
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
.getInt(key, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIntKeyValue(context: Context, `val`: Int, key: String) {
|
||||||
|
context
|
||||||
|
.getSharedPreferences(
|
||||||
|
SHARED_PREF_KEY,
|
||||||
|
Context.MODE_PRIVATE
|
||||||
|
)
|
||||||
|
.edit {
|
||||||
|
putInt(
|
||||||
|
key, `val`
|
||||||
|
)
|
||||||
|
apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
fun snapLocation(location: Location, stepCoordinates: List<Point>) : Location {
|
fun snapLocation(location: Location, stepCoordinates: List<Point>) : Location {
|
||||||
val newLocation = Location(location)
|
val newLocation = Location(location)
|
||||||
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
val oldPoint = Point.fromLngLat(location.longitude, location.latitude)
|
||||||
@@ -101,6 +128,26 @@ object NavigationUtils {
|
|||||||
return coordinates
|
return coordinates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the geographic center of the route's GeoJSON data.
|
||||||
|
*
|
||||||
|
* @return A [Location] object representing the center point.
|
||||||
|
* @throws IllegalStateException if the calculated center does not have valid Point geometry.
|
||||||
|
*/
|
||||||
|
fun createCenterLocation(routeGeoJson: String): Location {
|
||||||
|
// 1. Create a FeatureCollection from the raw GeoJSON string.
|
||||||
|
val featureCollection = FeatureCollection.fromJson(routeGeoJson)
|
||||||
|
|
||||||
|
// 2. Calculate the center feature of the collection.
|
||||||
|
val centerFeature = TurfMeasurement.center(featureCollection)
|
||||||
|
|
||||||
|
// 3. Safely access and cast the geometry, throwing an informative error if it fails.
|
||||||
|
val centerPoint = centerFeature.geometry() as? Point
|
||||||
|
?: throw IllegalStateException("Center of GeoJSON is not a valid Point.")
|
||||||
|
|
||||||
|
// 4. Create and return the Location object.
|
||||||
|
return location(centerPoint.longitude(), centerPoint.latitude())
|
||||||
|
}
|
||||||
fun createGeoJson(lineCoordinates: List<List<Double>>): String {
|
fun createGeoJson(lineCoordinates: List<List<Double>>): String {
|
||||||
|
|
||||||
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
|
val lineString = GeoJsonLineString(type = "LineString", coordinates = lineCoordinates)
|
||||||
@@ -210,4 +257,16 @@ fun formatDateTime(time: Long): String {
|
|||||||
fun Double.round(numFractionDigits: Int): Double {
|
fun Double.round(numFractionDigits: Int): Double {
|
||||||
val factor = 10.0.pow(numFractionDigits.toDouble())
|
val factor = 10.0.pow(numFractionDigits.toDouble())
|
||||||
return (this * factor).roundToInt() / factor
|
return (this * factor).roundToInt() / factor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun duration(preview: Boolean, bearing: Double, lastBearing: Double): Duration {
|
||||||
|
if (preview) {
|
||||||
|
return 3.seconds
|
||||||
|
}
|
||||||
|
val cameraDuration = if ((lastBearing - bearing).absoluteValue > 20.0) {
|
||||||
|
2.seconds
|
||||||
|
} else {
|
||||||
|
1.seconds
|
||||||
|
}
|
||||||
|
return cameraDuration
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.13.1"
|
agp = "8.13.2"
|
||||||
androidSdkTurf = "6.0.1"
|
androidSdkTurf = "6.0.1"
|
||||||
gradle = "8.13.1"
|
gradle = "8.13.2"
|
||||||
koinAndroid = "4.1.1"
|
koinAndroid = "4.1.1"
|
||||||
koinAndroidxCompose = "4.1.1"
|
koinAndroidxCompose = "4.1.1"
|
||||||
koinComposeViewmodel = "4.1.1"
|
koinComposeViewmodel = "4.1.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user