SheetContent Simulation

This commit is contained in:
Dimitris
2026-02-25 09:48:39 +01:00
parent e4b539c4e6
commit 5a6165dff8
13 changed files with 384 additions and 147 deletions

View File

@@ -99,6 +99,7 @@ dependencies {
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.compose.foundation.layout)
implementation(libs.androidx.compose.foundation)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

View File

@@ -98,4 +98,5 @@ class MockLocation (private var locationManager: LocationManager) {
}
}
}
}

View File

@@ -0,0 +1,132 @@
package com.kouros.navigation.model
import android.content.Context
import com.kouros.data.R
import com.kouros.navigation.MainApplication.Companion.navigationViewModel
import com.kouros.navigation.utils.location
import io.ticofab.androidgpxparser.parser.GPXParser
import io.ticofab.androidgpxparser.parser.domain.Gpx
import io.ticofab.androidgpxparser.parser.domain.TrackSegment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.joda.time.DateTime
import kotlin.collections.forEach
fun simulate(routeModel: RouteModel, mock: MockLocation) {
CoroutineScope(Dispatchers.IO).launch {
var lastLocation = location(0.0, 0.0)
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
val curLocation = location(waypoint[0], waypoint[1])
if (routeModel.isNavigating()) {
val deviation = 0.0
if (index in 0..routeModel.curRoute.waypoints.size) {
val bearing = lastLocation.bearingTo(curLocation)
mock.setMockLocation(waypoint[1], waypoint[0], bearing)
Thread.sleep(1000)
}
}
lastLocation = curLocation
}
}
}
fun test(applicationContext: Context, routeModel: RouteModel) {
for ((index, step) in routeModel.curLeg.steps.withIndex()) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation(
applicationContext,
location(waypoint[0], waypoint[1]), navigationViewModel
)
val step = routeModel.currentStep()
val nextStep = routeModel.nextStep()
println("Step: ${step.instruction} ${step.leftStepDistance} ${nextStep.currentManeuverType}")
}
}
}
fun testSingle(applicationContext: Context, routeModel: RouteModel, mock: MockLocation) {
testSingleUpdate(
applicationContext,
48.185976,
11.578463,
routeModel,
mock
) // Silcherstr. 23-13
testSingleUpdate(
applicationContext,
48.186712,
11.578574,
routeModel,
mock
) // Silcherstr. 27-33
testSingleUpdate(
applicationContext,
48.186899,
11.580480,
routeModel,
mock
) // Schmalkadenerstr. 24-28
}
fun testSingleUpdate(
applicationContext: Context,
latitude: Double,
longitude: Double,
routeModel: RouteModel,
mock: MockLocation
) {
if (1 == 1) {
mock.setMockLocation(latitude, longitude, 0F)
} else {
routeModel.updateLocation(
applicationContext,
location(longitude, latitude), navigationViewModel
)
}
val step = routeModel.currentStep()
val nextStep = routeModel.nextStep()
Thread.sleep(1_000)
}
fun gpx(context: Context, mock: MockLocation) {
CoroutineScope(Dispatchers.IO).launch {
var lastLocation = location(0.0, 0.0)
val parser = GPXParser()
val input = context.resources.openRawResource(R.raw.vh)
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
parsedGpx?.let {
val tracks = parsedGpx.tracks
tracks.forEach { tr ->
val segments: MutableList<TrackSegment?>? = tr.trackSegments
segments!!.forEach { seg ->
var lastTime = DateTime.now()
seg!!.trackPoints.forEach { p ->
val curLocation = location(p.longitude, p.latitude)
val ext = p.extensions
val speed: Double?
if (ext != null) {
speed = ext.speed
mock.curSpeed = speed.toFloat()
}
val duration = p.time.millis - lastTime.millis
val bearing = lastLocation.bearingTo(curLocation)
println("Bearing $bearing")
mock.setMockLocation(p.latitude, p.longitude, bearing)
if (duration > 0) {
delay(duration / 5)
}
lastTime = p.time
lastLocation = curLocation
}
}
}
}
}
}
enum class SimulationType {
SIMULATE, TEST, GPX, TEST_SINGLE
}

View File

