This commit is contained in:
Dimitris
2026-03-15 17:18:02 +01:00
parent 619ceb9f83
commit 5198725879
46 changed files with 840 additions and 1326 deletions

View File

@@ -13,8 +13,8 @@ android {
applicationId = "com.kouros.navigation" applicationId = "com.kouros.navigation"
minSdk = 33 minSdk = 33
targetSdk = 36 targetSdk = 36
versionCode = 66 versionCode = 68
versionName = "0.2.0.66" versionName = "0.2.0.68"
base.archivesName = "navi-$versionName" base.archivesName = "navi-$versionName"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -10,6 +10,7 @@ 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.appcompat.app.AppCompatDelegate
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
@@ -172,6 +173,7 @@ class MainActivity : ComponentActivity() {
} }
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
NavigationTheme {
CheckPermissionScreen(app = { CheckPermissionScreen(app = {
AppNavGraph( AppNavGraph(
mainActivity = this mainActivity = this
@@ -179,15 +181,21 @@ class MainActivity : ComponentActivity() {
}) })
} }
} }
}
@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()
if (darkMode == 1) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1) val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
val locationProvider = rememberDefaultLocationProvider( val locationProvider = rememberDefaultLocationProvider(
updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest

View File

@@ -16,10 +16,37 @@ fun AppNavGraph(mainActivity: MainActivity) {
val navController = rememberNavController() val navController = rememberNavController()
NavHost(navController = navController, startDestination = "startScreen") { NavHost(navController = navController, startDestination = "startScreen") {
composable("startScreen") { mainActivity.StartScreen(navController) } composable("startScreen") { mainActivity.StartScreen(navController) }
composable("display_settings") { SettingsRoute("display_settings", navController) { navController.popBackStack() } } composable("display_settings") {
composable("nav_settings") { SettingsRoute("nav_settings", navController) { navController.popBackStack() } } SettingsRoute(
composable("settings") { SettingsRoute("settings", navController) { navController.popBackStack() } } "display_settings",
composable("search") { SearchScreen(navController, navController.context, navigationViewModel, mainActivity.lastLocation) { navController.popBackStack() } navController
) { navController.popBackStack() }
}
composable("nav_settings") {
SettingsRoute(
"nav_settings",
navController
) { navController.popBackStack() }
}
composable("settings") {
SettingsRoute(
"settings",
navController
) { navController.popBackStack() }
}
composable("search") {
SearchScreen(
navController,
navController.context,
navigationViewModel,
mainActivity.lastLocation
) { navController.popBackStack() }
}
composable("settings_screen") {
SettingsRoute(
"settings_screen",
navController
) { navController.popBackStack() }
} }
} }
} }

View File

@@ -3,8 +3,10 @@ package com.kouros.navigation.ui.search
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.location.Location import android.location.Location
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@@ -28,14 +30,17 @@ 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.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
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.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember 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.Alignment
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.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@@ -49,8 +54,11 @@ 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.app.AppViewModel
import com.kouros.navigation.ui.app.appViewModel
import com.kouros.navigation.ui.theme.NavigationTheme import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlin.math.roundToInt
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@@ -64,10 +72,18 @@ fun SearchScreen(
function: () -> Unit function: () -> Unit
) { ) {
NavigationTheme(true) { val appViewModel: AppViewModel = appViewModel()
val darkMode by appViewModel.darkMode.collectAsState()
if (darkMode == 1 || darkMode == 2 && isSystemInDarkTheme()) {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
} else {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
//NavigationTheme(darkMode == 1 || darkMode == 2 && isSystemInDarkTheme()) {
Scaffold( Scaffold(
topBar = { topBar = {
CenterAlignedTopAppBar( TopAppBar(
title = { title = {
Text( Text(
stringResource(id = R.string.search_action_title), stringResource(id = R.string.search_action_title),
@@ -92,7 +108,7 @@ fun SearchScreen(
Categories(context, navigationViewModel, location, closeSheet = { }) Categories(context, navigationViewModel, location, closeSheet = { })
} }
} }
} // }
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -118,9 +134,6 @@ fun SearchBar(
} }
SearchBar( SearchBar(
colors = SearchBarDefaults.colors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
),
inputField = { inputField = {
SearchBarDefaults.InputField( SearchBarDefaults.InputField(
modifier = Modifier.focusRequester(focusRequester), modifier = Modifier.focusRequester(focusRequester),
@@ -206,11 +219,11 @@ private fun SearchPlaces(
) { ) {
val color = remember { PlaceColor } val color = remember { PlaceColor }
LazyColumn( LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp), contentPadding = PaddingValues(horizontal = 6.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(4.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
items(searchResults, key = { it.placeId }) { place -> items(searchResults, key = { it.placeId }) { place ->
Row { Row(verticalAlignment = Alignment.CenterVertically) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_place_white_24dp), painter = painterResource(id = R.drawable.ic_place_white_24dp),
"Navigation", "Navigation",
@@ -218,8 +231,11 @@ private fun SearchPlaces(
modifier = Modifier.size(24.dp, 24.dp), modifier = Modifier.size(24.dp, 24.dp),
) )
ListItem( ListItem(
headlineContent = { Text(place.displayName) }, headlineContent = {Text(place.address.road)},
leadingContent = {Text(place.name)},
trailingContent = { Text("${(place.distance/1000).roundToInt()} km") },
modifier = Modifier modifier = Modifier
.animateItem()
.clickable { .clickable {
val pl = Place( val pl = Place(
name = place.name, name = place.name,

View File

@@ -160,7 +160,7 @@ private fun RecentPlaces(
Icon( Icon(
painter = painterResource(id = R.drawable.ic_place_white_24dp), painter = painterResource(id = R.drawable.ic_place_white_24dp),
"Navigation", "Navigation",
tint = color.copy(alpha = 1f), //tint = color.copy(alpha = 1f),
modifier = Modifier.size(24.dp, 24.dp), modifier = Modifier.size(24.dp, 24.dp),
) )
ListItem( ListItem(

View File

@@ -1,35 +1,41 @@
package com.kouros.navigation.ui.settings package com.kouros.navigation.ui.settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height 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.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButtonDefaults.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.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
import androidx.navigation.NavHostController
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.model.SettingsViewModel import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.ui.components.RadioButtonSingleSelection import com.kouros.navigation.ui.components.RadioButtonSingleSelection
import com.kouros.navigation.ui.components.SectionTitle import com.kouros.navigation.ui.components.SectionTitle
import com.kouros.navigation.ui.components.SettingSwitch import com.kouros.navigation.ui.components.SettingSwitch
import com.kouros.navigation.ui.theme.NavigationTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -37,10 +43,12 @@ fun DisplayScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
val darkMode by viewModel.darkMode.collectAsState() val darkMode by viewModel.darkMode.collectAsState()
val show3D by viewModel.show3D.collectAsState() val show3D by viewModel.show3D.collectAsState()
NavigationTheme(useDarkTheme = viewModel.darkMode.collectAsState().value == 1) { val showTraffic by viewModel.traffic.collectAsState()
val distanceMode by viewModel.distanceMode.collectAsState()
Scaffold( Scaffold(
topBar = { topBar = {
CenterAlignedTopAppBar( TopAppBar(
title = { title = {
Text( Text(
stringResource(id = R.string.display_settings), stringResource(id = R.string.display_settings),
@@ -58,31 +66,39 @@ fun DisplayScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
) )
}, },
) )
{ padding -> { paddingValues ->
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Column( Column(
modifier = Modifier modifier = Modifier
.padding(paddingValues)
.fillMaxSize() .fillMaxSize()
.padding(top = 20.dp) .padding(top = 10.dp)
.verticalScroll(scrollState) .verticalScroll(scrollState)
) { ) {
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
Text( Column(
text = stringResource(R.string.settings_action_title), modifier = Modifier.padding(10.dp),
style = MaterialTheme.typography.headlineMedium ) {
)
Spacer(modifier = Modifier.height(24.dp))
// Appearance
SectionTitle(stringResource(R.string.threed_building))
SettingSwitch( SettingSwitch(
title = stringResource(R.string.threed_building), title = stringResource(R.string.threed_building),
checked = show3D, checked = show3D,
onCheckedChange = viewModel::onShow3DChanged onCheckedChange = viewModel::onShow3DChanged
) )
SettingSwitch(
title = stringResource(R.string.traffic),
checked = showTraffic,
onCheckedChange = viewModel::onTraffic
)
}
}
Spacer(modifier = Modifier.height(20.dp))
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(10.dp),
) {
SectionTitle(stringResource(R.string.dark_mode)) SectionTitle(stringResource(R.string.dark_mode))
val radioOptions = listOf( val radioOptions = listOf(
@@ -98,19 +114,29 @@ fun DisplayScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
) )
} }
} }
Spacer(modifier = Modifier.height(20.dp))
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(10.dp),
) {
SectionTitle(stringResource(R.string.distance_units))
val radioOptions = listOf(
stringResource(R.string.automatically),
stringResource(R.string.kilometer),
stringResource(R.string.miles)
)
RadioButtonSingleSelection(
modifier = Modifier.padding(),
selectedOption = distanceMode,
radioOptions = radioOptions,
onClick = viewModel::onDistanceModeChanged
)
}
}
}
} }
} }

View File

@@ -1,7 +1,10 @@
package com.kouros.navigation.ui.settings package com.kouros.navigation.ui.settings
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
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.rememberScrollState import androidx.compose.foundation.rememberScrollState
@@ -10,9 +13,11 @@ import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -38,11 +43,9 @@ import com.kouros.navigation.ui.theme.NavigationTheme
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun NavigationScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) { fun NavigationScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
NavigationTheme(useDarkTheme = viewModel.darkMode.collectAsState().value == 1) {
Scaffold( Scaffold(
topBar = { topBar = {
CenterAlignedTopAppBar( TopAppBar(
title = { title = {
Text( Text(
stringResource(id = R.string.navigation_settings), stringResource(id = R.string.navigation_settings),
@@ -64,15 +67,13 @@ fun NavigationScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
Column( Column(
modifier = modifier =
Modifier Modifier
.consumeWindowInsets(padding) .padding(padding)
.verticalScroll(scrollState) .verticalScroll(scrollState)
.padding(top = padding.calculateTopPadding()),
) { ) {
NavigationSettings(viewModel) NavigationSettings(viewModel)
} }
} }
} }
}
@Composable @Composable
fun NavigationSettings(viewModel: SettingsViewModel) { fun NavigationSettings(viewModel: SettingsViewModel) {
@@ -82,6 +83,10 @@ fun NavigationSettings(viewModel: SettingsViewModel) {
val routingEngine by viewModel.routingEngine.collectAsState() val routingEngine by viewModel.routingEngine.collectAsState()
val tomTomApiKey by viewModel.tomTomApiKey.collectAsState() val tomTomApiKey by viewModel.tomTomApiKey.collectAsState()
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(10.dp),
) {
SettingSwitch( SettingSwitch(
title = stringResource(R.string.avoid_highways_row_title), title = stringResource(R.string.avoid_highways_row_title),
checked = avoidMotorway, checked = avoidMotorway,
@@ -99,7 +104,15 @@ fun NavigationSettings(viewModel: SettingsViewModel) {
checked = carLocation, checked = carLocation,
onCheckedChange = viewModel::onCarLocation onCheckedChange = viewModel::onCarLocation
) )
}
}
Spacer(modifier = Modifier.height(20.dp))
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier.padding(10.dp),
) {
SectionTitle(stringResource(R.string.routing_engine)) SectionTitle(stringResource(R.string.routing_engine))
val routingEngineOptions = listOf( val routingEngineOptions = listOf(
@@ -128,3 +141,5 @@ fun NavigationSettings(viewModel: SettingsViewModel) {
) )
} }
} }
}
}

View File

@@ -0,0 +1,120 @@
package com.kouros.navigation.ui.settings
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO
import androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import com.kouros.data.R
import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.ui.theme.NavigationTheme
data class Settings(val id: String, val name: String, val icon: Int)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Settings(
viewModel: SettingsViewModel,
navController: NavHostController,
navigateBack: () -> Unit
) {
val items = listOf(
Settings(
id = "favorites_screen",
name = "Favoriten",
icon = R.drawable.ic_favorite_white_24dp
),
Settings(
id = "settings_screen",
name = "Einstellungen",
icon = R.drawable.speed_camera_24px
),
Settings(id = "info_screen", name = "Info", icon = R.drawable.ic_place_white_24dp),
)
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(id = R.string.settings_action_title)) },
)
},
) { paddingValues ->
LazyColumn(
modifier = Modifier
.padding(paddingValues)
.padding(8.dp),
) {
item {
Spacer(modifier = Modifier.height(10.dp))
}
items(items) { subItem ->
ScreenItem(item = subItem, onClick = { navController.navigate(subItem.id) })
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
@Composable
fun ScreenItem(
item: Settings,
onClick: (Settings) -> Unit,
) {
OutlinedCard(onClick = { onClick(item) }, modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.padding(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(item.icon),
contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.align(Alignment.CenterVertically),
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(text = item.name, style = MaterialTheme.typography.titleMedium)
}
IconForward()
}
}
}
@Composable
fun RowScope.IconForward() {
Icon(
painter = painterResource(R.drawable.arrow_back_24px),
contentDescription = stringResource(id = R.string.on_action_title),
modifier = Modifier.align(Alignment.CenterVertically),
)
}

View File

@@ -34,6 +34,9 @@ fun SettingsRoute(route: String, navController: NavHostController, function: ()
NavigationScreen (viewModel = viewModel, function) NavigationScreen (viewModel = viewModel, function)
} }
if (route == "settings") { if (route == "settings") {
Settings(viewModel, navController, function)
}
if (route == "settings_screen") {
SettingsScreen(viewModel, navController, function) SettingsScreen(viewModel, navController, function)
} }
} }

View File

@@ -1,20 +1,24 @@
package com.kouros.navigation.ui.settings package com.kouros.navigation.ui.settings
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.lazy.LazyColumn
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -24,49 +28,79 @@ import com.kouros.data.R
import com.kouros.navigation.model.SettingsViewModel import com.kouros.navigation.model.SettingsViewModel
import com.kouros.navigation.ui.theme.NavigationTheme import com.kouros.navigation.ui.theme.NavigationTheme
data class Item(val id: String, val name: String, val description: String, val icon: Int)
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SettingsScreen(viewModel: SettingsViewModel, navController: NavHostController, navigateBack: () -> Unit) { fun SettingsScreen(
NavigationTheme(useDarkTheme = viewModel.darkMode.collectAsState().value == 1) { viewModel: SettingsViewModel,
navController: NavHostController,
navigateBack: () -> Unit
) {
val items = listOf(
Item(
id = "display_settings",
name = "Display Settings",
description = "",
icon = R.drawable.dark_mode_24px
),
Item(
id = "nav_settings",
name = "Navigation Settings",
description = "",
icon = R.drawable.navigation_24px
)
)
Scaffold( Scaffold(
topBar = { topBar = {
CenterAlignedTopAppBar( TopAppBar(
title = { title = { Text("Settings") },
Text(
stringResource(id = R.string.settings_action_title),
) )
}, },
navigationIcon = { ) { paddingValues ->
IconButton(onClick = navigateBack) { LazyColumn(
Icon( modifier = Modifier
painter = painterResource(R.drawable.arrow_back_24px), .padding(paddingValues)
contentDescription = stringResource(id = R.string.accept_action_title), .padding(8.dp),
modifier = Modifier.size(48.dp, 48.dp),
)
}
},
)
},
) { padding ->
val scrollState = rememberScrollState()
Column(
modifier =
Modifier
.consumeWindowInsets(padding)
.verticalScroll(scrollState)
.padding(top = padding.calculateTopPadding()),
) { ) {
Column(modifier = Modifier.padding(16.dp)) { item {
Spacer(modifier = Modifier.height(10.dp))
Button(onClick = { navController.navigate("display_settings") }) {
Text(stringResource(R.string.display_settings))
}
Button(onClick = { navController.navigate("nav_settings") }) {
Text(stringResource(R.string.navigation_settings))
}
} }
items(items) { subItem ->
ScreenItem(item = subItem, onClick = { navController.navigate(subItem.id) })
Spacer(modifier = Modifier.height(10.dp))
} }
} }
} }
} }
@Composable
fun ScreenItem(
item: Item,
onClick: (Item) -> Unit,
) {
OutlinedCard(onClick = { onClick(item) }, modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.padding(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
painter = painterResource(item.icon),
contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.align(Alignment.CenterVertically),
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text(text = item.name, style = MaterialTheme.typography.titleMedium)
}
IconForward()
}
}
}

View File

@@ -84,28 +84,24 @@ private val DarkColors = darkColorScheme(
@Composable @Composable
fun NavigationTheme( fun NavigationTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(), useDarkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val colorScheme = when {
dynamicColor -> {
val context = LocalContext.current val context = LocalContext.current
val colors = run { if (useDarkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
if (useDarkTheme) dynamicDarkColorScheme(context)
else dynamicLightColorScheme(context)
} }
val view = LocalView.current useDarkTheme -> DarkColors
if (!view.isInEditMode) { else -> LightColors
SideEffect {
val window = (view.context as Activity).window
window.statusBarColor = colors.primary.toArgb()
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars =
useDarkTheme
} }
MaterialTheme( MaterialTheme(
colorScheme = if (useDarkTheme) DarkColors else colorScheme,
colorScheme = colorScheme,
typography = typography, typography = typography,
content = content, content = content,
shapes = shapes, shapes = shapes,
) )
} }
}

View File

@@ -176,14 +176,20 @@ class RouteModelTest {
@Test @Test
fun simulate() { fun simulate() {
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) { for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
val curLocation = location(waypoint[0], waypoint[1])
if (routeModel.isNavigating()) { if (routeModel.isNavigating()) {
val curLocation = location(waypoint[0], waypoint[1])
if (index in 0..routeModel.curRoute.waypoints.size) { if (index in 0..routeModel.curRoute.waypoints.size) {
//runBlocking { delay(1000) } //runBlocking { delay(1000) }
val start = System.currentTimeMillis() val start = System.currentTimeMillis()
routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository())) routeModel.updateLocation(curLocation, NavigationViewModel(TomTomRepository()))
val stepData = routeModel.currentStep() val stepData = routeModel.currentStep()
println("${stepData.instruction} ${System.currentTimeMillis() - start}") //println("${stepData.instruction} ${System.currentTimeMillis() - start}")
if (stepData.lane.isNotEmpty()) {
println(stepData.street)
stepData.lane.forEach {
println("${it.indications} ${it.valid}")
}
}
// val nextData = routeModel.nextStep() // val nextData = routeModel.nextStep()
} }
} }

View File

@@ -2,13 +2,11 @@ package com.kouros.navigation.car.map
import android.location.Location import android.location.Location
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
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
@@ -17,10 +15,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale import androidx.compose.ui.draw.scale
import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
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
@@ -36,7 +32,6 @@ 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.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
@@ -63,7 +58,6 @@ 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

View File

@@ -114,7 +114,11 @@ class RouteCarModel : RouteModel() {
) )
.setRemainingTimeColor(CarColor.GREEN) .setRemainingTimeColor(CarColor.GREEN)
.setRemainingDistanceColor(CarColor.BLUE) .setRemainingDistanceColor(CarColor.BLUE)
.setTripText(CarText.create("$traffic min")) if (traffic > 0) {
travelBuilder.setTripText(CarText.create("$traffic min"))
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
}
if (navState.travelMessage.isNotEmpty()) { if (navState.travelMessage.isNotEmpty()) {
travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px)) travelBuilder.setTripIcon(createCarIcon(carContext, R.drawable.warning_24px))
travelBuilder.setTripText(CarText.create(navState.travelMessage)) travelBuilder.setTripText(CarText.create(navState.travelMessage))
@@ -143,7 +147,7 @@ class RouteCarModel : RouteModel() {
} }
val laneType = val laneType =
Lane.Builder() Lane.Builder()
.addDirection(LaneDirection.create(laneDirection, true)) .addDirection(LaneDirection.create(laneDirection, it.valid))
.build() .build()
step.addLane(laneType) step.addLane(laneType)
} }

View File

@@ -4,6 +4,8 @@ import android.location.Location
import android.location.LocationManager import android.location.LocationManager
import android.os.SystemClock import android.os.SystemClock
import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.LifecycleCoroutineScope
import com.kouros.navigation.data.Constants.homeVogelhart
import com.kouros.navigation.utils.location
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -17,6 +19,10 @@ class Simulation {
lifecycleScope: LifecycleCoroutineScope, lifecycleScope: LifecycleCoroutineScope,
updateLocation: (Location) -> Unit updateLocation: (Location) -> Unit
) { ) {
// A92
//updateLocation(location(11.709508, 48.338923 ))
//updateLocation(homeVogelhart)
if (routeModel.navState.route.isRouteValid()) { if (routeModel.navState.route.isRouteValid()) {
val points = routeModel.curRoute.waypoints val points = routeModel.curRoute.waypoints
if (points.isEmpty()) return if (points.isEmpty()) return
@@ -38,7 +44,7 @@ class Simulation {
// 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 1 second) // Wait before moving to the next point (e.g., every 1 second)
delay(1000) delay(500)
lastLocation = fakeLocation lastLocation = fakeLocation
} }
routeModel.stopNavigation() routeModel.stopNavigation()

View File

@@ -1,5 +1,6 @@
package com.kouros.navigation.car.screen package com.kouros.navigation.car.screen
import androidx.activity.OnBackPressedCallback
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
@@ -40,8 +41,17 @@ class CategoriesScreen(
Category(id = CHARGING_STATION, name = carContext.getString(R.string.charging_station)) Category(id = CHARGING_STATION, name = carContext.getString(R.string.charging_station))
) )
private val backPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
navigationViewModel.elements.value = emptyList()
invalidate()
}
}
init { init {
navigationViewModel.elements.value = emptyList()
navigationViewModel.elements.observe(this, categoryObserver) navigationViewModel.elements.observe(this, categoryObserver)
carContext.onBackPressedDispatcher.addCallback(this, backPressedCallback)
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {

View File

@@ -530,8 +530,7 @@ 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(

View File

@@ -39,11 +39,12 @@ class PlaceListScreen(
private val places: List<Place> private val places: List<Place>
) : Screen(carContext) { ) : Screen(carContext) {
val routeModel = RouteCarModel() val routeModel = RouteCarModel()
var place = Place() var place = Place()
var mPlaces = mutableListOf<Place>()
val previewObserver = Observer<String> { route -> val previewObserver = Observer<String> { route ->
if (route.isNotEmpty()) { if (route.isNotEmpty()) {
val repository = getSettingsRepository(carContext) val repository = getSettingsRepository(carContext)
@@ -70,21 +71,25 @@ class PlaceListScreen(
} }
init { init {
loadPlaces() // loadPlaces()
navigationViewModel.recentPlaces.value = emptyList()
navigationViewModel.previewRoute.value = ""
mPlaces.addAll(places)
navigationViewModel.previewRoute.observe(this, previewObserver) navigationViewModel.previewRoute.observe(this, previewObserver)
} }
override fun onGetTemplate(): Template { override fun onGetTemplate(): Template {
val itemListBuilder = ItemList.Builder() val itemListBuilder = ItemList.Builder()
.setNoItemsMessage(carContext.getString(R.string.no_places)) .setNoItemsMessage(carContext.getString(R.string.no_places))
places.forEach { mPlaces.forEach {
val street = if (it.street != null) { val street = if (it.street != null) {
it.street it.street
} else { } else {
"" ""
} }
val row = Row.Builder() val row = Row.Builder()
// .setImage(contactIcon(it.avatar, it.category)) .setImage(contactIcon(null, it.category))
.setTitle("$street ${it.city}") .setTitle("$street ${it.city}")
.setOnClickListener { .setOnClickListener {
place = Place( place = Place(
@@ -155,7 +160,7 @@ class PlaceListScreen(
carContext, carContext,
R.string.recent_Item_deleted, CarToast.LENGTH_LONG R.string.recent_Item_deleted, CarToast.LENGTH_LONG
).show() ).show()
loadPlaces() mPlaces.remove(place)
invalidate() invalidate()
} }
.build() .build()

View File

@@ -3,16 +3,17 @@ package com.kouros.navigation.car.screen.settings
import androidx.car.app.CarContext import androidx.car.app.CarContext
import androidx.car.app.Screen import androidx.car.app.Screen
import androidx.car.app.model.Action import androidx.car.app.model.Action
import androidx.car.app.model.CarIcon
import androidx.car.app.model.Header import androidx.car.app.model.Header
import androidx.car.app.model.ItemList import androidx.car.app.model.ItemList
import androidx.car.app.model.ListTemplate import androidx.car.app.model.ListTemplate
import androidx.car.app.model.Row import androidx.car.app.model.Row
import androidx.car.app.model.Template import androidx.car.app.model.Template
import androidx.car.app.model.Toggle import androidx.car.app.model.Toggle
import androidx.core.graphics.drawable.IconCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.kouros.data.R import com.kouros.data.R
import com.kouros.navigation.car.screen.settings.PasswordSettings import com.kouros.navigation.car.navigation.NavigationUtils
import com.kouros.navigation.car.screen.settings.RoutingSettings
import com.kouros.navigation.model.NavigationViewModel import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.utils.getSettingsViewModel import com.kouros.navigation.utils.getSettingsViewModel
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
@@ -61,7 +62,8 @@ class NavigationSettings(
listBuilder.addItem( listBuilder.addItem(
buildRowForTemplate( buildRowForTemplate(
R.string.avoid_highways_row_title, R.string.avoid_highways_row_title,
highwayToggle highwayToggle,
NavigationUtils(carContext).createCarIcon(R.drawable.baseline_add_road_24)
) )
) )
@@ -71,7 +73,7 @@ class NavigationSettings(
settingsViewModel.onAvoidTollway(checked) settingsViewModel.onAvoidTollway(checked)
tollWayToggleState = !tollWayToggleState tollWayToggleState = !tollWayToggleState
}.setChecked(tollWayToggleState).build() }.setChecked(tollWayToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle)) listBuilder.addItem(buildRowForTemplate(R.string.avoid_tolls_row_title, tollwayToggle, NavigationUtils(carContext).createCarIcon(R.drawable.baseline_toll_24)))
// Ferry // Ferry
val ferryToggle: Toggle = val ferryToggle: Toggle =
@@ -79,7 +81,7 @@ class NavigationSettings(
settingsViewModel.onAvoidFerry(checked) settingsViewModel.onAvoidFerry(checked)
ferryToggleState = !ferryToggleState ferryToggleState = !ferryToggleState
}.setChecked(ferryToggleState).build() }.setChecked(ferryToggleState).build()
listBuilder.addItem(buildRowForTemplate(R.string.avoid_ferries, ferryToggle)) listBuilder.addItem(buildRowForTemplate(R.string.avoid_ferries, ferryToggle, NavigationUtils(carContext).createCarIcon(R.drawable.baseline_directions_boat_filled_24)))
// CarLocation // CarLocation
val carLocationToggle: Toggle = val carLocationToggle: Toggle =
@@ -91,7 +93,8 @@ class NavigationSettings(
listBuilder.addItem( listBuilder.addItem(
buildRowForTemplate( buildRowForTemplate(
R.string.use_car_location, R.string.use_car_location,
carLocationToggle carLocationToggle,
NavigationUtils(carContext).createCarIcon(R.drawable.ic_place_white_24dp)
) )
) )
@@ -118,10 +121,11 @@ class NavigationSettings(
.build() .build()
} }
private fun buildRowForTemplate(title: Int, toggle: Toggle): Row { private fun buildRowForTemplate(title: Int, toggle: Toggle, icon: CarIcon): Row {
return Row.Builder() return Row.Builder()
.setTitle(carContext.getString(title)) .setTitle(carContext.getString(title))
.setToggle(toggle) .setToggle(toggle)
.setImage(icon)
.build() .build()
} }

View File

@@ -62,7 +62,7 @@ dependencies {
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.maplibre.compose) implementation(libs.maplibre.compose)
implementation(libs.androidx.compose.material.icons.extended) //implementation(libs.androidx.compose.material.icons.extended)
testImplementation(libs.junit) testImplementation(libs.junit)
testImplementation(libs.mockito.core) testImplementation(libs.mockito.core)
testImplementation(libs.mockito.kotlin) testImplementation(libs.mockito.kotlin)

View File

@@ -62,7 +62,7 @@ data class StepData (
var icon: Int, var icon: Int,
var arrivalTime : Long, var arrivalTime : Long,
var leftDistance: Double, var leftDistance: Double,
var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList())), var lane: List<Lane> = listOf(Lane(location(0.0, 0.0), valid = false, indications = emptyList(), 0, 0)),
var exitNumber: Int = 0, var exitNumber: Int = 0,
var message: String = "", var message: String = "",
) )
@@ -116,7 +116,7 @@ object Constants {
val homeVogelhart = location(11.5793748, 48.185749) val homeVogelhart = location(11.5793748, 48.185749)
val homeHohenwaldeck = location( 11.594322, 48.1164817) val homeHohenwaldeck = location( 11.594322, 48.1164817)
const val NEXT_STEP_THRESHOLD = 1000.0 const val NEXT_STEP_THRESHOLD = 500.0
const val MAXIMAL_SNAP_CORRECTION = 50.0 const val MAXIMAL_SNAP_CORRECTION = 50.0

View File

@@ -15,6 +15,7 @@ data class NavigationState (
val lastLocation: Location = location(0.0, 0.0), val lastLocation: Location = location(0.0, 0.0),
val currentLocation: Location = location(0.0, 0.0), val currentLocation: Location = location(0.0, 0.0),
val routeBearing: Float = 0F, val routeBearing: Float = 0F,
// index of current route in the list of routes
val currentRouteIndex: Int = 0, val currentRouteIndex: Int = 0,
val destination: Place = Place(), val destination: Place = Place(),
val carConnection: Int = 0, val carConnection: Int = 0,

View File

@@ -48,7 +48,9 @@ class OsrmRoute {
val lane = Lane( val lane = Lane(
location(it2.location[0], it2.location[1]), location(it2.location[0], it2.location[1]),
it3.valid, it3.valid,
it3.indications it3.indications,
0,
0
) )
lanes.add(lane) lanes.add(lane)
} }

View File

@@ -4,5 +4,5 @@ import java.util.Collections
data class Intersection( data class Intersection(
val location: List<Double> = listOf(0.0, 0.0), val location: List<Double> = listOf(0.0, 0.0),
val lane : List<Lane> = Collections.emptyList<Lane>(), val lane : List<Lane> = Collections.emptyList(),
) )

View File

@@ -6,4 +6,6 @@ data class Lane (
val location: Location, val location: Location,
val valid: Boolean, val valid: Boolean,
var indications: List<String>, var indications: List<String>,
val startIndex: Int,
val endIndex: Int,
) )

View File

@@ -2,5 +2,4 @@ package com.kouros.navigation.data.route
data class Leg( data class Leg(
var steps : List<Step> = arrayListOf(), var steps : List<Step> = arrayListOf(),
var intersection: List<Intersection> = arrayListOf()
) )

View File

@@ -10,5 +10,6 @@ data class Maneuver(
val location: Location, val location: Location,
val exit: Int = 0, val exit: Int = 0,
val street: String = "", val street: String = "",
val message: String = "" val message: String = "",
val pointIndex : Int = 0,
) )

View File

@@ -35,7 +35,6 @@ class TomTomRoute {
} }
var stepDistance = 0.0 var stepDistance = 0.0
var stepDuration = 0.0 var stepDuration = 0.0
val allIntersections = mutableListOf<Intersection>()
val steps = mutableListOf<Step>() val steps = mutableListOf<Step>()
var lastPointIndex = 0 var lastPointIndex = 0
for (index in 1..<route.guidance.instructions.size) { for (index in 1..<route.guidance.instructions.size) {
@@ -56,32 +55,44 @@ class TomTomRoute {
instruction.point.longitude, instruction.point.latitude instruction.point.longitude, instruction.point.latitude
), ),
street = maneuverStreet, street = maneuverStreet,
message = instruction.message message = instruction.message,
pointIndex = instruction.pointIndex
) )
lastPointIndex = instruction.pointIndex lastPointIndex = instruction.pointIndex
val intersections = mutableListOf<Intersection>() val intersections = mutableListOf<Intersection>()
route.sections?.forEach { section -> route.sections?.forEach { section ->
if (section.sectionType == "LANES" && section.startPointIndex <= lastPointIndex && section.endPointIndex >= lastPointIndex) {
val lanes = mutableListOf<Lane>() val lanes = mutableListOf<Lane>()
var startIndex = 0 var startIndex = 0
var lastLane: Lane? = null
section.lanes?.forEach { itLane -> section.lanes?.forEach { itLane ->
val lane = Lane( val lane = Lane(
location( location = location(
waypoints[section.startPointIndex][0], waypoints[section.startPointIndex][0],
waypoints[section.startPointIndex][1] waypoints[section.startPointIndex][1]
), ),
itLane.directions.first() == itLane.follow, valid = itLane.directions.first() == itLane.follow,
itLane.directions indications = itLane.directions,
startIndex = startIndex,
endIndex = section.endPointIndex
) )
startIndex = section.startPointIndex startIndex = section.startPointIndex
if (lastLane == null
|| (!(lastLane.valid && lane.valid
&& lastLane.indications == lane.indications))
) {
lanes.add(lane) lanes.add(lane)
} }
lastLane = lane
}
intersections.add(Intersection(waypoints[startIndex], lanes)) intersections.add(Intersection(waypoints[startIndex], lanes))
} }
allIntersections.addAll(intersections) stepDistance =
stepDistance = route.guidance.instructions[index].routeOffsetInMeters - stepDistance route.guidance.instructions[index].routeOffsetInMeters - stepDistance
stepDuration = route.guidance.instructions[index].travelTimeInSeconds - stepDuration stepDuration =
route.guidance.instructions[index].travelTimeInSeconds - stepDuration
val step = Step( val step = Step(
index = stepIndex, index = stepIndex,
street = street, street = street,
@@ -95,7 +106,8 @@ class TomTomRoute {
steps.add(step) steps.add(step)
stepIndex += 1 stepIndex += 1
} }
legs.add(Leg(steps, allIntersections)) }
legs.add(Leg(steps))
val routeGeoJson = createLineStringCollection(waypoints) val routeGeoJson = createLineStringCollection(waypoints)
val centerLocation = createCenterLocation(createLineStringCollection(waypoints)) val centerLocation = createCenterLocation(createLineStringCollection(waypoints))
val newRoute = com.kouros.navigation.data.route.Routes( val newRoute = com.kouros.navigation.data.route.Routes(
@@ -184,8 +196,7 @@ class TomTomRoute {
} }
"TAKE_EXIT" -> { "TAKE_EXIT" -> {
newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SHARP_RIGHT newType = androidx.car.app.navigation.model.Maneuver.TYPE_TURN_SLIGHT_RIGHT
} }
} }
return newType return newType
@@ -195,8 +206,7 @@ class TomTomRoute {
private fun exitNumber( private fun exitNumber(
instruction: Instruction instruction: Instruction
): Int { ): Int {
return if (instruction.exitNumber == null return if (instruction.exitNumber.isNullOrEmpty()
|| instruction.exitNumber.isEmpty()
) { ) {
0 0
} else { } else {

View File

@@ -65,12 +65,15 @@ class IconMapper() {
currentTurnIcon = R.drawable.ic_roundabout_ccw currentTurnIcon = R.drawable.ic_roundabout_ccw
} }
Maneuver.TYPE_U_TURN_LEFT -> { Maneuver.TYPE_U_TURN_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_u_turn_left currentTurnIcon = R.drawable.ic_turn_u_turn_left
} }
Maneuver.TYPE_U_TURN_RIGHT -> { Maneuver.TYPE_U_TURN_RIGHT -> {
currentTurnIcon = R.drawable.ic_turn_u_turn_right currentTurnIcon = R.drawable.ic_turn_u_turn_right
} }
Maneuver.TYPE_MERGE_LEFT -> { Maneuver.TYPE_MERGE_LEFT -> {
currentTurnIcon = R.drawable.ic_turn_merge_symmetrical currentTurnIcon = R.drawable.ic_turn_merge_symmetrical
} }
@@ -136,10 +139,12 @@ class IconMapper() {
"right_slight", "slight_right" -> { "right_slight", "slight_right" -> {
when (stepData.currentManeuverType) { when (stepData.currentManeuverType) {
Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT Maneuver.TYPE_TURN_SLIGHT_RIGHT -> LaneDirection.SHAPE_NORMAL_RIGHT
Maneuver.TYPE_KEEP_RIGHT -> LaneDirection.SHAPE_SLIGHT_RIGHT
else else
-> LaneDirection.SHAPE_UNKNOWN -> LaneDirection.SHAPE_UNKNOWN
} }
} }
else -> { else -> {
LaneDirection.SHAPE_UNKNOWN LaneDirection.SHAPE_UNKNOWN
} }
@@ -151,8 +156,7 @@ class IconMapper() {
val bitmaps = mutableListOf<Bitmap>() val bitmaps = mutableListOf<Bitmap>()
stepData.lane.forEach { lane -> stepData.lane.forEach { lane ->
if (lane.indications.isNotEmpty()) { if (lane.indications.isNotEmpty()) {
Collections.sort<String>(lane.indications) val resource = laneToResource(lane.indications.sorted(), stepData)
val resource = laneToResource(lane.indications, stepData)
if (resource.isNotEmpty()) { if (resource.isNotEmpty()) {
val id = resourceId(resource) val id = resourceId(resource)
val bitMap = BitmapFactory.decodeResource(context.resources, id) val bitMap = BitmapFactory.decodeResource(context.resources, id)
@@ -222,11 +226,21 @@ class IconMapper() {
"right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x" "right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "${direction}_o" else "${direction}_x"
"left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x" "left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "${direction}_o" else "${direction}_x"
"straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT) "${direction}_o" else "${direction}_x" "straight" -> if (stepData.currentManeuverType == Maneuver.TYPE_STRAIGHT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_LEFT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_RIGHT
) "${direction}_o" else "${direction}_x"
"right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT "right_slight", "slight_right" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_RIGHT
|| stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT) "slight_right_o" else "slight_right_x" || stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_RIGHT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_RIGHT
) "slight_right_o" else "slight_right_x"
"left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT "left_slight", "slight_left" -> if (stepData.currentManeuverType == Maneuver.TYPE_TURN_SLIGHT_LEFT
|| stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT) "slight_left_o" else "slight_left_x" || stepData.currentManeuverType == Maneuver.TYPE_TURN_NORMAL_LEFT
|| stepData.currentManeuverType == Maneuver.TYPE_KEEP_LEFT
) "slight_left_o" else "slight_left_x"
else -> { else -> {
"" ""
} }

View File

@@ -550,14 +550,15 @@ class NavigationViewModel(private val repository: NavigationRepository) : ViewMo
val rp = settingsRepository.recentPlacesFlow.first() val rp = settingsRepository.recentPlacesFlow.first()
val places = mutableListOf<Place>() val places = mutableListOf<Place>()
if (rp.isNotEmpty()) { if (rp.isNotEmpty()) {
val recentPlaces = val rPlaces =
gson.fromJson(rp, Places::class.java).places.sortedBy { it.lastDate } gson.fromJson(rp, Places::class.java).places.sortedBy { it.lastDate }
for (curPlace in recentPlaces) { for (curPlace in rPlaces) {
if (curPlace.name != place.name || curPlace.category != place.category) { if (curPlace.name != place.name || curPlace.category != place.category) {
places.add(curPlace) places.add(curPlace)
} }
} }
settingsRepository.setRecentPlaces(gson.toJson(Places(places))) settingsRepository.setRecentPlaces(gson.toJson(Places(places)))
recentPlaces.value = places
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()

View File

@@ -31,7 +31,7 @@ class RouteCalculator(var routeModel: RouteModel) {
} }
} }
if (nearestDistance < NEAREST_LOCATION_DISTANCE) { if (nearestDistance < NEAREST_LOCATION_DISTANCE) {
break; break
} }
} }
} }

View File

@@ -11,6 +11,7 @@ import com.kouros.navigation.data.StepData
import com.kouros.navigation.data.route.Lane import com.kouros.navigation.data.route.Lane
import com.kouros.navigation.data.route.Leg import com.kouros.navigation.data.route.Leg
import com.kouros.navigation.data.route.Routes import com.kouros.navigation.data.route.Routes
import com.kouros.navigation.data.route.Step
import com.kouros.navigation.utils.location import com.kouros.navigation.utils.location
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@@ -29,6 +30,9 @@ open class RouteModel {
val curLeg: Leg val curLeg: Leg
get() = navState.route.routes[navState.currentRouteIndex].legs.first() get() = navState.route.routes[navState.currentRouteIndex].legs.first()
val currentStep: Step
get() = navState.route.nextStep(0)
fun startNavigation(routeString: String) { fun startNavigation(routeString: String) {
navState = navState.copy( navState = navState.copy(
route = Route.Builder() route = Route.Builder()
@@ -69,39 +73,8 @@ open class RouteModel {
navState = navState.copy(lastLocation = navState.currentLocation) navState = navState.copy(lastLocation = navState.currentLocation)
} }
fun currentStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance()
// Determine the maneuver type and corresponding icon
val currentStep = navState.route.nextStep(0)
var streetName = currentStep.maneuver.street
var curManeuverType = currentStep.maneuver.type
if (navState.nextStep) {
if (distanceToNextStep > NEXT_STEP_THRESHOLD) {
streetName = currentStep.street
curManeuverType = Maneuver.TYPE_STRAIGHT
}
}
val exitNumber = currentStep.maneuver.exit
val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType)
navState = navState.copy(maneuverType = curManeuverType)
// Construct and return the final StepData object
return StepData(
instruction = streetName,
street = currentStep.street,
leftStepDistance = distanceToNextStep,
currentManeuverType = navState.maneuverType,
icon = maneuverIcon,
arrivalTime = routeCalculator.arrivalTime(),
leftDistance = routeCalculator.travelLeftDistance(),
lane = currentLanes(),
exitNumber = exitNumber,
message = currentStep.maneuver.message
)
}
fun nextStep(): StepData { fun nextStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance() val distanceToNextStep = routeCalculator.leftStepDistance()
val currentStep = navState.route.nextStep(0)
val nextStep = navState.route.nextStep(1) val nextStep = navState.route.nextStep(1)
var streetName = nextStep.street var streetName = nextStep.street
var maneuverType = currentStep.maneuver.type var maneuverType = currentStep.maneuver.type
@@ -128,16 +101,13 @@ open class RouteModel {
private fun currentLanes(): List<Lane> { private fun currentLanes(): List<Lane> {
var lanes = emptyList<Lane>() var lanes = emptyList<Lane>()
if (navState.route.legs().isNotEmpty()) { if (navState.route.legs().isNotEmpty()) {
navState.route.legs().first().intersection.forEach { currentStep.intersection.forEach {
if (it.lane.isNotEmpty()) { if (it.lane.isNotEmpty()) {
val distance = val distance =
navState.lastLocation.distanceTo(location(it.location[0], it.location[1])) navState.lastLocation.distanceTo(location(it.location[0], it.location[1]))
val sectionBearing = if (distance < NEXT_STEP_THRESHOLD) {
navState.lastLocation.bearingTo(location(it.location[0], it.location[1]))
val bearingDeviation =
(navState.routeBearing.absoluteValue - sectionBearing.absoluteValue).absoluteValue
if (distance < NEXT_STEP_THRESHOLD && bearingDeviation < 10) {
lanes = it.lane lanes = it.lane
return@forEach
} }
} }
} }
@@ -145,6 +115,36 @@ open class RouteModel {
return lanes return lanes
} }
fun currentStep(): StepData {
val distanceToNextStep = routeCalculator.leftStepDistance()
// Determine the maneuver type and corresponding icon
var streetName = currentStep.maneuver.street
var curManeuverType = currentStep.maneuver.type
if (navState.nextStep) {
if (distanceToNextStep > NEXT_STEP_THRESHOLD) {
streetName = currentStep.street
curManeuverType = Maneuver.TYPE_STRAIGHT
}
}
val exitNumber = currentStep.maneuver.exit
val maneuverIcon = navState.iconMapper.maneuverIcon(curManeuverType)
navState = navState.copy(maneuverType = curManeuverType)
val currentLanes = currentLanes()
// Construct and return the final StepData object
return StepData(
instruction = streetName,
street = currentStep.street,
leftStepDistance = distanceToNextStep,
currentManeuverType = navState.maneuverType,
icon = maneuverIcon,
arrivalTime = routeCalculator.arrivalTime(),
leftDistance = routeCalculator.travelLeftDistance(),
lane = currentLanes,
exitNumber = exitNumber,
message = currentStep.maneuver.message
)
}
fun isNavigating(): Boolean { fun isNavigating(): Boolean {
return navState.navigating return navState.navigating
} }

View File

@@ -0,0 +1,26 @@
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,18l0,-3l-2,0l0,3l-3,0l0,2l3,0l0,3l2,0l0,-3l3,0l0,-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M18,4h2v9h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M4,4h2v16h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M11,4h2v4h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M11,10h2v4h-2z"/>
<path android:fillColor="@android:color/white" android:pathData="M11,16h2v4h-2z"/>
</vector>

View File

@@ -0,0 +1,21 @@
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20,21c-1.39,0 -2.78,-0.47 -4,-1.32c-2.44,1.71 -5.56,1.71 -8,0C6.78,20.53 5.39,21 4,21H2v2h2c1.38,0 2.74,-0.35 4,-0.99c2.52,1.29 5.48,1.29 8,0c1.26,0.65 2.62,0.99 4,0.99h2v-2H20zM3.95,19H4c1.6,0 3.02,-0.88 4,-2c0.98,1.12 2.4,2 4,2s3.02,-0.88 4,-2c0.98,1.12 2.4,2 4,2h0.05l1.9,-6.68c0.11,-0.37 0.04,-1.06 -0.66,-1.28L20,10.62V6c0,-1.1 -0.9,-2 -2,-2h-3V1H9v3H6C4.9,4 4,4.9 4,6v4.62l-1.29,0.42c-0.63,0.19 -0.81,0.84 -0.66,1.28L3.95,19zM6,6h12v3.97L12,8L6,9.97V6z"/>
</vector>

View File

@@ -0,0 +1,22 @@
<!--
Copyright 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M15,4c-4.42,0 -8,3.58 -8,8s3.58,8 8,8 8,-3.58 8,-8 -3.58,-8 -8,-8zM15,18c-3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6 6,2.69 6,6 -2.69,6 -6,6z"/>
<path android:fillColor="@android:color/white" android:pathData="M3,12c0,-2.61 1.67,-4.83 4,-5.65V4.26C3.55,5.15 1,8.27 1,12s2.55,6.85 6,7.74v-2.09c-2.33,-0.82 -4,-3.04 -4,-5.65z"/>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -2,14 +2,14 @@
agp = "9.1.0" agp = "9.1.0"
androidGpxParser = "2.3.1" androidGpxParser = "2.3.1"
androidSdkTurf = "6.0.1" androidSdkTurf = "6.0.1"
datastore = "1.2.0" datastore = "1.2.1"
gradle = "9.1.0" gradle = "9.1.0"
koinAndroid = "4.1.1" koinAndroid = "4.1.1"
koinAndroidxCompose = "4.1.1" koinAndroidxCompose = "4.1.1"
koinComposeViewmodel = "4.1.1" koinComposeViewmodel = "4.1.1"
koinCore = "4.1.1" koinCore = "4.1.1"
kotlin = "2.3.10" kotlin = "2.3.10"
coreKtx = "1.17.0" coreKtx = "1.18.0"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
@@ -21,35 +21,35 @@ material = "1.13.0"
carApp = "1.7.0" carApp = "1.7.0"
androidx-car = "1.7.0" androidx-car = "1.7.0"
materialIconsExtended = "1.7.8" materialIconsExtended = "1.7.8"
mockitoCore = "5.22.0" mockitoCore = "5.23.0"
mockitoKotlin = "6.2.3" mockitoKotlin = "6.2.3"
rules = "1.7.0" rules = "1.7.0"
runner = "1.7.0" runner = "1.7.0"
material3 = "1.4.0" material3 = "1.4.0"
runtimeLivedata = "1.10.4" runtimeLivedata = "1.10.5"
foundation = "1.10.4" foundation = "1.10.5"
maplibre-compose = "0.12.1" maplibre-compose = "0.12.1"
playServicesLocation = "21.3.0" playServicesLocation = "21.3.0"
runtime = "1.10.4" runtime = "1.10.5"
accompanist = "0.37.3" accompanist = "0.37.3"
uiVersion = "1.10.4" uiVersion = "1.10.5"
uiText = "1.10.4" uiText = "1.10.5"
navigationCompose = "2.9.7" navigationCompose = "2.9.7"
uiToolingPreview = "1.10.4" uiToolingPreview = "1.10.5"
uiTooling = "1.10.4" uiTooling = "1.10.5"
material3WindowSizeClass = "1.4.0" material3WindowSizeClass = "1.4.0"
uiGraphics = "1.10.4" uiGraphics = "1.10.5"
window = "1.5.1" window = "1.5.1"
foundationLayout = "1.10.4" foundationLayout = "1.10.5"
datastorePreferences = "1.2.0" datastorePreferences = "1.2.1"
datastoreCore = "1.2.0" datastoreCore = "1.2.1"
monitor = "1.8.0" monitor = "1.8.0"
[libraries] [libraries]
android-gpx-parser = { module = "com.github.ticofab:android-gpx-parser", version.ref = "androidGpxParser" } android-gpx-parser = { module = "com.github.ticofab:android-gpx-parser", version.ref = "androidGpxParser" }
android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" } android-sdk-turf = { module = "org.maplibre.gl:android-sdk-turf", version.ref = "androidSdkTurf" }
androidx-app-projected = { module = "androidx.car.app:app-projected" } androidx-app-projected = { module = "androidx.car.app:app-projected" }
androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } #androidx-compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" } androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastore" }
androidx-rules = { module = "androidx.test:rules", version.ref = "rules" } androidx-rules = { module = "androidx.test:rules", version.ref = "rules" }