This commit is contained in:
Dimitris
2026-03-12 15:34:34 +01:00
parent 61ce09f393
commit 619ceb9f83
28 changed files with 1815 additions and 634 deletions

View File

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

View File

@@ -1,7 +1,6 @@
package com.kouros.navigation.ui
import android.Manifest
import android.annotation.SuppressLint
import android.app.AppOpsManager
import android.location.LocationManager
import android.os.Bundle
@@ -11,32 +10,22 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
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.FloatingActionButton
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.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
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.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
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.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.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.bearing
@@ -80,8 +71,10 @@ import kotlinx.coroutines.runBlocking
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location
import org.maplibre.compose.location.UserLocationState
import org.maplibre.compose.location.rememberDefaultLocationProvider
import org.maplibre.compose.location.rememberUserLocationState
import org.maplibre.compose.style.BaseStyle
import org.maplibre.spatialk.geojson.Position
import kotlin.time.Duration.Companion.seconds
@@ -108,7 +101,7 @@ class MainActivity : ComponentActivity() {
routeModel.navState = routeModel.navState.copy(routingEngine = routingEngine)
routeModel.startNavigation(newRoute)
routeData.value = routeModel.curRoute.routeGeoJson
// checkMock()
// checkMock()
}
}
@@ -121,6 +114,7 @@ class MainActivity : ComponentActivity() {
SimulationType.GPX -> gpx(
context = applicationContext, mock
)
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
}
}
@@ -178,30 +172,19 @@ class MainActivity : ComponentActivity() {
}
enableEdgeToEdge()
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)
@Composable
fun StartScreen(
navController: NavHostController) {
navController: NavHostController
) {
val appViewModel: AppViewModel = appViewModel()
val darkMode by appViewModel.darkMode.collectAsState()
@@ -209,6 +192,10 @@ class MainActivity : ComponentActivity() {
val locationProvider = rememberDefaultLocationProvider(
updateInterval = 0.5.seconds, desiredAccuracy = DesiredAccuracy.Highest
)
val lastRoute by appViewModel.lastRoute.collectAsState()
if (lastRoute.isNotEmpty()) {
navigationViewModel.route.value = lastRoute
}
val userLocationState = rememberUserLocationState(locationProvider)
if (!useMock) {
val locationState = locationProvider.location.collectAsState()
@@ -217,112 +204,41 @@ class MainActivity : ComponentActivity() {
val step: StepData? by stepData.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) {
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetShape = RoundedCornerShape(
bottomStart = 0.dp,
bottomEnd = 0.dp,
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 = {
isUpdated = false
}
)
}
) {
SheetContent(step, nextStep) { closeSheet() }
}
},
sheetPeekHeight = height.dp
) {
MapView(
applicationContext,
userLocationState,
step,
nextStep,
cameraPosition,
routeData,
tilt,
baseStyle,
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
SheetLayout(
map = { _ ->
Map(
userLocationState, step, nextStep, baseStyle, navController
)
},
menu = { SheetContent(navController, step, nextStep) },
)
if (!routeModel.isNavigating()) {
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
private fun Map(
userLocationState: UserLocationState,
step: StepData?,
nextStep: StepData?,
baseStyle: BaseStyle.Json,
navController: NavHostController
) {
MapView(
applicationContext,
userLocationState,
step,
nextStep,
cameraPosition,
routeData,
tilt,
baseStyle,
)
if (!routeModel.isNavigating()) {
Settings(navController, modifier = Modifier.fillMaxWidth())
}
AppNavGraph(this)
}
@Composable
@@ -347,10 +263,10 @@ class MainActivity : ComponentActivity() {
@Composable
fun SheetContent(
step: StepData?, nextStep: StepData?, closeSheet: () -> Unit
navController: NavHostController, step: StepData?, nextStep: StepData?
) {
if (!routeModel.isNavigating()) {
SearchSheet(applicationContext, navigationViewModel, lastLocation) { closeSheet() }
SearchSheet(applicationContext, navController, navigationViewModel, lastLocation) { }
} else {
if (step != null) {
NavigationSheet(
@@ -358,7 +274,7 @@ class MainActivity : ComponentActivity() {
routeModel,
step,
nextStep,
{ stopNavigation { closeSheet() } },
{ stopNavigation {} },
{ simulateNavigation() })
}
}
@@ -446,8 +362,7 @@ class MainActivity : ComponentActivity() {
fun simulateNavigation() {
simulate(
routeModel = routeModel,
mock = mock
routeModel = routeModel, mock = mock
)
}

View File

@@ -1,6 +1,7 @@
package com.kouros.navigation.ui
import android.content.Context
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.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.rememberCameraState
import org.maplibre.compose.location.LocationTrackingEffect
@@ -62,6 +64,8 @@ fun MapView(
val showBuildings by appViewModel.show3D.collectAsState()
val darkMode by appViewModel.darkMode.collectAsState()
val dark = darkMode == 1 || darkMode == 2 && isSystemInDarkTheme()
Column {
NavigationInfo(step, nextStep)
Box(contentAlignment = Alignment.Center) {
@@ -88,7 +92,7 @@ fun MapView(
duration = 1.seconds
)
}
NavigationImage(paddingValues, width, height / 6, "", darkMode)
NavigationImage(paddingValues, width, height / 6, "", dark)
}
}
}

View File

@@ -1,5 +1,7 @@
package com.kouros.navigation.ui
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.provider.Settings
import androidx.compose.animation.animateContentSize
@@ -37,6 +39,25 @@ import androidx.core.net.toUri
*
* 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)
@Composable
fun PermissionScreen(

View 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),
)
}
}

View File

@@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.ui.MainActivity
import com.kouros.navigation.ui.search.SearchScreen
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("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() }
}
}
}

