Preview
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
81
app/src/main/java/com/kouros/navigation/ui/SheetLayout.kt
Normal file
81
app/src/main/java/com/kouros/navigation/ui/SheetLayout.kt
Normal file
@@ -0,0 +1,81 @@
|
||||
package com.kouros.navigation.ui
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.BottomSheetScaffold
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SheetLayout(
|
||||
map: @Composable (PaddingValues) -> Unit,
|
||||
menu: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val sheetState = rememberBottomSheetScaffoldState()
|
||||
BottomSheetScaffold(
|
||||
sheetPeekHeight = 180.dp,
|
||||
scaffoldState = sheetState,
|
||||
sheetSwipeEnabled = true,
|
||||
sheetDragHandle = {
|
||||
ExpandCollapseButton(
|
||||
sheetState.bottomSheetState.targetValue == SheetValue.Expanded,
|
||||
onExpand = { sheetState.bottomSheetState.expand() },
|
||||
onCollapse = { sheetState.bottomSheetState.partialExpand() },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
},
|
||||
sheetContent = {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier.background(BottomSheetDefaults.ContainerColor)
|
||||
.consumeWindowInsets(PaddingValues(top = 56.dp))
|
||||
.requiredHeight(500.dp)
|
||||
) {
|
||||
menu()
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
) { padding ->
|
||||
map(padding)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpandCollapseButton(
|
||||
expanded: Boolean,
|
||||
onExpand: suspend () -> Unit,
|
||||
onCollapse: suspend () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val degrees by animateFloatAsState(targetValue = if (expanded) 180f else 0f)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
IconButton(
|
||||
modifier = modifier,
|
||||
onClick = { coroutineScope.launch { if (expanded) onCollapse() else onExpand() } },
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(com.kouros.android.cars.carappservice.R.drawable.keyboard_arrow_up_24px),
|
||||
contentDescription = if (expanded) "Collapse" else "Expand",
|
||||
modifier = Modifier.rotate(degrees),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,9 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
180
app/src/main/java/com/kouros/navigation/ui/search/SearchSheet.kt
Normal file
180
app/src/main/java/com/kouros/navigation/ui/search/SearchSheet.kt
Normal file
@@ -0,0 +1,180 @@
|
||||
package com.kouros.navigation.ui.search
|
||||
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.SearchBarDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.kouros.data.R
|
||||
import com.kouros.navigation.data.Place
|
||||
import com.kouros.navigation.data.PlaceColor
|
||||
import com.kouros.navigation.model.NavigationViewModel
|
||||
import com.kouros.navigation.utils.location
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SearchSheet(
|
||||
applicationContext: Context,
|
||||
navController: NavHostController,
|
||||
viewModel: NavigationViewModel,
|
||||
location: Location,
|
||||
closeSheet: () -> Unit
|
||||
) {
|
||||
val recentPlaces = viewModel.recentPlaces.observeAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
) {
|
||||
SearchBar(
|
||||
modifier = Modifier.clickable {
|
||||
navController.navigate("search")
|
||||
},
|
||||
colors = SearchBarDefaults.colors(
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer
|
||||
),
|
||||
expanded = false,
|
||||
onExpandedChange = { navController.navigate("search") },
|
||||
inputField = {
|
||||
SearchBarDefaults.InputField(
|
||||
enabled = false,
|
||||
modifier = Modifier.clickable {
|
||||
navController.navigate("search")
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.search_48px),
|
||||
applicationContext.getString(R.string.search_action_title),
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
},
|
||||
query = applicationContext.getString(R.string.search_action_title),
|
||||
onQueryChange = { },
|
||||
onSearch = {
|
||||
|
||||
},
|
||||
expanded = false,
|
||||
onExpandedChange = { },
|
||||
placeholder = { applicationContext.getString(R.string.search_action_title) }
|
||||
)
|
||||
},
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
|
||||
if (recentPlaces.value != null) {
|
||||
val items = listOf(recentPlaces)
|
||||
if (items.isNotEmpty()) {
|
||||
Column(Modifier.padding(all = 10.dp)) {
|
||||
Text(applicationContext.getString(R.string.recent_destinations))
|
||||
RecentPlaces(
|
||||
recentPlaces.value!!,
|
||||
viewModel,
|
||||
applicationContext,
|
||||
location,
|
||||
closeSheet
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Home(
|
||||
applicationContext: Context,
|
||||
viewModel: NavigationViewModel,
|
||||
location: Location,
|
||||
closeSheet: () -> Unit
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Button(onClick = {
|
||||
val places = viewModel.loadRecentPlace(applicationContext)
|
||||
val toLocation = location(places.first()!!.longitude, places.first()!!.latitude)
|
||||
viewModel.loadRoute(applicationContext, location, toLocation, 0F)
|
||||
closeSheet()
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_place_white_24dp),
|
||||
"Home",
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
Text("Home")
|
||||
}
|
||||
Button(onClick = {
|
||||
}) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_favorite_white_24dp),
|
||||
"Work",
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
Text("Arbeit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
private fun RecentPlaces(
|
||||
recentPlaces: List<Place>,
|
||||
viewModel: NavigationViewModel,
|
||||
context: Context,
|
||||
location: Location,
|
||||
closeSheet: () -> Unit
|
||||
) {
|
||||
val color = remember { PlaceColor }
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(recentPlaces, key = { it.id }) { place ->
|
||||
Row {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_place_white_24dp),
|
||||
"Navigation",
|
||||
tint = color.copy(alpha = 1f),
|
||||
modifier = Modifier.size(24.dp, 24.dp),
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = { Text("${place.street} ${place.postalCode} ${place.city}") },
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
val toLocation = location(place.longitude, place.latitude)
|
||||
viewModel.loadRoute(context, location, toLocation, 0F)
|
||||
closeSheet()
|
||||
}
|
||||
.fillMaxWidth()
|
||||
)
|
||||
HorizontalDivider(color = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user