@@ -1,10 +1,8 @@
package com.kouros.navigation.ui
import NavigationSheet
import android.Manifest
import android.annotation.SuppressLint
import android.app.AppOpsManager
import android.content.Context
import android.location.LocationManager
import android.os.Bundle
import android.os.Process
@@ -18,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
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
@@ -56,6 +55,11 @@ import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.BaseStyleModel
import com.kouros.navigation.model.MockLocation
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.model.SimulationType
import com.kouros.navigation.model.gpx
import com.kouros.navigation.model.simulate
import com.kouros.navigation.model.test
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
@@ -64,14 +68,7 @@ import com.kouros.navigation.utils.GeoUtils.snapLocation
import com.kouros.navigation.utils.bearing
import com.kouros.navigation.utils.getSettingsViewModel
import com.kouros.navigation.utils.location
import io.ticofab.androidgpxparser.parser.GPXParser
import io.ticofab.androidgpxparser.parser.domain.Gpx
import io.ticofab.androidgpxparser.parser.domain.TrackSegment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.joda.time.DateTime
import org.maplibre.compose.camera.CameraPosition
import org.maplibre.compose.location.DesiredAccuracy
import org.maplibre.compose.location.Location
@@ -86,7 +83,7 @@ class MainActivity : ComponentActivity() {
val routeModel = RouteModel()
var tilt = 50.0
val useMock = false
val type = 3 // 1 simulate 2 test 3 gpx 4 testSingle
val type = SimulationType.GPX
val stepData: MutableLiveData<StepData> by lazy {
MutableLiveData()
@@ -94,6 +91,7 @@ class MainActivity : ComponentActivity() {
val nextStepData: MutableLiveData<StepData> by lazy {
MutableLiveData()
}
var lastLocation = location(0.0, 0.0)
val observer = Observer<String> { newRoute ->
if (newRoute.isNotEmpty()) {
@@ -101,13 +99,12 @@ class MainActivity : ComponentActivity() {
routeData.value = routeModel.curRoute.routeGeoJson
if (useMock) {
when (type) {
1 -> simulate()
2 -> test()
3 -> gpx(
context = applicationContext
SimulationType.SIMULATE -> simulate(routeModel, mock)
SimulationType.TEST -> test(applicationContext, routeModel)
SimulationType.GPX -> gpx(
context = applicationContext, mock
)
4 -> testSingle()
SimulationType.TEST_SINGLE -> testSingle(applicationContext, routeModel, mock)
}
}
}
@@ -187,11 +184,12 @@ class MainActivity : ComponentActivity() {
val appViewModel: AppViewModel = appViewModel()
val darkMode by appViewModel.darkMode.collectAsState()
val sheetPeekHeight = 250.dp
val baseStyle = BaseStyleModel().readStyle(applicationContext, darkMode, darkMode == 1)
val scaffoldState = rememberBottomSheetScaffoldState()
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
val sheetPeekHeight = 250.dp
val sheetPeekHeightState = remember { mutableStateOf(sheetPeekHeight) }
val locationProvider = rememberDefaultLocationProvider(
@@ -209,7 +207,7 @@ class MainActivity : ComponentActivity() {
fun closeSheet() {
scope.launch {
scaffoldState.bottomSheetState.partialExpand()
sheetPeekHeightState.value = sheetPeekHeight
sheetPeekHeightState.value = 50.dp
}
}
NavigationTheme(useDarkTheme = darkMode == 1) {
@@ -217,7 +215,7 @@ class MainActivity : ComponentActivity() {
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
scaffoldState = scaffoldState,
scaffoldState = scaffoldState,
sheetPeekHeight = sheetPeekHeightState.value,
sheetContent = {
SheetContent(latitude, step, nextStep) { closeSheet() }
@@ -363,7 +361,10 @@ class MainActivity : ComponentActivity() {
}
fun simulateNavigation() {
simulate()
simulate(
routeModel = routeModel,
mock = mock
)
}
private fun checkMockLocationEnabled() {
@@ -385,95 +386,5 @@ class MainActivity : ComponentActivity() {
e.printStackTrace()
}
}
}
fun simulate() {
CoroutineScope(Dispatchers.IO).launch {
var lastLocation = location(0.0, 0.0)
for ((index, waypoint) in routeModel.curRoute.waypoints.withIndex()) {
val curLocation = location(waypoint[0], waypoint[1])
if (routeModel.isNavigating()) {
val deviation = 0.0
if (index in 0..routeModel.curRoute.waypoints.size) {
val bearing = lastLocation.bearingTo(curLocation)
mock.setMockLocation(waypoint[1], waypoint[0], bearing)
Thread.sleep(1000)
}
}
lastLocation = curLocation
}
}
}
fun test() {
for ((index, step) in routeModel.curLeg.steps.withIndex()) {
//if (index in 3..3) {
for ((windex, waypoint) in step.maneuver.waypoints.withIndex()) {
routeModel.updateLocation(
applicationContext,
location(waypoint[0], waypoint[1]), navigationViewModel
)
val step = routeModel.currentStep()
val nextStep = routeModel.nextStep()
println("Step: ${step.instruction} ${step.leftStepDistance} ${nextStep.currentManeuverType}")
}
//}
}
}
fun testSingle() {
testSingleUpdate(48.185976, 11.578463) // Silcherstr. 23-13
testSingleUpdate(48.186712, 11.578574) // Silcherstr. 27-33
testSingleUpdate(48.186899, 11.580480) // Schmalkadenerstr. 24-28
}
fun testSingleUpdate(latitude: Double, longitude: Double) {
if (1 == 1) {
mock.setMockLocation(latitude, longitude, 0F)
} else {
routeModel.updateLocation(
applicationContext,
location(longitude, latitude), navigationViewModel
)
}
val step = routeModel.currentStep()
val nextStep = routeModel.nextStep()
Thread.sleep(1_000)
}
fun gpx(context: Context) {
CoroutineScope(Dispatchers.IO).launch {
var lastLocation = location(0.0, 0.0)
val parser = GPXParser()
val input = context.resources.openRawResource(R.raw.vh)
val parsedGpx: Gpx? = parser.parse(input) // consider using a background thread
parsedGpx?.let {
val tracks = parsedGpx.tracks
tracks.forEach { tr ->
val segments: MutableList<TrackSegment?>? = tr.trackSegments
segments!!.forEach { seg ->
var lastTime = DateTime.now()
seg!!.trackPoints.forEach { p ->
val curLocation = location(p.longitude, p.latitude)
val ext = p.extensions
val speed: Double?
if (ext != null) {
speed = ext.speed
mock.curSpeed = speed.toFloat()
}
val duration = p.time.millis - lastTime.millis
val bearing = lastLocation.bearingTo(curLocation)
println("Bearing $bearing")
mock.setMockLocation(p.latitude, p.longitude, bearing)
if (duration > 0) {
delay(duration / 5)
}
lastTime = p.time
lastLocation = curLocation
}
}
}
}
}
}
}

View File

@@ -1,3 +1,5 @@
package com.kouros.navigation.ui
import android.content.Context
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -13,9 +15,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.graphics.drawable.IconCompat
import com.kouros.data.R
import com.kouros.navigation.data.Constants.NEXT_STEP_THRESHOLD
import com.kouros.navigation.data.StepData
import com.kouros.navigation.model.RouteModel
import com.kouros.navigation.utils.formatDateTime

View File

@@ -61,6 +61,7 @@ fun SearchSheet(
.fillMaxWidth()
.wrapContentHeight()
) {
// Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
SearchBar(
textFieldState = textFieldState,
searchPlaces = emptyList(),
@@ -71,7 +72,7 @@ fun SearchSheet(
closeSheet = { closeSheet() }
)
//Home(applicationContext, viewModel, location, closeSheet = { closeSheet() })
if (recentPlaces.value != null) {
val items = listOf(recentPlaces)
if (items.isNotEmpty()) {
@@ -142,7 +143,7 @@ fun SearchBar(
SearchBarDefaults.InputField(
leadingIcon = {
Icon(
painter = painterResource(id = R.drawable.search_48px),
painter = painterResource(id = R.drawable.speed_camera_24px),
"Search",
modifier = Modifier.size(24.dp, 24.dp),
)
@@ -166,8 +167,8 @@ fun SearchBar(
RecentPlaces(searchPlaces, viewModel, context, location, closeSheet)
}
if (searchResults.isNotEmpty()) {
Text("Search places")
SearchPlaces(searchResults, viewModel, context, location, closeSheet)
Text("Search places")
SearchPlaces(searchResults, viewModel, context, location, closeSheet)
}
}
}
@@ -186,7 +187,7 @@ private fun SearchPlaces(
) {
val color = remember { PlaceColor }
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 24.dp),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
if (searchResults.isNotEmpty()) {
@@ -199,7 +200,7 @@ private fun SearchPlaces(
modifier = Modifier.size(24.dp, 24.dp),
)
ListItem(
headlineContent = { Text("${place.address.road} ${place.address.postcode}") },
headlineContent = { Text(place.displayName) },
modifier = Modifier
.clickable {
val pl = Place(
@@ -235,7 +236,7 @@ private fun RecentPlaces(
) {
val color = remember { PlaceColor }
LazyColumn(
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 24.dp),
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 10.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
items(recentPlaces, key = { it.id }) { place ->