View File

@@ -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.Row

View File

@@ -1,4 +1,4 @@
package com.kouros.navigation.ui
package com.kouros.navigation.ui.navigation
import android.content.Context
import androidx.compose.foundation.layout.Arrangement

View File

@@ -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.location.Location
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.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.foundation.text.input.TextFieldState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SearchBar
import androidx.compose.material3.SearchBarDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
@@ -35,119 +37,93 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
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.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.data.Place
import com.kouros.navigation.data.PlaceColor
import com.kouros.navigation.data.nominatim.SearchResult
import com.kouros.navigation.model.NavigationViewModel
import com.kouros.navigation.ui.theme.NavigationTheme
import com.kouros.navigation.utils.location
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchSheet(
applicationContext: Context,
viewModel: NavigationViewModel,
fun SearchScreen(
navController: NavHostController,
context: Context,
navigationViewModel: NavigationViewModel,
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() }
)
Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
if (recentPlaces.value != null) {
val items = listOf(recentPlaces)
if (items.isNotEmpty()) {
RecentPlaces(
recentPlaces.value!!,
viewModel,
applicationContext,
location,
closeSheet
NavigationTheme(true) {
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
stringResource(id = R.string.search_action_title),
)
},
navigationIcon = {
IconButton(onClick = function) {
Icon(
painter = painterResource(R.drawable.arrow_back_24px),
contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.size(48.dp, 48.dp),
)
}
},
)
},
)
{ padding ->
val scrollState = rememberScrollState()
Column(Modifier.padding(top = 50.dp)) {
SearchBar(context, navigationViewModel, location)
Categories(context, navigationViewModel, 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")
}
}
}
private fun searchPlaces(viewModel: NavigationViewModel, location: Location, it: String) {
viewModel.searchPlaces(it, location)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SearchBar(
textFieldState: TextFieldState,
searchPlaces: List<Place>,
searchResults: List<SearchResult>,
viewModel: NavigationViewModel,
context: Context,
navigationViewModel: NavigationViewModel,
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) }
val focusRequester = remember { FocusRequester() }
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
SearchBar(
windowInsets = WindowInsets.captionBar,
colors = SearchBarDefaults.colors(
containerColor = MaterialTheme.colorScheme.secondaryContainer
),
inputField = {
SearchBarDefaults.InputField(
modifier = Modifier.focusRequester(focusRequester),
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.search_48px),
@@ -158,24 +134,64 @@ fun SearchBar(
query = textFieldState.text.toString(),
onQueryChange = { textFieldState.edit { replace(0, length, it) } },
onSearch = {
searchPlaces(viewModel, location, it)
expanded = false
navigationViewModel.searchPlaces(it, location)
expanded = true
},
expanded = expanded,
onExpandedChange = { expanded = it },
onExpandedChange = {
//expanded = it
},
placeholder = { Text(context.getString(R.string.search_action_title)) }
)
},
expanded = expanded,
onExpandedChange = { },
) {
if (searchPlaces.isNotEmpty()) {
Text(context.getString(R.string.recent_destinations))
RecentPlaces(searchPlaces, viewModel, context, location, closeSheet)
}
if (searchResults.isNotEmpty()) {
Text("Search places")
SearchPlaces( searchResults, viewModel, context, location, closeSheet)
SearchPlaces(searchResults, navigationViewModel, context, location, { })
}
}
}
@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,56 +209,7 @@ private fun SearchPlaces(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
if (searchResults.isNotEmpty()) {
items(searchResults, key = { it.placeId }) { 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.displayName) },
modifier = Modifier
.clickable {
val pl = Place(
name = place.name,
longitude = place.lon.toDouble(),
latitude = place.lat.toDouble(),
postalCode = place.address.postcode,
city = place.address.city,
street = place.address.road
)
viewModel.saveRecent(context,pl)
val toLocation =
location(place.lon.toDouble(), place.lat.toDouble())
viewModel.loadRoute(context, location, toLocation, 0F)
closeSheet()
}
.fillMaxWidth()
)
HorizontalDivider(color = Color.Gray)
}
}
}
}
}
@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 ->
items(searchResults, key = { it.placeId }) { place ->
Row {
Icon(
painter = painterResource(id = R.drawable.ic_place_white_24dp),
@@ -251,10 +218,20 @@ private fun RecentPlaces(
modifier = Modifier.size(24.dp, 24.dp),
)
ListItem(
headlineContent = { Text("${place.street} ${place.postalCode} ${place.city}") },
headlineContent = { Text(place.displayName) },
modifier = Modifier
.clickable {
val toLocation = location(place.longitude, place.latitude)
val pl = Place(
name = place.name,
longitude = place.lon.toDouble(),
latitude = place.lat.toDouble(),
postalCode = place.address.postcode,
city = place.address.city,
street = place.address.road
)
viewModel.saveRecent(context, pl)
val toLocation =
location(place.lon.toDouble(), place.lat.toDouble())
viewModel.loadRoute(context, location, toLocation, 0F)
closeSheet()
}
@@ -265,3 +242,5 @@ private fun RecentPlaces(
}
}
}

View 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)
}
}
}
}

