Skip to content

Commit

Permalink
Move towards better StreetPreview UI
Browse files Browse the repository at this point in the history
The direction choices are now provided as a flow to the UI which can
change depending on the heading and choices. Simple buttons for now.
  • Loading branch information
davecraig committed Feb 5, 2025
1 parent 32e332a commit dd74314
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 176 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import dagger.hilt.android.scopes.ActivityRetainedScoped
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import org.scottishtecharmy.soundscape.geoengine.StreetPreviewState
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt
import org.scottishtecharmy.soundscape.services.SoundscapeBinder
import org.scottishtecharmy.soundscape.services.SoundscapeService
Expand All @@ -33,7 +34,7 @@ class SoundscapeServiceConnection @Inject constructor() {
fun getBeaconFlow(): StateFlow<LngLatAlt?>? {
return soundscapeService?.beaconFlow
}
fun getStreetPreviewModeFlow(): StateFlow<Boolean>? {
fun getStreetPreviewModeFlow(): StateFlow<StreetPreviewState>? {
return soundscapeService?.streetPreviewFlow
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import com.squareup.otto.Subscribe
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -44,7 +43,6 @@ import org.scottishtecharmy.soundscape.services.getOttoBus
import org.scottishtecharmy.soundscape.utils.getCurrentLocale
import java.util.Locale
import kotlin.math.abs
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.TimeSource

data class PositionedString(val text : String, val location : LngLatAlt? = null, val earcon : String? = null)
Expand Down Expand Up @@ -441,18 +439,8 @@ class GeoEngine {
}
}

fun streetPreviewGo() {
if(true) {
streetPreviewGoInternal()
} else {
// Random walker for StreetPreview
CoroutineScope(Job()).launch {
repeat(1000) {
streetPreviewGoWander()
delay(200.milliseconds)
}
}
}
fun streetPreviewGo() : List<StreetPreviewChoice> {
return streetPreviewGoInternal()
}

private fun streetPreviewGoWander() {
Expand All @@ -473,7 +461,7 @@ class GeoEngine {
heading = choices.random().heading
if(!lastHeading.isNaN()) {
// If we came in on a road, then try and keep going in that direction
val trimmedChoices = mutableListOf<StreetPreview.StreetPreviewChoice>()
val trimmedChoices = mutableListOf<StreetPreviewChoice>()
for (choice in choices) {
if ((choice.heading != lastHeading) && (!choice.heading.isNaN())) {
// Don't add the road we just came in on, or any with a NaN heading.
Expand All @@ -499,15 +487,23 @@ class GeoEngine {
}
}

private fun streetPreviewGoInternal() {
private fun streetPreviewGoInternal() : List<StreetPreviewChoice> {
// Run the code within the treeContext to protect it from changes to the trees whilst it's
// running.
// running. We want to return the new set of choices so that these can be sent up to the UI.
val engine = this
CoroutineScope(Job()).launch(gridState.treeContext) {
// Get our current location and figure out what GO means
val userGeometry = getCurrentUserGeometry(UserGeometry.HeadingMode.Phone)
streetPreview.go(userGeometry, engine)
val results = runBlocking {
withContext(gridState.treeContext) {
// Get our current location and figure out what GO means
val userGeometry = getCurrentUserGeometry(UserGeometry.HeadingMode.Phone)
val newLocation = streetPreview.go(userGeometry, engine)
if(newLocation != null) {
streetPreview.getDirectionChoices(engine, newLocation)
} else {
streetPreview.getDirectionChoices(engine, userGeometry.location)
}
}
}
return results
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.scottishtecharmy.soundscape.geoengine
import android.util.Log
import org.scottishtecharmy.soundscape.geoengine.utils.RoadDirectionAtIntersection
import org.scottishtecharmy.soundscape.geoengine.utils.bearingFromTwoPoints
import org.scottishtecharmy.soundscape.geoengine.utils.calculateHeadingOffset
import org.scottishtecharmy.soundscape.geoengine.utils.getDirectionAtIntersection
import org.scottishtecharmy.soundscape.geoengine.utils.getIntersectionRoadNames
import org.scottishtecharmy.soundscape.geoengine.utils.splitRoadAtNode
Expand All @@ -11,7 +12,17 @@ import org.scottishtecharmy.soundscape.geojsonparser.geojson.Feature
import org.scottishtecharmy.soundscape.geojsonparser.geojson.FeatureCollection
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LineString
import org.scottishtecharmy.soundscape.geojsonparser.geojson.LngLatAlt
import kotlin.math.abs

data class StreetPreviewChoice(
val heading: Double,
val name: String,
val route: List<LngLatAlt>
)

data class StreetPreviewState(
val enabled: Boolean = false,
val choices: List<StreetPreviewChoice> = emptyList()
)

class StreetPreview {

Expand All @@ -20,12 +31,6 @@ class StreetPreview {
AT_NODE(1)
}

data class StreetPreviewChoice(
val heading: Double,
val name: String,
val route: List<LngLatAlt>
)

private var previewState = PreviewState.INITIAL
private var previewRoad: StreetPreviewChoice? = null

Expand All @@ -35,13 +40,13 @@ class StreetPreview {
previewState = PreviewState.INITIAL
}

fun go(userGeometry: UserGeometry, engine: GeoEngine) {
fun go(userGeometry: UserGeometry, engine: GeoEngine) : LngLatAlt? {
when (previewState) {

PreviewState.INITIAL -> {
// Jump to a node on the nearest road or path
val road = engine.gridState.getNearestFeature(TreeId.ROADS_AND_PATHS, userGeometry.location, Double.POSITIVE_INFINITY)
?: return
?: return null

var nearestDistance = Double.POSITIVE_INFINITY
var nearestPoint = LngLatAlt()
Expand All @@ -58,6 +63,7 @@ class StreetPreview {
// We've got a location, so jump to it
engine.locationProvider.updateLocation(nearestPoint, 0.0F)
previewState = PreviewState.AT_NODE
return nearestPoint
}
}

Expand All @@ -68,28 +74,26 @@ class StreetPreview {
var bestHeadingDiff = Double.POSITIVE_INFINITY

// Find the choice with the closest heading to our own
var diff: Double
for ((index, choice) in choices.withIndex()) {
diff = abs(choice.heading - userGeometry.heading())
val diff = calculateHeadingOffset(choice.heading, userGeometry.heading())
if (diff < bestHeadingDiff) {
bestHeadingDiff = diff
bestIndex = index
}
Log.d(TAG, "Choice: ${choice.name} heading: ${choice.heading}")
}
// Check that the closest heading is close enough
if (bestHeadingDiff < 30.0) {

// We've got a road - let's head down it
previewRoad = extendChoice(engine, userGeometry.location, choices[bestIndex])
previewRoad?.let { road ->
engine.locationProvider.updateLocation(road.route.last(), 1.0F)
lastHeading = bearingOfLineFromEnd(road.route.last(), road.route)
}
previewState = PreviewState.AT_NODE

// We've got a road - let's head down it
previewRoad = extendChoice(engine, userGeometry.location, choices[bestIndex])
previewRoad?.let { road ->
engine.locationProvider.updateLocation(road.route.last(), 1.0F)
lastHeading = bearingOfLineFromEnd(road.route.last(), road.route)
return road.route.last()
}
previewState = PreviewState.AT_NODE
}
}
return null
}

private fun bearingOfLineFromEnd(location: LngLatAlt, line: List<LngLatAlt>): Double {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1322,4 +1322,18 @@ fun nearestPointOnBoundingBox(box: BoundingBox, point: LngLatAlt): LngLatAlt {
val nearestLng = clip(point.longitude, box.westLongitude, box.eastLongitude)

return LngLatAlt(nearestLng, nearestLat)
}

/**
* calculateHeadingOffset calculates the angle between two headings e.g. the user heading and the
* heading of a road.
* @param heading1
* @param heading2
* @return The inner angle between the two headings in degrees.
*/
fun calculateHeadingOffset(heading1: Double, heading2: Double): Double {
var diff = abs(heading1 - heading2) % 360.0
if (diff > 180.0) diff = 360.0 - diff

return diff
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ fun HomeScreen(
searchItems = state.value.searchItems.orEmpty(),
shareLocation = { viewModel.shareLocation(context) },
rateSoundscape = rateSoundscape,
streetPreviewEnabled = state.value.streetPreviewMode,
streetPreviewState = state.value.streetPreviewState,
streetPreviewGo = { viewModel.streetPreviewGo() },
streetPreviewExit = { viewModel.streetPreviewExit() },
tileGridGeoJson = state.value.tileGridGeoJson,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.LocationOff
import androidx.compose.material.icons.rounded.LocationOn
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material.icons.rounded.PlayCircle
import androidx.compose.material.icons.rounded.PlayCircleOutline
import androidx.compose.material.icons.rounded.Preview
import androidx.compose.material3.DrawerState
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
Expand All @@ -31,7 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.heading
import androidx.compose.ui.semantics.semantics
Expand All @@ -43,6 +39,7 @@ import org.maplibre.android.geometry.LatLng
import org.scottishtecharmy.soundscape.MainActivity
import org.scottishtecharmy.soundscape.R
import org.scottishtecharmy.soundscape.components.MainSearchBar
import org.scottishtecharmy.soundscape.geoengine.StreetPreviewState
import org.scottishtecharmy.soundscape.screens.home.DrawerContent
import org.scottishtecharmy.soundscape.screens.home.data.LocationDescription
import org.scottishtecharmy.soundscape.screens.home.locationDetails.generateLocationDetailsRoute
Expand All @@ -64,7 +61,9 @@ fun HomePreview() {
getWhatsAheadOfMe = {},
shareLocation = {},
rateSoundscape = {},
streetPreviewEnabled = false,
streetPreviewState = StreetPreviewState(false),
streetPreviewExit = {},
streetPreviewGo = {},
tileGridGeoJson = "",
searchText = "Lille",
isSearching = true,
Expand All @@ -88,7 +87,9 @@ fun Home(
getWhatsAheadOfMe: () -> Unit,
shareLocation: () -> Unit,
rateSoundscape: () -> Unit,
streetPreviewEnabled: Boolean,
streetPreviewState: StreetPreviewState,
streetPreviewGo: () -> Unit,
streetPreviewExit: () -> Unit,
modifier: Modifier = Modifier,
tileGridGeoJson: String,
searchText: String,
Expand Down Expand Up @@ -117,8 +118,7 @@ fun Home(
topBar = {
HomeTopAppBar(
drawerState,
coroutineScope,
streetPreviewEnabled,
coroutineScope
)
},
bottomBar = {
Expand Down Expand Up @@ -165,6 +165,9 @@ fun Home(
onMapLongClick = onMapLongClick,
onMarkerClick = onMarkerClick,
tileGridGeoJson = tileGridGeoJson,
streetPreviewState = streetPreviewState,
streetPreviewGo = streetPreviewGo,
streetPreviewExit = streetPreviewExit
)
}
}
Expand All @@ -174,8 +177,7 @@ fun Home(
@Composable
fun HomeTopAppBar(
drawerState: DrawerState,
coroutineScope: CoroutineScope,
streetPreviewEnabled: Boolean,
coroutineScope: CoroutineScope
) {
val context = LocalContext.current
TopAppBar(
Expand Down Expand Up @@ -205,63 +207,6 @@ fun HomeTopAppBar(
},
actions = {
var serviceRunning by remember { mutableStateOf(true) }
IconButton(
enabled = streetPreviewEnabled,
onClick = {
if(streetPreviewEnabled) {
(context as MainActivity).soundscapeServiceConnection.streetPreviewGo()
}
},
) {
if (streetPreviewEnabled) {
Icon(
Icons.Rounded.PlayCircle,
tint = MaterialTheme.colorScheme.primary,
contentDescription = "StreetPreview play"
)
} else {
Icon(
Icons.Rounded.PlayCircleOutline,
tint = MaterialTheme.colorScheme.secondary,
contentDescription = "StreetPreview play disabled"
)
}
}
IconToggleButton(
checked = streetPreviewEnabled,
enabled = true,
onCheckedChange = { state ->
if (!state) {
(context as MainActivity).soundscapeServiceConnection.setStreetPreviewMode(
false,
)
}
},
) {
if (streetPreviewEnabled) {
Icon(
Icons.Rounded.PlayCircle,
tint = MaterialTheme.colorScheme.primary,
contentDescription = "StreetPreview play"
)
Icon(
Icons.Rounded.Preview,
tint = MaterialTheme.colorScheme.primary,
contentDescription = stringResource(R.string.street_preview_enabled),
)
} else {
Icon(
Icons.Rounded.PlayCircleOutline,
tint = MaterialTheme.colorScheme.secondary,
contentDescription = "StreetPreview play disabled"
)
Icon(
painterResource(R.drawable.preview_off),
tint = MaterialTheme.colorScheme.secondary,
contentDescription = stringResource(R.string.street_preview_disabled),
)
}
}
IconToggleButton(
checked = serviceRunning,
enabled = true,
Expand Down
Loading

0 comments on commit dd74314

Please sign in to comment.