Preview
This commit is contained in:
@@ -13,8 +13,8 @@ android {
|
|||||||
applicationId = "com.kouros.navigation"
|
applicationId = "com.kouros.navigation"
|
||||||
minSdk = 33
|
minSdk = 33
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 64
|
versionCode = 66
|
||||||
versionName = "0.2.0.64"
|
versionName = "0.2.0.66"
|
||||||
base.archivesName = "navi-$versionName"
|
base.archivesName = "navi-$versionName"
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.AppOpsManager
|
import android.app.AppOpsManager
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -11,32 +10,22 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
import androidx.compose.animation.core.animateIntAsState
|
|
||||||
import androidx.compose.foundation.gestures.detectVerticalDragGestures
|
|
||||||
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.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.BottomSheetScaffold
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
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.mutableIntStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -68,6 +57,8 @@ import com.kouros.navigation.model.testSingle
|
|||||||
import com.kouros.navigation.ui.app.AppViewModel
|
import com.kouros.navigation.ui.app.AppViewModel
|
||||||
import com.kouros.navigation.ui.app.appViewModel
|
import com.kouros.navigation.ui.app.appViewModel
|
||||||
import com.kouros.navigation.ui.navigation.AppNavGraph
|
import com.kouros.navigation.ui.navigation.AppNavGraph
|
||||||
|
import com.kouros.navigation.ui.navigation.NavigationSheet
|
||||||
|
import com.kouros.navigation.ui.search.SearchSheet
|
||||||
import com.kouros.navigation.ui.theme.NavigationTheme
|
import com.kouros.navigation.ui.theme.NavigationTheme
|
||||||
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
import com.kouros.navigation.utils.GeoUtils.snapLocation
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
@@ -80,8 +71,10 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.location.DesiredAccuracy
|
import org.maplibre.compose.location.DesiredAccuracy
|
||||||
import org.maplibre.compose.location.Location
|
import org.maplibre.compose.location.Location
|
||||||
|
import org.maplibre.compose.location.UserLocationState
|
||||||
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
import org.maplibre.compose.location.rememberDefaultLocationProvider
|
||||||
import org.maplibre.compose.location.rememberUserLocationState
|
import org.maplibre.compose.location.rememberUserLocationState
|
||||||
|
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
|
||||||
|
|
||||||
@@ -121,6 +114,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
SimulationType.GPX -> gpx(
|
SimulationType.GPX -> gpx(
|
||||||
context = applicationContext, mock
|
context = applicationContext, mock
|
||||||
)
|
)
|
||||||
|
|
||||||
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
|
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,30 +172,19 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
setContent {
|
setContent {
|
||||||
CheckPermissionScreen()
|
CheckPermissionScreen(app = {
|
||||||
}
|
AppNavGraph(
|
||||||
}
|
mainActivity = this
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
@Composable
|
|
||||||
fun CheckPermissionScreen() {
|
|
||||||
val permissions = listOf(
|
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
)
|
|
||||||
PermissionScreen(
|
|
||||||
permissions = permissions,
|
|
||||||
requiredPermissions = listOf(permissions.first()),
|
|
||||||
onGranted = {
|
|
||||||
Application()
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun StartScreen(
|
fun StartScreen(
|
||||||
navController: NavHostController) {
|
navController: NavHostController
|
||||||
|
) {
|
||||||
|
|
||||||
val appViewModel: AppViewModel = appViewModel()
|
val appViewModel: AppViewModel = appViewModel()
|
||||||
val darkMode by appViewModel.darkMode.collectAsState()
|
val darkMode by appViewModel.darkMode.collectAsState()
|
||||||
@@ -209,6 +192,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
val locationProvider = rememberDefaultLocationProvider(
|
val locationProvider = rememberDefaultLocationProvider(
|
||||||
updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest
|
updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest
|
||||||
)
|
)
|
||||||
|
val lastRoute by appViewModel.lastRoute.collectAsState()
|
||||||
|
if (lastRoute.isNotEmpty()) {
|
||||||
|
navigationViewModel.route.value = lastRoute
|
||||||
|
}
|
||||||
val userLocationState = rememberUserLocationState(locationProvider)
|
val userLocationState = rememberUserLocationState(locationProvider)
|
||||||
if (!useMock) {
|
if (!useMock) {
|
||||||
val locationState = locationProvider.location.collectAsState()
|
val locationState = locationProvider.location.collectAsState()
|
||||||
@@ -217,86 +204,27 @@ class MainActivity : ComponentActivity() {
|
|||||||
val step: StepData? by stepData.observeAsState()
|
val step: StepData? by stepData.observeAsState()
|
||||||
val nextStep: StepData? by nextStepData.observeAsState()
|
val nextStep: StepData? by nextStepData.observeAsState()
|
||||||
|
|
||||||
var collapsedHeight by remember {
|
|
||||||
mutableIntStateOf(300)
|
|
||||||
}
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
fun closeSheet() {
|
|
||||||
scope.launch {
|
|
||||||
collapsedHeight = if (routeModel.isNavigating()) {
|
|
||||||
150
|
|
||||||
} else {
|
|
||||||
300
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp
|
|
||||||
var expandedType by remember {
|
|
||||||
mutableStateOf(ExpandedType.COLLAPSED)
|
|
||||||
}
|
|
||||||
|
|
||||||
val height by animateIntAsState(
|
|
||||||
when (expandedType) {
|
|
||||||
ExpandedType.HALF -> screenHeight / 2
|
|
||||||
ExpandedType.FULL -> screenHeight
|
|
||||||
ExpandedType.COLLAPSED -> collapsedHeight
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState()
|
|
||||||
NavigationTheme(useDarkTheme = darkMode == 1) {
|
NavigationTheme(useDarkTheme = darkMode == 1) {
|
||||||
BottomSheetScaffold(
|
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||||
scaffoldState = bottomSheetScaffoldState,
|
SheetLayout(
|
||||||
sheetShape = RoundedCornerShape(
|
map = { _ ->
|
||||||
bottomStart = 0.dp,
|
Map(
|
||||||
bottomEnd = 0.dp,
|
userLocationState, step, nextStep, baseStyle, navController
|
||||||
topStart = 12.dp,
|
)
|
||||||
topEnd = 12.dp
|
|
||||||
),
|
|
||||||
sheetContent = {
|
|
||||||
var isUpdated = false
|
|
||||||
Box(
|
|
||||||
Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(height.dp)
|
|
||||||
.pointerInput(Unit) {
|
|
||||||
detectVerticalDragGestures(
|
|
||||||
onVerticalDrag = { change, dragAmount ->
|
|
||||||
change.consume()
|
|
||||||
if (!isUpdated) {
|
|
||||||
expandedType = when {
|
|
||||||
dragAmount < 0 && expandedType == ExpandedType.COLLAPSED -> {
|
|
||||||
ExpandedType.HALF
|
|
||||||
}
|
|
||||||
dragAmount < 0 && expandedType == ExpandedType.HALF -> {
|
|
||||||
ExpandedType.FULL
|
|
||||||
}
|
|
||||||
|
|
||||||
dragAmount > 0 && expandedType == ExpandedType.FULL -> {
|
|
||||||
ExpandedType.HALF
|
|
||||||
}
|
|
||||||
|
|
||||||
dragAmount > 0 && expandedType == ExpandedType.HALF -> {
|
|
||||||
ExpandedType.COLLAPSED
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
ExpandedType.FULL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isUpdated = true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onDragEnd = {
|
menu = { SheetContent(navController, step, nextStep) },
|
||||||
isUpdated = false
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
SheetContent(step, nextStep) { closeSheet() }
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
sheetPeekHeight = height.dp
|
|
||||||
|
@Composable
|
||||||
|
private fun Map(
|
||||||
|
userLocationState: UserLocationState,
|
||||||
|
step: StepData?,
|
||||||
|
nextStep: StepData?,
|
||||||
|
baseStyle: BaseStyle.Json,
|
||||||
|
navController: NavHostController
|
||||||
) {
|
) {
|
||||||
MapView(
|
MapView(
|
||||||
applicationContext,
|
applicationContext,
|
||||||
@@ -312,18 +240,6 @@ class MainActivity : ComponentActivity() {
|
|||||||
Settings(navController, modifier = Modifier.fillMaxWidth())
|
Settings(navController, modifier = Modifier.fillMaxWidth())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Application() {
|
|
||||||
val appViewModel: AppViewModel = appViewModel()
|
|
||||||
val lastRoute by appViewModel.lastRoute.collectAsState()
|
|
||||||
if (lastRoute.isNotEmpty()) {
|
|
||||||
navigationViewModel.route.value = lastRoute
|
|
||||||
}
|
|
||||||
AppNavGraph(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Settings(navController: NavController, modifier: Modifier = Modifier) {
|
fun Settings(navController: NavController, modifier: Modifier = Modifier) {
|
||||||
@@ -347,10 +263,10 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SheetContent(
|
fun SheetContent(
|
||||||
step: StepData?, nextStep: StepData?, closeSheet: () -> Unit
|
navController: NavHostController, step: StepData?, nextStep: StepData?
|
||||||
) {
|
) {
|
||||||
if (!routeModel.isNavigating()) {
|
if (!routeModel.isNavigating()) {
|
||||||
SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() }
|
SearchSheet(applicationContext, navController, navigationViewModel, lastLocation) { }
|
||||||
} else {
|
} else {
|
||||||
if (step != null) {
|
if (step != null) {
|
||||||
NavigationSheet(
|
NavigationSheet(
|
||||||
@@ -358,7 +274,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
routeModel,
|
routeModel,
|
||||||
step,
|
step,
|
||||||
nextStep,
|
nextStep,
|
||||||
{ stopNavigation { closeSheet() } },
|
{ stopNavigation {} },
|
||||||
{ simulateNavigation() })
|
{ simulateNavigation() })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,8 +362,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
fun simulateNavigation() {
|
fun simulateNavigation() {
|
||||||
simulate(
|
simulate(
|
||||||
routeModel = routeModel,
|
routeModel = routeModel, mock = mock
|
||||||
mock = mock
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -18,6 +19,7 @@ import com.kouros.navigation.car.map.NavigationImage
|
|||||||
import com.kouros.navigation.data.StepData
|
import com.kouros.navigation.data.StepData
|
||||||
import com.kouros.navigation.ui.app.AppViewModel
|
import com.kouros.navigation.ui.app.AppViewModel
|
||||||
import com.kouros.navigation.ui.app.appViewModel
|
import com.kouros.navigation.ui.app.appViewModel
|
||||||
|
import com.kouros.navigation.ui.navigation.NavigationInfo
|
||||||
import org.maplibre.compose.camera.CameraPosition
|
import org.maplibre.compose.camera.CameraPosition
|
||||||
import org.maplibre.compose.camera.rememberCameraState
|
import org.maplibre.compose.camera.rememberCameraState
|
||||||
import org.maplibre.compose.location.LocationTrackingEffect
|
import org.maplibre.compose.location.LocationTrackingEffect
|
||||||
@@ -62,6 +64,8 @@ fun MapView(
|
|||||||
val showBuildings by appViewModel.show3D.collectAsState()
|
val showBuildings by appViewModel.show3D.collectAsState()
|
||||||
val darkMode by appViewModel.darkMode.collectAsState()
|
val darkMode by appViewModel.darkMode.collectAsState()
|
||||||
|
|
||||||
|
val dark = darkMode == 1 || darkMode == 2 && isSystemInDarkTheme()
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
NavigationInfo(step, nextStep)
|
NavigationInfo(step, nextStep)
|
||||||
Box(contentAlignment = Alignment.Center) {
|
Box(contentAlignment = Alignment.Center) {
|
||||||
@@ -88,7 +92,7 @@ fun MapView(
|
|||||||
duration = 1.seconds
|
duration = 1.seconds
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
NavigationImage(paddingValues, width, height / 6, "", darkMode)
|
NavigationImage(paddingValues, width, height / 6, "", dark)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
@@ -37,6 +39,25 @@ import androidx.core.net.toUri
|
|||||||
*
|
*
|
||||||
* By default it assumes that all [permissions] are required.
|
* By default it assumes that all [permissions] are required.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
@Composable
|
||||||
|
fun CheckPermissionScreen(
|
||||||
|
app: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val permissions = listOf(
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
)
|
||||||
|
PermissionScreen(
|
||||||
|
permissions = permissions,
|
||||||
|
requiredPermissions = listOf(permissions.first()),
|
||||||
|
onGranted = {
|
||||||
|
app()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PermissionScreen(
|
fun PermissionScreen(
|
||||||
|
|||||||
81
app/src/main/java/com/kouros/navigation/ui/SheetLayout.kt
Normal file
81
app/src/main/java/com/kouros/navigation/ui/SheetLayout.kt
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
package com.kouros.navigation.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.requiredHeight
|
||||||
|
import androidx.compose.material3.BottomSheetDefaults
|
||||||
|
import androidx.compose.material3.BottomSheetScaffold
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.SheetValue
|
||||||
|
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.rotate
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SheetLayout(
|
||||||
|
map: @Composable (PaddingValues) -> Unit,
|
||||||
|
menu: @Composable () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val sheetState = rememberBottomSheetScaffoldState()
|
||||||
|
BottomSheetScaffold(
|
||||||
|
sheetPeekHeight = 180.dp,
|
||||||
|
scaffoldState = sheetState,
|
||||||
|
sheetSwipeEnabled = true,
|
||||||
|
sheetDragHandle = {
|
||||||
|
ExpandCollapseButton(
|
||||||
|
sheetState.bottomSheetState.targetValue == SheetValue.Expanded,
|
||||||
|
onExpand = { sheetState.bottomSheetState.expand() },
|
||||||
|
onCollapse = { sheetState.bottomSheetState.partialExpand() },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
sheetContent = {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier.background(BottomSheetDefaults.ContainerColor)
|
||||||
|
.consumeWindowInsets(PaddingValues(top = 56.dp))
|
||||||
|
.requiredHeight(500.dp)
|
||||||
|
) {
|
||||||
|
menu()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = modifier,
|
||||||
|
) { padding ->
|
||||||
|
map(padding)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ExpandCollapseButton(
|
||||||
|
expanded: Boolean,
|
||||||
|
onExpand: suspend () -> Unit,
|
||||||
|
onCollapse: suspend () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val degrees by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
IconButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = { coroutineScope.launch { if (expanded) onCollapse() else onExpand() } },
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(com.kouros.android.cars.carappservice.R.drawable.keyboard_arrow_up_24px),
|
||||||
|
contentDescription = if (expanded) "Collapse" else "Expand",
|
||||||
|
modifier = Modifier.rotate(degrees),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
|
||||||
import com.kouros.navigation.ui.MainActivity
|
import com.kouros.navigation.ui.MainActivity
|
||||||
|
import com.kouros.navigation.ui.search.SearchScreen
|
||||||
import com.kouros.navigation.ui.settings.SettingsRoute
|
import com.kouros.navigation.ui.settings.SettingsRoute
|
||||||
|
|
||||||
|
|
||||||
@@ -17,5 +19,7 @@ fun AppNavGraph(mainActivity: MainActivity) {
|
|||||||
composable("display_settings") { SettingsRoute("display_settings", navController) { navController.popBackStack() } }
|
composable("display_settings") { SettingsRoute("display_settings", navController) { navController.popBackStack() } }
|
||||||
composable("nav_settings") { SettingsRoute("nav_settings", navController) { navController.popBackStack() } }
|
composable("nav_settings") { SettingsRoute("nav_settings", navController) { navController.popBackStack() } }
|
||||||
composable("settings") { SettingsRoute("settings", navController) { navController.popBackStack() } }
|
composable("settings") { SettingsRoute("settings", navController) { navController.popBackStack() } }
|
||||||
|
composable("search") { SearchScreen(navController, navController.context, navigationViewModel, mainActivity.lastLocation) { navController.popBackStack() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui.navigation
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui.navigation
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -1,33 +1,35 @@
|
|||||||
package com.kouros.navigation.ui
|
package com.kouros.navigation.ui.search
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
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.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.captionBar
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.text.input.rememberTextFieldState
|
import androidx.compose.foundation.text.input.rememberTextFieldState
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
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
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.SearchBar
|
import androidx.compose.material3.SearchBar
|
||||||
import androidx.compose.material3.SearchBarDefaults
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
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.mutableStateOf
|
||||||
@@ -35,119 +37,93 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.PlaceColor
|
import com.kouros.navigation.data.PlaceColor
|
||||||
import com.kouros.navigation.data.nominatim.SearchResult
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
import com.kouros.navigation.ui.theme.NavigationTheme
|
||||||
import com.kouros.navigation.utils.location
|
import com.kouros.navigation.utils.location
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchSheet(
|
fun SearchScreen(
|
||||||
applicationContext: Context,
|
navController: NavHostController,
|
||||||
viewModel: NavigationViewModel,
|
context: Context,
|
||||||
|
navigationViewModel: NavigationViewModel,
|
||||||
location: Location,
|
location: Location,
|
||||||
closeSheet: () -> Unit
|
function: () -> Unit
|
||||||
) {
|
) {
|
||||||
val searchResults = mutableListOf<SearchResult>()
|
|
||||||
val recentPlaces = viewModel.places.observeAsState()
|
|
||||||
val search = viewModel.searchPlaces.observeAsState()
|
|
||||||
if (search.value != null) {
|
|
||||||
searchResults.addAll(search.value!!)
|
|
||||||
}
|
|
||||||
val textFieldState = rememberTextFieldState()
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.wrapContentHeight()
|
|
||||||
) {
|
|
||||||
SearchBar(
|
|
||||||
textFieldState = textFieldState,
|
|
||||||
searchPlaces = emptyList(),
|
|
||||||
searchResults = searchResults,
|
|
||||||
viewModel = viewModel,
|
|
||||||
context = applicationContext,
|
|
||||||
location = location,
|
|
||||||
closeSheet = { closeSheet() }
|
|
||||||
|
|
||||||
|
NavigationTheme(true) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
CenterAlignedTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(id = R.string.search_action_title),
|
||||||
)
|
)
|
||||||
Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
|
},
|
||||||
if (recentPlaces.value != null) {
|
navigationIcon = {
|
||||||
val items = listOf(recentPlaces)
|
IconButton(onClick = function) {
|
||||||
if (items.isNotEmpty()) {
|
|
||||||
RecentPlaces(
|
|
||||||
recentPlaces.value!!,
|
|
||||||
viewModel,
|
|
||||||
applicationContext,
|
|
||||||
location,
|
|
||||||
closeSheet
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Home(
|
|
||||||
applicationContext: Context,
|
|
||||||
viewModel: NavigationViewModel,
|
|
||||||
location: Location,
|
|
||||||
closeSheet: () -> Unit
|
|
||||||
) {
|
|
||||||
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
|
||||||
Button(onClick = {
|
|
||||||
val places = viewModel.loadRecentPlace(applicationContext)
|
|
||||||
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
|
|
||||||
viewModel.loadRoute(applicationContext, location, toLocation, 0F)
|
|
||||||
closeSheet()
|
|
||||||
}) {
|
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.ic_place_white_24dp),
|
painter = painterResource(R.drawable.arrow_back_24px),
|
||||||
"Home",
|
contentDescription = stringResource(id = R.string.accept_action_title),
|
||||||
modifier = Modifier.size(24.dp, 24.dp),
|
modifier = Modifier.size(48.dp, 48.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")
|
},
|
||||||
|
)
|
||||||
|
{ padding ->
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
Column(Modifier.padding(top = 50.dp)) {
|
||||||
|
SearchBar(context, navigationViewModel, location)
|
||||||
|
Categories(context, navigationViewModel, location, closeSheet = { })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun searchPlaces(viewModel: NavigationViewModel, location: Location, it: String) {
|
|
||||||
viewModel.searchPlaces(it, location)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SearchBar(
|
fun SearchBar(
|
||||||
textFieldState: TextFieldState,
|
|
||||||
searchPlaces: List<Place>,
|
|
||||||
searchResults: List<SearchResult>,
|
|
||||||
viewModel: NavigationViewModel,
|
|
||||||
context: Context,
|
context: Context,
|
||||||
|
navigationViewModel: NavigationViewModel,
|
||||||
location: Location,
|
location: Location,
|
||||||
closeSheet: () -> Unit
|
|
||||||
) {
|
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
|
val searchResults = mutableListOf<SearchResult>()
|
||||||
|
val search = navigationViewModel.searchPlaces.observeAsState()
|
||||||
|
if (search.value != null) {
|
||||||
|
searchResults.addAll(search.value!!)
|
||||||
|
}
|
||||||
|
val textFieldState = rememberTextFieldState()
|
||||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
SearchBar(
|
SearchBar(
|
||||||
windowInsets = WindowInsets.captionBar,
|
|
||||||
colors = SearchBarDefaults.colors(
|
colors = SearchBarDefaults.colors(
|
||||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
),
|
),
|
||||||
inputField = {
|
inputField = {
|
||||||
SearchBarDefaults.InputField(
|
SearchBarDefaults.InputField(
|
||||||
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.drawable.search_48px),
|
painter = painterResource(id = R.drawable.search_48px),
|
||||||
@@ -158,24 +134,64 @@ fun SearchBar(
|
|||||||
query = textFieldState.text.toString(),
|
query = textFieldState.text.toString(),
|
||||||
onQueryChange = { textFieldState.edit { replace(0, length, it) } },
|
onQueryChange = { textFieldState.edit { replace(0, length, it) } },
|
||||||
onSearch = {
|
onSearch = {
|
||||||
searchPlaces(viewModel, location, it)
|
navigationViewModel.searchPlaces(it, location)
|
||||||
expanded = false
|
expanded = true
|
||||||
},
|
},
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onExpandedChange = { expanded = it },
|
onExpandedChange = {
|
||||||
|
//expanded = it
|
||||||
|
},
|
||||||
placeholder = { Text(context.getString(R.string.search_action_title)) }
|
placeholder = { Text(context.getString(R.string.search_action_title)) }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expanded = expanded,
|
expanded = expanded,
|
||||||
onExpandedChange = { },
|
onExpandedChange = { },
|
||||||
) {
|
) {
|
||||||
if (searchPlaces.isNotEmpty()) {
|
|
||||||
Text(context.getString(R.string.recent_destinations))
|
|
||||||
RecentPlaces(searchPlaces, viewModel, context, location, closeSheet)
|
|
||||||
}
|
|
||||||
if (searchResults.isNotEmpty()) {
|
if (searchResults.isNotEmpty()) {
|
||||||
Text("Search places")
|
SearchPlaces(searchResults, navigationViewModel, context, location, { })
|
||||||
SearchPlaces( searchResults, viewModel, context, location, closeSheet)
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Categories(
|
||||||
|
applicationContext: Context,
|
||||||
|
viewModel: NavigationViewModel,
|
||||||
|
location: Location,
|
||||||
|
closeSheet: () -> Unit
|
||||||
|
) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.SpaceAround,
|
||||||
|
modifier = Modifier.horizontalScroll(scrollState)
|
||||||
|
) {
|
||||||
|
Button(onClick = {
|
||||||
|
val places = viewModel.loadRecentPlace(applicationContext)
|
||||||
|
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
|
||||||
|
viewModel.loadRoute(applicationContext, location, toLocation, 0F)
|
||||||
|
closeSheet()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.local_gas_station_24),
|
||||||
|
applicationContext.getString(R.string.fuel_station),
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(onClick = {
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ev_station_24px),
|
||||||
|
"Work",
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(onClick = {
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.local_pharmacy_24px),
|
||||||
|
"Work",
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +209,6 @@ private fun SearchPlaces(
|
|||||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
) {
|
) {
|
||||||
if (searchResults.isNotEmpty()) {
|
|
||||||
items(searchResults, key = { it.placeId }) { place ->
|
items(searchResults, key = { it.placeId }) { place ->
|
||||||
Row {
|
Row {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -214,7 +229,7 @@ private fun SearchPlaces(
|
|||||||
city = place.address.city,
|
city = place.address.city,
|
||||||
street = place.address.road
|
street = place.address.road
|
||||||
)
|
)
|
||||||
viewModel.saveRecent(context,pl)
|
viewModel.saveRecent(context, pl)
|
||||||
val toLocation =
|
val toLocation =
|
||||||
location(place.lon.toDouble(), place.lat.toDouble())
|
location(place.lon.toDouble(), place.lat.toDouble())
|
||||||
viewModel.loadRoute(context, location, toLocation, 0F)
|
viewModel.loadRoute(context, location, toLocation, 0F)
|
||||||
@@ -226,42 +241,6 @@ private fun SearchPlaces(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RecentPlaces(
|
|
||||||
recentPlaces: List<Place>,
|
|
||||||
viewModel: NavigationViewModel,
|
|
||||||
context: Context,
|
|
||||||
location: Location,
|
|
||||||
closeSheet: () -> Unit
|
|
||||||
) {
|
|
||||||
val color = remember { PlaceColor }
|
|
||||||
LazyColumn(
|
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
) {
|
|
||||||
items(recentPlaces, key = { it.id }) { place ->
|
|
||||||
Row {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_place_white_24dp),
|
|
||||||
"Navigation",
|
|
||||||
tint = color.copy(alpha = 1f),
|
|
||||||
modifier = Modifier.size(24.dp, 24.dp),
|
|
||||||
)
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text("${place.street} ${place.postalCode} ${place.city}") },
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable {
|
|
||||||
val toLocation = location(place.longitude, place.latitude)
|
|
||||||
viewModel.loadRoute(context, location, toLocation, 0F)
|
|
||||||
closeSheet()
|
|
||||||
}
|
|
||||||
.fillMaxWidth()
|
|
||||||
)
|
|
||||||
HorizontalDivider(color = Color.Gray)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
180
app/src/main/java/com/kouros/navigation/ui/search/SearchSheet.kt
Normal file
180
app/src/main/java/com/kouros/navigation/ui/search/SearchSheet.kt
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package com.kouros.navigation.ui.search
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.location.Location
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SearchBar
|
||||||
|
import androidx.compose.material3.SearchBarDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import com.kouros.data.R
|
||||||
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.PlaceColor
|
||||||
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun SearchSheet(
|
||||||
|
applicationContext: Context,
|
||||||
|
navController: NavHostController,
|
||||||
|
viewModel: NavigationViewModel,
|
||||||
|
location: Location,
|
||||||
|
closeSheet: () -> Unit
|
||||||
|
) {
|
||||||
|
val recentPlaces = viewModel.recentPlaces.observeAsState()
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
) {
|
||||||
|
SearchBar(
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
navController.navigate("search")
|
||||||
|
},
|
||||||
|
colors = SearchBarDefaults.colors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||||
|
),
|
||||||
|
expanded = false,
|
||||||
|
onExpandedChange = { navController.navigate("search") },
|
||||||
|
inputField = {
|
||||||
|
SearchBarDefaults.InputField(
|
||||||
|
enabled = false,
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
navController.navigate("search")
|
||||||
|
},
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.search_48px),
|
||||||
|
applicationContext.getString(R.string.search_action_title),
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
query = applicationContext.getString(R.string.search_action_title),
|
||||||
|
onQueryChange = { },
|
||||||
|
onSearch = {
|
||||||
|
|
||||||
|
},
|
||||||
|
expanded = false,
|
||||||
|
onExpandedChange = { },
|
||||||
|
placeholder = { applicationContext.getString(R.string.search_action_title) }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
|
||||||
|
if (recentPlaces.value != null) {
|
||||||
|
val items = listOf(recentPlaces)
|
||||||
|
if (items.isNotEmpty()) {
|
||||||
|
Column(Modifier.padding(all = 10.dp)) {
|
||||||
|
Text(applicationContext.getString(R.string.recent_destinations))
|
||||||
|
RecentPlaces(
|
||||||
|
recentPlaces.value!!,
|
||||||
|
viewModel,
|
||||||
|
applicationContext,
|
||||||
|
location,
|
||||||
|
closeSheet
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Home(
|
||||||
|
applicationContext: Context,
|
||||||
|
viewModel: NavigationViewModel,
|
||||||
|
location: Location,
|
||||||
|
closeSheet: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
||||||
|
Button(onClick = {
|
||||||
|
val places = viewModel.loadRecentPlace(applicationContext)
|
||||||
|
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
|
||||||
|
viewModel.loadRoute(applicationContext, location, toLocation, 0F)
|
||||||
|
closeSheet()
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_place_white_24dp),
|
||||||
|
"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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RecentPlaces(
|
||||||
|
recentPlaces: List<Place>,
|
||||||
|
viewModel: NavigationViewModel,
|
||||||
|
context: Context,
|
||||||
|
location: Location,
|
||||||
|
closeSheet: () -> Unit
|
||||||
|
) {
|
||||||
|
val color = remember { PlaceColor }
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
items(recentPlaces, key = { it.id }) { place ->
|
||||||
|
Row {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_place_white_24dp),
|
||||||
|
"Navigation",
|
||||||
|
tint = color.copy(alpha = 1f),
|
||||||
|
modifier = Modifier.size(24.dp, 24.dp),
|
||||||
|
)
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text("${place.street} ${place.postalCode} ${place.city}") },
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
val toLocation = location(place.longitude, place.latitude)
|
||||||
|
viewModel.loadRoute(context, location, toLocation, 0F)
|
||||||
|
closeSheet()
|
||||||
|
}
|
||||||
|
.fillMaxWidth()
|
||||||
|
)
|
||||||
|
HorizontalDivider(color = Color.Gray)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package com.kouros.navigation.ui.theme
|
|||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val md_theme_light_primary = Color(0xFF825500)
|
val md_theme_light_primary = Color(0xFF6FE5E1)
|
||||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||||
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
|
val md_theme_light_primaryContainer = Color(0xFFFFDDB3)
|
||||||
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
|
val md_theme_light_onPrimaryContainer = Color(0xFF291800)
|
||||||
@@ -33,7 +33,7 @@ val md_theme_light_surfaceTint = Color(0xFF825500)
|
|||||||
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
|
val md_theme_light_outlineVariant = Color(0xFFD3C4B4)
|
||||||
val md_theme_light_scrim = Color(0xFF000000)
|
val md_theme_light_scrim = Color(0xFF000000)
|
||||||
|
|
||||||
val md_theme_dark_primary = Color(0xFFFFB951)
|
val md_theme_dark_primary = Color(0xFF8CD6EF)
|
||||||
val md_theme_dark_onPrimary = Color(0xFF452B00)
|
val md_theme_dark_onPrimary = Color(0xFF452B00)
|
||||||
val md_theme_dark_primaryContainer = Color(0xFF633F00)
|
val md_theme_dark_primaryContainer = Color(0xFF633F00)
|
||||||
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
|
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ fun NavigationTheme(
|
|||||||
}
|
}
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = if (useDarkTheme) darkColorScheme() else colorScheme,
|
colorScheme = if (useDarkTheme) DarkColors else colorScheme,
|
||||||
typography = typography,
|
typography = typography,
|
||||||
content = content,
|
content = content,
|
||||||
shapes = shapes,
|
shapes = shapes,
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import com.kouros.navigation.data.Constants.TILT
|
|||||||
import com.kouros.navigation.data.Constants.homeVogelhart
|
import com.kouros.navigation.data.Constants.homeVogelhart
|
||||||
import com.kouros.navigation.data.RouteEngine
|
import com.kouros.navigation.data.RouteEngine
|
||||||
import com.kouros.navigation.model.BaseStyleModel
|
import com.kouros.navigation.model.BaseStyleModel
|
||||||
import com.kouros.navigation.model.RouteModel
|
|
||||||
import com.kouros.navigation.utils.bearing
|
import com.kouros.navigation.utils.bearing
|
||||||
import com.kouros.navigation.utils.calculateTilt
|
import com.kouros.navigation.utils.calculateTilt
|
||||||
import com.kouros.navigation.utils.calculateZoom
|
import com.kouros.navigation.utils.calculateZoom
|
||||||
@@ -46,6 +45,7 @@ import kotlinx.coroutines.flow.first
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
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.expressions.dsl.zoom
|
||||||
import org.maplibre.compose.style.BaseStyle
|
import org.maplibre.compose.style.BaseStyle
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
|
|
||||||
@@ -261,6 +261,7 @@ class SurfaceRenderer(
|
|||||||
val paddingValues = getPaddingValues(height, viewStyle)
|
val paddingValues = getPaddingValues(height, viewStyle)
|
||||||
val cameraState = cameraState(paddingValues, position, tilt)
|
val cameraState = cameraState(paddingValues, position, tilt)
|
||||||
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
val baseStyle = BaseStyleModel().readStyle(carContext, darkMode, carContext.isDarkMode)
|
||||||
|
val dark = darkMode == 1 || darkMode == 2 && carContext.isDarkMode
|
||||||
|
|
||||||
MapLibre(
|
MapLibre(
|
||||||
cameraState,
|
cameraState,
|
||||||
@@ -271,7 +272,7 @@ class SurfaceRenderer(
|
|||||||
speedCameras,
|
speedCameras,
|
||||||
showBuildings
|
showBuildings
|
||||||
)
|
)
|
||||||
ShowPosition(cameraState, position, paddingValues, darkMode)
|
ShowPosition(cameraState, position, paddingValues, dark)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -283,18 +284,18 @@ class SurfaceRenderer(
|
|||||||
cameraState: CameraState,
|
cameraState: CameraState,
|
||||||
position: CameraPosition?,
|
position: CameraPosition?,
|
||||||
paddingValues: PaddingValues,
|
paddingValues: PaddingValues,
|
||||||
darkMode: Int
|
darkMode: Boolean
|
||||||
) {
|
) {
|
||||||
val cameraDuration =
|
val cameraDuration =
|
||||||
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
duration(viewStyle == ViewStyle.PREVIEW, position!!.bearing, lastBearing)
|
||||||
val currentSpeed: Float? by speed.observeAsState()
|
val currentSpeed: Float? by speed.observeAsState()
|
||||||
val speed: Int? by maxSpeed.observeAsState()
|
val maximumSpeed: Int? by maxSpeed.observeAsState()
|
||||||
val streetName: String? by street.observeAsState()
|
val streetName: String? by street.observeAsState()
|
||||||
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
if (viewStyle == ViewStyle.VIEW || viewStyle == ViewStyle.PAN_VIEW) {
|
||||||
DrawNavigationImages(
|
DrawNavigationImages(
|
||||||
paddingValues,
|
paddingValues,
|
||||||
currentSpeed,
|
currentSpeed,
|
||||||
speed!!,
|
maximumSpeed!!,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
streetName,
|
streetName,
|
||||||
@@ -340,7 +341,7 @@ class SurfaceRenderer(
|
|||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
cameraPosition.value!!.bearing,
|
cameraPosition.value!!.bearing,
|
||||||
newZoom,
|
newZoom,
|
||||||
cameraPosition.value!!.target,
|
cameraPosition.value!!.target, tilt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +380,7 @@ class SurfaceRenderer(
|
|||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
bearing,
|
bearing,
|
||||||
zoom,
|
zoom,
|
||||||
Position(location.longitude, location.latitude)
|
Position(location.longitude, location.latitude), tilt
|
||||||
)
|
)
|
||||||
lastBearing = cameraPosition.value!!.bearing
|
lastBearing = cameraPosition.value!!.bearing
|
||||||
lastLocation = location
|
lastLocation = location
|
||||||
@@ -387,11 +388,19 @@ class SurfaceRenderer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets route data for active navigation and switches to VIEW mode.
|
||||||
|
*/
|
||||||
|
fun setRouteData() {
|
||||||
|
routeData.value = routeModel.curRoute.routeGeoJson
|
||||||
|
viewStyle = ViewStyle.VIEW
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates camera position with new bearing, zoom, and target.
|
* Updates camera position with new bearing, zoom, and target.
|
||||||
* Posts update to LiveData for UI observation.
|
* Posts update to LiveData for UI observation.
|
||||||
*/
|
*/
|
||||||
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position) {
|
private fun updateCameraPosition(bearing: Double, zoom: Double, target: Position, tilt: Double) {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
cameraPosition.postValue(
|
cameraPosition.postValue(
|
||||||
cameraPosition.value!!.copy(
|
cameraPosition.value!!.copy(
|
||||||
@@ -408,9 +417,15 @@ class SurfaceRenderer(
|
|||||||
/**
|
/**
|
||||||
* Sets route data for active navigation and switches to VIEW mode.
|
* Sets route data for active navigation and switches to VIEW mode.
|
||||||
*/
|
*/
|
||||||
fun setRouteData() {
|
fun clearRouteData() {
|
||||||
routeData.value = routeModel.curRoute.routeGeoJson
|
routeData.value = ""
|
||||||
viewStyle = ViewStyle.VIEW
|
viewStyle = ViewStyle.VIEW
|
||||||
|
cameraPosition.postValue(
|
||||||
|
cameraPosition.value!!.copy(
|
||||||
|
zoom = 16.0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tilt = TILT
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -424,18 +439,21 @@ class SurfaceRenderer(
|
|||||||
* Sets up route preview mode with overview camera position.
|
* Sets up route preview mode with overview camera position.
|
||||||
* Calculates appropriate zoom based on route distance.
|
* Calculates appropriate zoom based on route distance.
|
||||||
*/
|
*/
|
||||||
fun setPreviewRouteData(routeModel: RouteModel) {
|
fun setPreviewRouteData(routeModel: RouteCarModel) {
|
||||||
viewStyle = ViewStyle.PREVIEW
|
viewStyle = ViewStyle.PREVIEW
|
||||||
with(routeModel) {
|
with(routeModel) {
|
||||||
routeData.value = curRoute.routeGeoJson
|
routeData.value = curRoute.routeGeoJson
|
||||||
centerLocation = curRoute.centerLocation
|
centerLocation = curRoute.centerLocation
|
||||||
previewDistance = curRoute.summary.distance
|
previewDistance = curRoute.summary.distance
|
||||||
}
|
}
|
||||||
|
tilt = 0.0
|
||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
0.0,
|
0.0,
|
||||||
previewZoom(previewDistance),
|
previewZoom(centerLocation, previewDistance),
|
||||||
Position(centerLocation.longitude, centerLocation.latitude)
|
Position(centerLocation.longitude, centerLocation.latitude), 0.0
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -448,7 +466,7 @@ class SurfaceRenderer(
|
|||||||
updateCameraPosition(
|
updateCameraPosition(
|
||||||
0.0,
|
0.0,
|
||||||
14.0,
|
14.0,
|
||||||
target = Position(location.longitude, location.latitude)
|
target = Position(location.longitude, location.latitude), tilt
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||||||
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.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.text.BasicText
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -19,6 +20,7 @@ import androidx.compose.ui.geometry.Offset
|
|||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.drawscope.scale
|
import androidx.compose.ui.graphics.drawscope.scale
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.drawText
|
import androidx.compose.ui.text.drawText
|
||||||
@@ -33,6 +35,8 @@ import com.kouros.navigation.data.Constants
|
|||||||
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.isMetricSystem
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
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
|
||||||
@@ -59,6 +63,7 @@ import org.maplibre.compose.sources.Source
|
|||||||
import org.maplibre.compose.sources.getBaseSource
|
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.compose.style.BaseStyle
|
||||||
|
import org.maplibre.spatialk.geojson.BoundingBox
|
||||||
import org.maplibre.spatialk.geojson.Position
|
import org.maplibre.spatialk.geojson.Position
|
||||||
|
|
||||||
|
|
||||||
@@ -296,7 +301,7 @@ fun DrawNavigationImages(
|
|||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
streetName: String?,
|
streetName: String?,
|
||||||
darkMode: Int,
|
darkMode: Boolean,
|
||||||
) {
|
) {
|
||||||
NavigationImage(padding, width, height, streetName, darkMode)
|
NavigationImage(padding, width, height, streetName, darkMode)
|
||||||
if (speed != null) {
|
if (speed != null) {
|
||||||
@@ -314,7 +319,7 @@ fun NavigationImage(
|
|||||||
width: Int,
|
width: Int,
|
||||||
height: Int,
|
height: Int,
|
||||||
streetName: String?,
|
streetName: String?,
|
||||||
darkMode: Int
|
darkMode: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val imageSize = (height / 8)
|
val imageSize = (height / 8)
|
||||||
@@ -323,9 +328,9 @@ fun NavigationImage(
|
|||||||
val textMeasurerStreet = rememberTextMeasurer()
|
val textMeasurerStreet = rememberTextMeasurer()
|
||||||
val street = streetName.toString()
|
val street = streetName.toString()
|
||||||
val styleStreet = TextStyle(
|
val styleStreet = TextStyle(
|
||||||
fontSize = 14.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = if (darkMode == 1) Color.White else navigationColor,
|
color = if (darkMode) Color.White else navigationColor,
|
||||||
)
|
)
|
||||||
val textLayoutStreet = remember(street) {
|
val textLayoutStreet = remember(street) {
|
||||||
textMeasurerStreet.measure(street, styleStreet, overflow = TextOverflow.Ellipsis)
|
textMeasurerStreet.measure(street, styleStreet, overflow = TextOverflow.Ellipsis)
|
||||||
@@ -348,28 +353,31 @@ fun NavigationImage(
|
|||||||
.size(imageSize.dp, imageSize.dp)
|
.size(imageSize.dp, imageSize.dp)
|
||||||
.scale(scaleX = 1f, scaleY = 0.7f),
|
.scale(scaleX = 1f, scaleY = 0.7f),
|
||||||
)
|
)
|
||||||
|
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size((width / 5).dp, (height/4).dp)
|
.size(textLayoutStreet.size.width.dp, textLayoutStreet.size.height.dp * 6 )
|
||||||
) {
|
) {
|
||||||
if (!streetName.isNullOrEmpty()) {
|
if (street.isNotEmpty()) {
|
||||||
|
val topLeftX = center.x - textLayoutStreet.size.width / 2
|
||||||
|
val topLeftY = center.y + textLayoutStreet.size.height
|
||||||
drawRoundRect(
|
drawRoundRect(
|
||||||
topLeft = Offset(
|
topLeft = Offset(
|
||||||
x = center.x - textLayoutStreet.size.width / 2 ,
|
x = topLeftX ,
|
||||||
y = center.y + textLayoutStreet.size.height,
|
y = topLeftY,
|
||||||
),
|
),
|
||||||
color = if (darkMode == 1) navigationColor else Color.White,
|
color = if (darkMode) navigationColor else Color.White,
|
||||||
cornerRadius = CornerRadius(x = 10f, y = 10f),
|
cornerRadius = CornerRadius(x = 10f, y = 10f),
|
||||||
)
|
)
|
||||||
drawText(
|
drawText(
|
||||||
textMeasurer = textMeasurerStreet,
|
textMeasurer = textMeasurerStreet,
|
||||||
text = streetName,
|
text = street,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
style = styleStreet,
|
style = styleStreet,
|
||||||
topLeft = Offset(
|
topLeft = Offset(
|
||||||
x = center.x - textLayoutStreet.size.width / 2,
|
x = topLeftX,
|
||||||
y = center.y + textLayoutStreet.size.height + 10,
|
y = topLeftY + 10,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -384,6 +392,7 @@ private fun CurrentSpeed(
|
|||||||
curSpeed: Float,
|
curSpeed: Float,
|
||||||
maxSpeed: Int
|
maxSpeed: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val radius = 34
|
val radius = 34
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -395,9 +404,11 @@ private fun CurrentSpeed(
|
|||||||
) {
|
) {
|
||||||
val textMeasurerSpeed = rememberTextMeasurer()
|
val textMeasurerSpeed = rememberTextMeasurer()
|
||||||
val textMeasurerKm = rememberTextMeasurer()
|
val textMeasurerKm = rememberTextMeasurer()
|
||||||
val speed = (curSpeed * 3.6).toInt().toString()
|
|
||||||
|
|
||||||
val kmh = "km/h"
|
val speed = if (isMetricSystem()) (curSpeed * 3.6).toInt().toString() else (curSpeed * 3.6 * 0.6214).toInt().toString()
|
||||||
|
|
||||||
|
val kmh = if (isMetricSystem()) "km/h" else "mph"
|
||||||
|
|
||||||
val styleSpeed = TextStyle(
|
val styleSpeed = TextStyle(
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
@@ -433,7 +444,7 @@ private fun CurrentSpeed(
|
|||||||
)
|
)
|
||||||
drawText(
|
drawText(
|
||||||
textMeasurer = textMeasurerKm,
|
textMeasurer = textMeasurerKm,
|
||||||
text = "km/h",
|
text = kmh,
|
||||||
style = styleKm,
|
style = styleKm,
|
||||||
topLeft = Offset(
|
topLeft = Offset(
|
||||||
x = center.x - textLayoutKm.size.width / 2,
|
x = center.x - textLayoutKm.size.width / 2,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class Simulation {
|
|||||||
lifecycleScope: LifecycleCoroutineScope,
|
lifecycleScope: LifecycleCoroutineScope,
|
||||||
updateLocation: (Location) -> Unit
|
updateLocation: (Location) -> Unit
|
||||||
) {
|
) {
|
||||||
|
if (routeModel.navState.route.isRouteValid()) {
|
||||||
val points = routeModel.curRoute.waypoints
|
val points = routeModel.curRoute.waypoints
|
||||||
if (points.isEmpty()) return
|
if (points.isEmpty()) return
|
||||||
simulationJob?.cancel()
|
simulationJob?.cancel()
|
||||||
@@ -36,13 +37,14 @@ class Simulation {
|
|||||||
curBearing = lastLocation.bearingTo(fakeLocation)
|
curBearing = lastLocation.bearingTo(fakeLocation)
|
||||||
// Update your app's state as if a real GPS update occurred
|
// Update your app's state as if a real GPS update occurred
|
||||||
updateLocation(fakeLocation)
|
updateLocation(fakeLocation)
|
||||||
// Wait before moving to the next point (e.g., every 2 seconds)
|
// Wait before moving to the next point (e.g., every 1 second)
|
||||||
delay(1000)
|
delay(1000)
|
||||||
lastLocation = fakeLocation
|
lastLocation = fakeLocation
|
||||||
}
|
}
|
||||||
routeModel.stopNavigation()
|
routeModel.stopNavigation()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun stopSimulation() {
|
fun stopSimulation() {
|
||||||
simulationJob?.cancel()
|
simulationJob?.cancel()
|
||||||
|
|||||||
@@ -116,7 +116,8 @@ class CategoryScreen(
|
|||||||
} else {
|
} else {
|
||||||
row.addText("${(it.distance / 1000).round(1)} km")
|
row.addText("${(it.distance / 1000).round(1)} km")
|
||||||
}
|
}
|
||||||
if (category == Constants.CHARGING_STATION) {
|
if (category == CHARGING_STATION) {
|
||||||
|
if (it.tags.socketType2 != null)
|
||||||
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
|
row.addText("${it.tags.socketType2} X Typ 2 ${it.tags.socketType2Output}")
|
||||||
} else {
|
} else {
|
||||||
row.addText(carText("${it.tags.openingHours}"))
|
row.addText(carText("${it.tags.openingHours}"))
|
||||||
@@ -134,7 +135,7 @@ class CategoryScreen(
|
|||||||
setResult(
|
setResult(
|
||||||
Place(
|
Place(
|
||||||
name = name,
|
name = name,
|
||||||
category = Constants.CHARGING_STATION,
|
category = CHARGING_STATION,
|
||||||
latitude = it.lat,
|
latitude = it.lat,
|
||||||
longitude = it.lon
|
longitude = it.lon
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -377,7 +377,6 @@ class NavigationScreen(
|
|||||||
*/
|
*/
|
||||||
private fun stopAction(): Action {
|
private fun stopAction(): Action {
|
||||||
return Action.Builder()
|
return Action.Builder()
|
||||||
.setTitle(carContext.getString(R.string.stop_action_title))
|
|
||||||
.setIcon(
|
.setIcon(
|
||||||
CarIcon.Builder(
|
CarIcon.Builder(
|
||||||
IconCompat.createWithResource(
|
IconCompat.createWithResource(
|
||||||
@@ -531,6 +530,8 @@ class NavigationScreen(
|
|||||||
* Pushes the search screen and handles the search result.
|
* Pushes the search screen and handles the search result.
|
||||||
*/
|
*/
|
||||||
private fun startSearchScreen() {
|
private fun startSearchScreen() {
|
||||||
|
navigationViewModel.recentPlaces.value = emptyList()
|
||||||
|
navigationViewModel.previewRoute.value = ""
|
||||||
screenManager
|
screenManager
|
||||||
.pushForResult(
|
.pushForResult(
|
||||||
SearchScreen(
|
SearchScreen(
|
||||||
@@ -558,16 +559,22 @@ class NavigationScreen(
|
|||||||
* Loads a route to the specified place and sets it as the destination.
|
* Loads a route to the specified place and sets it as the destination.
|
||||||
*/
|
*/
|
||||||
fun navigateToPlace(place: Place) {
|
fun navigateToPlace(place: Place) {
|
||||||
|
val preview = navigationViewModel.previewRoute.value
|
||||||
navigationType = NavigationType.VIEW
|
navigationType = NavigationType.VIEW
|
||||||
val location = location(place.longitude, place.latitude)
|
val location = location(place.longitude, place.latitude)
|
||||||
navigationViewModel.saveRecent(carContext, place)
|
navigationViewModel.saveRecent(carContext, place)
|
||||||
currentNavigationLocation = location
|
currentNavigationLocation = location
|
||||||
|
if (preview.isNullOrEmpty()) {
|
||||||
navigationViewModel.loadRoute(
|
navigationViewModel.loadRoute(
|
||||||
carContext,
|
carContext,
|
||||||
surfaceRenderer.lastLocation,
|
surfaceRenderer.lastLocation,
|
||||||
location,
|
location,
|
||||||
surfaceRenderer.carOrientation
|
surfaceRenderer.carOrientation
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
routeModel.navState = routeModel.navState.copy(currentRouteIndex = place.routeIndex)
|
||||||
|
navigationViewModel.route.value = preview
|
||||||
|
}
|
||||||
routeModel.navState = routeModel.navState.copy(destination = place)
|
routeModel.navState = routeModel.navState.copy(destination = place)
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import androidx.car.app.model.Row
|
|||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.asLiveData
|
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
@@ -27,58 +26,52 @@ import com.kouros.navigation.data.Constants.RECENT
|
|||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.utils.getSettingsRepository
|
||||||
|
import com.kouros.navigation.utils.location
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
|
||||||
class PlaceListScreen(
|
class PlaceListScreen(
|
||||||
private val carContext: CarContext,
|
private val carContext: CarContext,
|
||||||
private val surfaceRenderer: SurfaceRenderer,
|
private val surfaceRenderer: SurfaceRenderer,
|
||||||
private val category: String,
|
private val category: String,
|
||||||
private val navigationViewModel: NavigationViewModel
|
private val navigationViewModel: NavigationViewModel,
|
||||||
|
private val places: List<Place>
|
||||||
) : Screen(carContext) {
|
) : Screen(carContext) {
|
||||||
|
|
||||||
var places = listOf<Place>()
|
|
||||||
|
|
||||||
val observer = Observer<List<Place>> { newPlaces ->
|
val routeModel = RouteCarModel()
|
||||||
places = newPlaces
|
|
||||||
invalidate()
|
var place = Place()
|
||||||
|
|
||||||
|
val previewObserver = Observer<String> { route ->
|
||||||
|
if (route.isNotEmpty()) {
|
||||||
|
val repository = getSettingsRepository(carContext)
|
||||||
|
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
||||||
|
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
||||||
|
routeModel.startNavigation(route)
|
||||||
|
surfaceRenderer.setPreviewRouteData(routeModel)
|
||||||
|
screenManager
|
||||||
|
.pushForResult(
|
||||||
|
RoutePreviewScreen(
|
||||||
|
carContext,
|
||||||
|
surfaceRenderer,
|
||||||
|
place,
|
||||||
|
navigationViewModel,
|
||||||
|
routeModel = routeModel
|
||||||
|
)
|
||||||
|
) { obj: Any? ->
|
||||||
|
if (obj != null) {
|
||||||
|
setResult(obj)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val observerAddress = Observer<List<Place>> { newContacts ->
|
|
||||||
places = newContacts
|
|
||||||
invalidate()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (category == RECENT) {
|
|
||||||
navigationViewModel.places.observe(this, observer)
|
|
||||||
}
|
|
||||||
if (category == CONTACTS) {
|
|
||||||
navigationViewModel.contactAddress.observe(this, observerAddress)
|
|
||||||
}
|
|
||||||
if (category == FAVORITES) {
|
|
||||||
navigationViewModel.favorites.observe(this, observer)
|
|
||||||
}
|
|
||||||
loadPlaces()
|
loadPlaces()
|
||||||
}
|
navigationViewModel.previewRoute.observe(this, previewObserver)
|
||||||
|
|
||||||
fun loadPlaces() {
|
|
||||||
if (category == RECENT) {
|
|
||||||
navigationViewModel.loadRecentPlaces(
|
|
||||||
carContext,
|
|
||||||
surfaceRenderer.lastLocation,
|
|
||||||
surfaceRenderer.carOrientation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (category == CONTACTS) {
|
|
||||||
navigationViewModel.loadContacts(carContext)
|
|
||||||
}
|
|
||||||
if (category == FAVORITES) {
|
|
||||||
navigationViewModel.loadFavorites(
|
|
||||||
carContext,
|
|
||||||
surfaceRenderer.lastLocation,
|
|
||||||
surfaceRenderer.carOrientation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
@@ -94,7 +87,7 @@ class PlaceListScreen(
|
|||||||
// .setImage(contactIcon(it.avatar, it.category))
|
// .setImage(contactIcon(it.avatar, it.category))
|
||||||
.setTitle("$street ${it.city}")
|
.setTitle("$street ${it.city}")
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
val place = Place(
|
place = Place(
|
||||||
0,
|
0,
|
||||||
it.name,
|
it.name,
|
||||||
it.category,
|
it.category,
|
||||||
@@ -105,27 +98,20 @@ class PlaceListScreen(
|
|||||||
it.street,
|
it.street,
|
||||||
// avatar = null
|
// avatar = null
|
||||||
)
|
)
|
||||||
screenManager
|
val location = location(place.longitude, place.latitude)
|
||||||
.pushForResult(
|
navigationViewModel.loadPreviewRoute(
|
||||||
RoutePreviewScreen(
|
|
||||||
carContext,
|
carContext,
|
||||||
surfaceRenderer,
|
surfaceRenderer.lastLocation,
|
||||||
place,
|
location,
|
||||||
navigationViewModel
|
surfaceRenderer.carOrientation
|
||||||
)
|
)
|
||||||
) { obj: Any? ->
|
|
||||||
if (obj != null) {
|
|
||||||
setResult(obj)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (category != CONTACTS) {
|
if (category != CONTACTS) {
|
||||||
row.addText(SpannableString(" ").apply {
|
row.addText(SpannableString(" ").apply {
|
||||||
setSpan(
|
setSpan(
|
||||||
DistanceSpan.create(
|
DistanceSpan.create(
|
||||||
Distance.create(
|
Distance.create(
|
||||||
(it.distance/1000).toDouble(),
|
(it.distance / 1000).toDouble(),
|
||||||
Distance.UNIT_KILOMETERS
|
Distance.UNIT_KILOMETERS
|
||||||
)
|
)
|
||||||
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
|
), 0, 1, Spannable.SPAN_INCLUSIVE_INCLUSIVE
|
||||||
@@ -184,4 +170,10 @@ class PlaceListScreen(
|
|||||||
}
|
}
|
||||||
return CarIcon.Builder(IconCompat.createWithContentUri(avatar)).build()
|
return CarIcon.Builder(IconCompat.createWithContentUri(avatar)).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadPlaces() {
|
||||||
|
if (category == CONTACTS) {
|
||||||
|
navigationViewModel.loadContacts(carContext)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package com.kouros.navigation.car.screen
|
|||||||
|
|
||||||
import android.os.CountDownTimer
|
import android.os.CountDownTimer
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.car.app.CarContext
|
import androidx.car.app.CarContext
|
||||||
import androidx.car.app.CarToast
|
import androidx.car.app.CarToast
|
||||||
import androidx.car.app.Screen
|
import androidx.car.app.Screen
|
||||||
|
import androidx.car.app.constraints.ConstraintManager
|
||||||
import androidx.car.app.model.Action
|
import androidx.car.app.model.Action
|
||||||
import androidx.car.app.model.Action.FLAG_DEFAULT
|
import androidx.car.app.model.Action.FLAG_DEFAULT
|
||||||
import androidx.car.app.model.ActionStrip
|
import androidx.car.app.model.ActionStrip
|
||||||
@@ -20,88 +22,79 @@ import androidx.car.app.model.Row
|
|||||||
import androidx.car.app.model.Template
|
import androidx.car.app.model.Template
|
||||||
import androidx.car.app.navigation.model.MapController
|
import androidx.car.app.navigation.model.MapController
|
||||||
import androidx.car.app.navigation.model.MapWithContentTemplate
|
import androidx.car.app.navigation.model.MapWithContentTemplate
|
||||||
|
import androidx.car.app.versioning.CarAppApiLevels
|
||||||
import androidx.core.graphics.drawable.IconCompat
|
import androidx.core.graphics.drawable.IconCompat
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Lifecycle
|
||||||
import com.kouros.data.R
|
import com.kouros.data.R
|
||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.navigation.NavigationUtils
|
import com.kouros.navigation.car.navigation.NavigationUtils
|
||||||
import com.kouros.navigation.car.navigation.RouteCarModel
|
import com.kouros.navigation.car.navigation.RouteCarModel
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
|
import com.kouros.navigation.data.route.Routes
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
import com.kouros.navigation.utils.getSettingsRepository
|
import com.kouros.navigation.model.RouteModel
|
||||||
import com.kouros.navigation.utils.location
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
|
/** Creates a screen using the new [androidx.car.app.navigation.model.MapWithContentTemplate] */
|
||||||
class RoutePreviewScreen(
|
class RoutePreviewScreen(
|
||||||
carContext: CarContext,
|
carContext: CarContext,
|
||||||
private var surfaceRenderer: SurfaceRenderer,
|
private var surfaceRenderer: SurfaceRenderer,
|
||||||
private var destination: Place,
|
private var destination: Place,
|
||||||
private val navigationViewModel: NavigationViewModel
|
private val navigationViewModel: NavigationViewModel,
|
||||||
|
private val routeModel: RouteCarModel
|
||||||
) :
|
) :
|
||||||
Screen(carContext) {
|
Screen(carContext) {
|
||||||
private var isFavorite = false
|
private var isFavorite = false
|
||||||
|
|
||||||
val routeModel = RouteCarModel()
|
val maxListItems: Int = 3
|
||||||
|
|
||||||
val navigationUtils = NavigationUtils(carContext)
|
val navigationUtils = NavigationUtils(carContext)
|
||||||
val observer = Observer<String> { route ->
|
|
||||||
if (route.isNotEmpty()) {
|
private val backPressedCallback = object : OnBackPressedCallback(false) {
|
||||||
val repository = getSettingsRepository(carContext)
|
override fun handleOnBackPressed() {
|
||||||
val routingEngine = runBlocking { repository.routingEngineFlow.first() }
|
|
||||||
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
|
|
||||||
routeModel.startNavigation(route)
|
|
||||||
surfaceRenderer.setPreviewRouteData(routeModel)
|
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
navigationViewModel.previewRoute.observe(this, observer)
|
carContext.onBackPressedDispatcher.addCallback(this, backPressedCallback)
|
||||||
val location = location(destination.longitude, destination.latitude)
|
|
||||||
navigationViewModel.loadPreviewRoute(
|
|
||||||
carContext,
|
|
||||||
surfaceRenderer.lastLocation,
|
|
||||||
location,
|
|
||||||
surfaceRenderer.carOrientation
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
val navigateActionIcon: CarIcon = CarIcon.Builder(
|
|
||||||
IconCompat.createWithResource(
|
|
||||||
carContext, R.drawable.navigation_48px
|
|
||||||
)
|
|
||||||
).build()
|
|
||||||
val navigateAction = Action.Builder()
|
|
||||||
.setFlags(FLAG_DEFAULT)
|
|
||||||
.setIcon(navigateActionIcon)
|
|
||||||
.setOnClickListener { this.onNavigate() }
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val itemListBuilder = ItemList.Builder()
|
val itemListBuilder = ItemList.Builder()
|
||||||
var i = 0
|
if (carContext.getCarAppApiLevel() > CarAppApiLevels.LEVEL_1) {
|
||||||
routeModel.route.routes.forEach { _ ->
|
val listLimit = min(
|
||||||
itemListBuilder.addItem(createRow(i++, navigateAction))
|
maxListItems,
|
||||||
|
carContext.getCarService(ConstraintManager::class.java)
|
||||||
|
.getContentLimit(
|
||||||
|
ConstraintManager.CONTENT_LIMIT_TYPE_LIST
|
||||||
|
)
|
||||||
|
)
|
||||||
|
var index = 0
|
||||||
|
routeModel.route.routes.forEach { route ->
|
||||||
|
itemListBuilder.addItem(createRow(route, index++))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
val header = Header.Builder()
|
val header = Header.Builder()
|
||||||
.setStartHeaderAction(Action.BACK)
|
.setStartHeaderAction(Action.BACK)
|
||||||
.setTitle(carContext.getString(R.string.route_preview))
|
.setTitle(carContext.getString(R.string.route_preview))
|
||||||
.addEndHeaderAction(
|
|
||||||
|
if (routeModel.route.routes.size == 1) {
|
||||||
|
header.addEndHeaderAction(
|
||||||
favoriteAction()
|
favoriteAction()
|
||||||
)
|
)
|
||||||
.addEndHeaderAction(
|
header.addEndHeaderAction(
|
||||||
deleteFavoriteAction()
|
deleteFavoriteAction()
|
||||||
)
|
)
|
||||||
.build()
|
}
|
||||||
|
|
||||||
val message =
|
val message =
|
||||||
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
if (routeModel.isNavigating() && routeModel.curRoute.waypoints.isNotEmpty()) {
|
||||||
createRouteText(0)
|
createRouteText(routeModel.route.routes.first())
|
||||||
} else {
|
} else {
|
||||||
CarText.Builder("Wait")
|
CarText.Builder("Wait")
|
||||||
.build()
|
.build()
|
||||||
@@ -110,7 +103,7 @@ class RoutePreviewScreen(
|
|||||||
val timer = object : CountDownTimer(5000, 1000) {
|
val timer = object : CountDownTimer(5000, 1000) {
|
||||||
override fun onTick(millisUntilFinished: Long) {}
|
override fun onTick(millisUntilFinished: Long) {}
|
||||||
override fun onFinish() {
|
override fun onFinish() {
|
||||||
onNavigate()
|
onNavigate(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
timer.start()
|
timer.start()
|
||||||
@@ -118,18 +111,27 @@ class RoutePreviewScreen(
|
|||||||
|
|
||||||
val content = if (routeModel.route.routes.size > 1) {
|
val content = if (routeModel.route.routes.size > 1) {
|
||||||
ListTemplate.Builder()
|
ListTemplate.Builder()
|
||||||
.setHeader(header)
|
.setHeader(header.build())
|
||||||
.setSingleList(itemListBuilder.build())
|
.setSingleList(itemListBuilder.build())
|
||||||
.build()
|
.build()
|
||||||
} else {
|
} else {
|
||||||
|
val navigateActionIcon: CarIcon = CarIcon.Builder(
|
||||||
|
IconCompat.createWithResource(
|
||||||
|
carContext, R.drawable.navigation_48px
|
||||||
|
)
|
||||||
|
).build()
|
||||||
|
val navigateAction = Action.Builder()
|
||||||
|
.setFlags(FLAG_DEFAULT)
|
||||||
|
.setIcon(navigateActionIcon)
|
||||||
|
.setOnClickListener { this.onNavigate(0) }
|
||||||
|
.build()
|
||||||
MessageTemplate.Builder(
|
MessageTemplate.Builder(
|
||||||
message
|
message
|
||||||
)
|
)
|
||||||
.setHeader(header)
|
.setHeader(header.build())
|
||||||
.addAction(navigateAction)
|
.addAction(navigateAction)
|
||||||
.setLoading(message.toString() == "Wait")
|
.setLoading(message.toString() == "Wait")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
}
|
}
|
||||||
return MapWithContentTemplate.Builder()
|
return MapWithContentTemplate.Builder()
|
||||||
.setContentTemplate(content)
|
.setContentTemplate(content)
|
||||||
@@ -176,7 +178,7 @@ class RoutePreviewScreen(
|
|||||||
private fun deleteFavoriteAction(): Action = Action.Builder()
|
private fun deleteFavoriteAction(): Action = Action.Builder()
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
if (isFavorite) {
|
if (isFavorite) {
|
||||||
navigationViewModel.deleteFavorite(carContext,destination)
|
navigationViewModel.deleteFavorite(carContext, destination)
|
||||||
}
|
}
|
||||||
isFavorite = !isFavorite
|
isFavorite = !isFavorite
|
||||||
finish()
|
finish()
|
||||||
@@ -192,10 +194,10 @@ class RoutePreviewScreen(
|
|||||||
)
|
)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun createRouteText(index: Int): CarText {
|
private fun createRouteText(route: Routes): CarText {
|
||||||
val time = routeModel.route.routes[index].summary.duration
|
val time = route.summary.duration
|
||||||
val length =
|
val length =
|
||||||
BigDecimal((routeModel.route.routes[index].summary.distance) / 1000).setScale(
|
BigDecimal((route.summary.distance) / 1000).setScale(
|
||||||
1,
|
1,
|
||||||
RoundingMode.HALF_EVEN
|
RoundingMode.HALF_EVEN
|
||||||
)
|
)
|
||||||
@@ -207,18 +209,43 @@ class RoutePreviewScreen(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createRow(index: Int, action: Action): Row {
|
private fun createRow(route: Routes, index: Int): Row {
|
||||||
val route = createRouteText(index)
|
val navigateActionIcon: CarIcon = CarIcon.Builder(
|
||||||
val titleText = "$index"
|
IconCompat.createWithResource(
|
||||||
return Row.Builder()
|
carContext, R.drawable.navigation_48px
|
||||||
.setTitle(route)
|
)
|
||||||
.setOnClickListener { onRouteSelected(index) }
|
).build()
|
||||||
.addText(titleText)
|
val navigateAction = Action.Builder()
|
||||||
.addAction(action)
|
.setFlags(FLAG_DEFAULT)
|
||||||
|
.setIcon(navigateActionIcon)
|
||||||
|
.setOnClickListener { this.onNavigate(index) }
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
val routeText = createRouteText(route)
|
||||||
|
var street = ""
|
||||||
|
var maxDistance = 0.0
|
||||||
|
routeModel.route.routes[index].legs.first().steps.forEach {
|
||||||
|
if (it.distance > maxDistance) {
|
||||||
|
maxDistance = it.distance
|
||||||
|
street = it.street
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val delay = (route.summary.trafficDelay / 60).toInt().toString()
|
||||||
|
|
||||||
|
val row = Row.Builder()
|
||||||
|
.setTitle(routeText)
|
||||||
|
.setOnClickListener { onRouteSelected(index) }
|
||||||
|
.addText(street)
|
||||||
|
.addAction(navigateAction)
|
||||||
|
|
||||||
|
if (route.summary.trafficDelay > 60) {
|
||||||
|
row.addText("$delay min")
|
||||||
|
}
|
||||||
|
return row.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onNavigate() {
|
private fun onNavigate(index: Int) {
|
||||||
|
destination.routeIndex = index
|
||||||
setResult(destination)
|
setResult(destination)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import com.kouros.data.R
|
|||||||
import com.kouros.navigation.car.SurfaceRenderer
|
import com.kouros.navigation.car.SurfaceRenderer
|
||||||
import com.kouros.navigation.car.ViewStyle
|
import com.kouros.navigation.car.ViewStyle
|
||||||
import com.kouros.navigation.data.Category
|
import com.kouros.navigation.data.Category
|
||||||
import com.kouros.navigation.data.Constants
|
import com.kouros.navigation.data.Constants.CATEGORIES
|
||||||
|
import com.kouros.navigation.data.Constants.FAVORITES
|
||||||
|
import com.kouros.navigation.data.Constants.RECENT
|
||||||
import com.kouros.navigation.data.Place
|
import com.kouros.navigation.data.Place
|
||||||
import com.kouros.navigation.data.nominatim.SearchResult
|
import com.kouros.navigation.data.nominatim.SearchResult
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
@@ -30,11 +32,13 @@ class SearchScreen(
|
|||||||
|
|
||||||
var isSearchComplete: Boolean = false
|
var isSearchComplete: Boolean = false
|
||||||
|
|
||||||
|
var category = ""
|
||||||
|
|
||||||
var categories: List<Category> = listOf(
|
var categories: List<Category> = listOf(
|
||||||
Category(id = Constants.RECENT, name = carContext.getString(R.string.recent_destinations)),
|
Category(id = RECENT, name = carContext.getString(R.string.recent_destinations)),
|
||||||
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
// Category(id = Constants.CONTACTS, name = carContext.getString(R.string.contacts)),
|
||||||
Category(id = Constants.CATEGORIES, name = carContext.getString(R.string.category_title)),
|
Category(id = CATEGORIES, name = carContext.getString(R.string.category_title)),
|
||||||
Category(id = Constants.FAVORITES, name = carContext.getString(R.string.favorites))
|
Category(id = FAVORITES, name = carContext.getString(R.string.favorites))
|
||||||
)
|
)
|
||||||
|
|
||||||
lateinit var searchResult: List<SearchResult>
|
lateinit var searchResult: List<SearchResult>
|
||||||
@@ -44,8 +48,50 @@ class SearchScreen(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val observerRecentPlaces = Observer<List<Place>> { newPlaces ->
|
||||||
|
if (newPlaces.isNotEmpty()) {
|
||||||
|
screenManager
|
||||||
|
.pushForResult(
|
||||||
|
PlaceListScreen(
|
||||||
|
carContext,
|
||||||
|
surfaceRenderer,
|
||||||
|
RECENT,
|
||||||
|
navigationViewModel,
|
||||||
|
newPlaces
|
||||||
|
)
|
||||||
|
) { obj: Any? ->
|
||||||
|
surfaceRenderer.clearRouteData()
|
||||||
|
if (obj != null) {
|
||||||
|
setResult(obj)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val observerFavorites = Observer<List<Place>> { newPlaces ->
|
||||||
|
screenManager
|
||||||
|
.pushForResult(
|
||||||
|
PlaceListScreen(
|
||||||
|
carContext,
|
||||||
|
surfaceRenderer,
|
||||||
|
FAVORITES,
|
||||||
|
navigationViewModel,
|
||||||
|
newPlaces
|
||||||
|
)
|
||||||
|
) { obj: Any? ->
|
||||||
|
if (obj != null) {
|
||||||
|
setResult(obj)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
navigationViewModel.searchPlaces.observe(this, observer)
|
navigationViewModel.searchPlaces.observe(this, observer)
|
||||||
|
navigationViewModel.recentPlaces.observe(this, observerRecentPlaces)
|
||||||
|
navigationViewModel.favorites.observe(this, observerFavorites)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetTemplate(): Template {
|
override fun onGetTemplate(): Template {
|
||||||
@@ -62,7 +108,7 @@ class SearchScreen(
|
|||||||
.setTitle(it.name)
|
.setTitle(it.name)
|
||||||
.setImage(categoryIcon(it.id))
|
.setImage(categoryIcon(it.id))
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
if (it.id == Constants.CATEGORIES) {
|
if (it.id == CATEGORIES) {
|
||||||
screenManager
|
screenManager
|
||||||
.pushForResult(
|
.pushForResult(
|
||||||
CategoriesScreen(
|
CategoriesScreen(
|
||||||
@@ -78,19 +124,19 @@ class SearchScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
screenManager
|
if (it.id == RECENT) {
|
||||||
.pushForResult(
|
navigationViewModel.loadRecentPlaces(
|
||||||
PlaceListScreen(
|
|
||||||
carContext,
|
carContext,
|
||||||
surfaceRenderer,
|
surfaceRenderer.lastLocation,
|
||||||
it.id,
|
surfaceRenderer.carOrientation
|
||||||
navigationViewModel
|
|
||||||
)
|
)
|
||||||
) { obj: Any? ->
|
|
||||||
if (obj != null) {
|
|
||||||
setResult(obj)
|
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
|
if (it.id == FAVORITES) {
|
||||||
|
navigationViewModel.loadFavorites(
|
||||||
|
carContext,
|
||||||
|
surfaceRenderer.lastLocation,
|
||||||
|
surfaceRenderer.carOrientation
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,13 +168,15 @@ class SearchScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun categoryIcon(category: String?): CarIcon {
|
fun categoryIcon(category: String?): CarIcon {
|
||||||
val resId : Int = when (category) {
|
val resId: Int = when (category) {
|
||||||
Constants.RECENT -> {
|
RECENT -> {
|
||||||
R.drawable.ic_place_white_24dp
|
R.drawable.ic_place_white_24dp
|
||||||
}
|
}
|
||||||
Constants.FAVORITES -> {
|
|
||||||
|
FAVORITES -> {
|
||||||
R.drawable.ic_favorite_white_24dp
|
R.drawable.ic_favorite_white_24dp
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
R.drawable.navigation_48px
|
R.drawable.navigation_48px
|
||||||
}
|
}
|
||||||
@@ -148,7 +196,7 @@ class SearchScreen(
|
|||||||
searchResult.forEach {
|
searchResult.forEach {
|
||||||
searchItemListBuilder.addItem(
|
searchItemListBuilder.addItem(
|
||||||
Row.Builder()
|
Row.Builder()
|
||||||
.setTitle("${(it.distance/1000).toInt()} km ${it.displayName} ")
|
.setTitle("${(it.distance / 1000).toInt()} km ${it.displayName} ")
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
navigateToPlace(it)
|
navigateToPlace(it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ data class Place(
|
|||||||
var street: String? = null,
|
var street: String? = null,
|
||||||
var distance: Float = 0F,
|
var distance: Float = 0F,
|
||||||
//var avatar: Uri? = null,
|
//var avatar: Uri? = null,
|
||||||
var lastDate: Long = 0
|
var lastDate: Long = 0,
|
||||||
|
var routeIndex: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
data class ContactData(
|
data class ContactData(
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ data class Route(
|
|||||||
val routeEngine: Int,
|
val routeEngine: Int,
|
||||||
val routes: List<com.kouros.navigation.data.route.Routes>,
|
val routes: List<com.kouros.navigation.data.route.Routes>,
|
||||||
var currentStepIndex: Int = 0,
|
var currentStepIndex: Int = 0,
|
||||||
|
var routeIndex : Int = 0,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
data class Builder(
|
data class Builder(
|
||||||
var routeEngine: Int = 0,
|
var routeEngine: Int = 0,
|
||||||
var summary: Summary = Summary(),
|
var summary: Summary = Summary(),
|
||||||
var routes: List<com.kouros.navigation.data.route.Routes> = emptyList(),
|
var routes: List<com.kouros.navigation.data.route.Routes> = emptyList(),
|
||||||
|
var routeIndex : Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun routeType(routeEngine: Int) = apply { this.routeEngine = routeEngine }
|
fun routeType(routeEngine: Int) = apply { this.routeEngine = routeEngine }
|
||||||
@@ -38,6 +40,8 @@ data class Route(
|
|||||||
}
|
}
|
||||||
fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine }
|
fun routeEngine(routeEngine: Int) = apply { this.routeEngine = routeEngine }
|
||||||
|
|
||||||
|
fun routeIndex(routeIndex: Int) = apply { this.routeIndex = routeIndex}
|
||||||
|
|
||||||
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()
|
||||||
@@ -68,6 +72,7 @@ data class Route(
|
|||||||
return Route(
|
return Route(
|
||||||
routeEngine = this.routeEngine,
|
routeEngine = this.routeEngine,
|
||||||
routes = this.routes,
|
routes = this.routes,
|
||||||
|
routeIndex = this.routeIndex
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +80,7 @@ data class Route(
|
|||||||
return Route(
|
return Route(
|
||||||
routeEngine = 0,
|
routeEngine = 0,
|
||||||
routes = emptyList(),
|
routes = emptyList(),
|
||||||
|
routeIndex = 0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,7 +88,7 @@ data class Route(
|
|||||||
|
|
||||||
fun legs(): List<Leg> {
|
fun legs(): List<Leg> {
|
||||||
return if (routes.isNotEmpty()) {
|
return if (routes.isNotEmpty()) {
|
||||||
routes.first().legs
|
routes[routeIndex].legs
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class TomTomRepository : NavigationRepository() {
|
|||||||
"&vehicleMaxSpeed=120&vehicleCommercial=false" +
|
"&vehicleMaxSpeed=120&vehicleCommercial=false" +
|
||||||
"&instructionsType=text&language=$language§ionType=lanes" +
|
"&instructionsType=text&language=$language§ionType=lanes" +
|
||||||
"&routeRepresentation=encodedPolyline" +
|
"&routeRepresentation=encodedPolyline" +
|
||||||
|
"&maxAlternatives=2" +
|
||||||
"&vehicleEngineType=combustion$filter&key=$tomtomApiKey"
|
"&vehicleEngineType=combustion$filter&key=$tomtomApiKey"
|
||||||
return fetchUrl(
|
return fetchUrl(
|
||||||
url,
|
url,
|
||||||
|
|||||||
@@ -3,10 +3,8 @@ package com.kouros.navigation.model
|
|||||||
//import com.kouros.navigation.data.Preferences.boxStore
|
//import com.kouros.navigation.data.Preferences.boxStore
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.util.Log
|
|
||||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
import androidx.compose.runtime.toMutableStateList
|
import androidx.compose.runtime.toMutableStateList
|
||||||
import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
@@ -58,7 +56,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** LiveData containing list of recent navigation destinations */
|
/** LiveData containing list of recent navigation destinations */
|
||||||
val places: MutableLiveData<List<Place>> by lazy {
|
val recentPlaces: MutableLiveData<List<Place>> by lazy {
|
||||||
MutableLiveData()
|
MutableLiveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +142,11 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
val settingsRepository = getSettingsRepository(context)
|
val settingsRepository = getSettingsRepository(context)
|
||||||
val rp = settingsRepository.recentPlacesFlow.first()
|
val rp = settingsRepository.recentPlacesFlow.first()
|
||||||
val gson = GsonBuilder().serializeNulls().create()
|
val gson = GsonBuilder().serializeNulls().create()
|
||||||
val recentPlaces = gson.fromJson(rp, Places::class.java)
|
val places = gson.fromJson(rp, Places::class.java)
|
||||||
val pl = mutableListOf<Place>()
|
val pl = mutableListOf<Place>()
|
||||||
var id: Long = 0
|
var id: Long = 0
|
||||||
if (rp.isNotEmpty()) {
|
if (rp.isNotEmpty()) {
|
||||||
for (place in recentPlaces.places) {
|
for (place in places.places) {
|
||||||
if (place.category.equals(Constants.RECENT)) {
|
if (place.category.equals(Constants.RECENT)) {
|
||||||
val plLocation = location(place.longitude, place.latitude)
|
val plLocation = location(place.longitude, place.latitude)
|
||||||
if (place.latitude != 0.0) {
|
if (place.latitude != 0.0) {
|
||||||
@@ -167,7 +165,7 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
places.postValue(pl)
|
recentPlaces.postValue(pl)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ open class RouteModel {
|
|||||||
route = Route.Builder()
|
route = Route.Builder()
|
||||||
.routeEngine(navState.routingEngine)
|
.routeEngine(navState.routingEngine)
|
||||||
.route(routeString)
|
.route(routeString)
|
||||||
|
.routeIndex(navState.currentRouteIndex)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
if (hasLegs()) {
|
if (hasLegs()) {
|
||||||
@@ -42,7 +43,7 @@ open class RouteModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasLegs(): Boolean {
|
fun hasLegs(): Boolean {
|
||||||
return navState.route.routes.isNotEmpty() && navState.route.routes[0].legs.isNotEmpty()
|
return navState.route.routes.isNotEmpty() && navState.route.routes[navState.currentRouteIndex].legs.isNotEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stopNavigation() {
|
fun stopNavigation() {
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ import com.kouros.navigation.data.osrm.OsrmRepository
|
|||||||
import com.kouros.navigation.data.tomtom.TomTomRepository
|
import com.kouros.navigation.data.tomtom.TomTomRepository
|
||||||
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
import com.kouros.navigation.data.valhalla.ValhallaRepository
|
||||||
import com.kouros.navigation.model.NavigationViewModel
|
import com.kouros.navigation.model.NavigationViewModel
|
||||||
|
import com.kouros.navigation.utils.GeoUtils.calculateSquareRadius
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import java.lang.Math.toDegrees
|
||||||
|
import java.lang.Math.toRadians
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.ZoneOffset
|
import java.time.ZoneOffset
|
||||||
@@ -19,6 +22,8 @@ import java.time.format.DateTimeFormatter
|
|||||||
import java.time.format.FormatStyle
|
import java.time.format.FormatStyle
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.ln
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
@@ -53,15 +58,36 @@ fun calculateZoom(speed: Double?): Double {
|
|||||||
return zoom
|
return zoom
|
||||||
}
|
}
|
||||||
|
|
||||||
fun previewZoom(previewDistance: Double): Double {
|
fun previewZoom(centerLocation: Location, previewDistance: Double): Double {
|
||||||
when (previewDistance / 1000) {
|
return calculateZoomFromBoundingBox(centerLocation, previewDistance / 1000)
|
||||||
in 0.0..10.0 -> return 13.5
|
|
||||||
in 10.0..20.0 -> return 11.5
|
|
||||||
in 20.0..30.0 -> return 10.5
|
|
||||||
}
|
|
||||||
return 9.5
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun calculateZoomFromBoundingBox(centerLocation: Location, previewDistance: Double): Double {
|
||||||
|
|
||||||
|
val earthRadius = 6371.0
|
||||||
|
val maxLat = centerLocation.latitude + toDegrees(previewDistance / earthRadius)
|
||||||
|
val minLat = centerLocation.latitude - toDegrees(previewDistance / earthRadius)
|
||||||
|
val maxLon = centerLocation.longitude + toDegrees(previewDistance / earthRadius / cos(toRadians(centerLocation.latitude)))
|
||||||
|
val minLon = centerLocation.longitude - toDegrees(previewDistance / earthRadius / cos(toRadians(centerLocation.latitude)))
|
||||||
|
|
||||||
|
var zoomLevel: Double
|
||||||
|
val latDiff = maxLat - minLat
|
||||||
|
val lngDiff = maxLon - minLon
|
||||||
|
|
||||||
|
val maxDiff = if(lngDiff > latDiff) lngDiff else latDiff
|
||||||
|
|
||||||
|
if (maxDiff < 360 / 2.0.pow(20.0)) {
|
||||||
|
zoomLevel = 21.0
|
||||||
|
} else {
|
||||||
|
zoomLevel = (-1 * ((ln(maxDiff) / ln(2.0)) - (ln(360.0) / ln(2.0))));
|
||||||
|
if (zoomLevel < 1)
|
||||||
|
zoomLevel = 1.0;
|
||||||
|
}
|
||||||
|
return zoomLevel + 1.2
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun calculateTilt(newZoom: Double, tilt: Double): Double =
|
fun calculateTilt(newZoom: Double, tilt: Double): Double =
|
||||||
if (newZoom < 13) {
|
if (newZoom < 13) {
|
||||||
0.0
|
0.0
|
||||||
@@ -119,7 +145,7 @@ fun isMetricSystem(): Boolean {
|
|||||||
return !setOf("US", "UK", "LR", "MM").contains(country)
|
return !setOf("US", "UK", "LR", "MM").contains(country)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formattedDistance(distanceMode : Int, distance: Double): Pair<Double, Int> {
|
fun formattedDistance(distanceMode: Int, distance: Double): Pair<Double, Int> {
|
||||||
var currentDistance = distance
|
var currentDistance = distance
|
||||||
var displayUnit: Int
|
var displayUnit: Int
|
||||||
if (distanceMode == 1 || distanceMode == 0 && isMetricSystem()) {
|
if (distanceMode == 1 || distanceMode == 0 && isMetricSystem()) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user