View File

@@ -33,7 +33,7 @@ import com.kouros.navigation.ui.theme.NavigationTheme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DisplayScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
fun DisplayScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
val darkMode by viewModel.darkMode.collectAsState()
val show3D by viewModel.show3D.collectAsState()
@@ -47,13 +47,13 @@ fun DisplayScreen(viewModel: SettingsViewModel, navigateBack: () -> Unit) {
)
},
navigationIcon = {
IconButton(onClick = navigateBack) {
Icon(
painter = painterResource(R.drawable.arrow_back_24px),
contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.size(48.dp, 48.dp),
)
}
IconButton(onClick = navigateBack) {
Icon(
painter = painterResource(R.drawable.arrow_back_24px),
contentDescription = stringResource(id = R.string.accept_action_title),
modifier = Modifier.size(48.dp, 48.dp),
)
}
},
)
},

View File

@@ -2,7 +2,7 @@ package com.kouros.navigation.ui.theme
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_primaryContainer = Color(0xFFFFDDB3)
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_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_primaryContainer = Color(0xFF633F00)
val md_theme_dark_onPrimaryContainer = Color(0xFFFFDDB3)

View File

@@ -102,7 +102,7 @@ fun NavigationTheme(
}
MaterialTheme(
colorScheme = if (useDarkTheme) darkColorScheme() else colorScheme,
colorScheme = if (useDarkTheme) DarkColors else colorScheme,
typography = typography,
content = content,
shapes = shapes,