diff --git a/android/MapLibreUI/build.gradle b/android/MapLibreUI/build.gradle
index fe4a739a..57881ded 100644
--- a/android/MapLibreUI/build.gradle
+++ b/android/MapLibreUI/build.gradle
@@ -45,6 +45,9 @@ dependencies {
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
+
+ implementation 'org.ramani-maps:ramani-maplibre:0.2.0'
implementation project(':core')
diff --git a/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/NavigationMapView.kt b/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/NavigationMapView.kt
new file mode 100644
index 00000000..0213d5b3
--- /dev/null
+++ b/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/NavigationMapView.kt
@@ -0,0 +1,32 @@
+package com.stadiamaps.ferrostar.maplibreui
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.ui.Modifier
+import com.mapbox.mapboxsdk.geometry.LatLng
+import com.stadiamaps.ferrostar.core.NavigationViewModel
+import org.ramani.compose.Circle
+import org.ramani.compose.MapLibre
+import org.ramani.compose.Polyline
+
+@Composable
+fun NavigationMapView(
+ viewModel: NavigationViewModel
+) {
+ val uiState = viewModel.uiState.collectAsState()
+
+ MapLibre(modifier = Modifier.fillMaxSize()) {
+ Circle(
+ center = LatLng(
+ uiState.value.snappedLocation.coordinates.lat,
+ uiState.value.snappedLocation.coordinates.lng
+ ), radius = 10f, color = "Blue"
+ )
+ Polyline(
+ points = uiState.value.routeGeometry.map { LatLng(it.lat, it.lng) },
+ color = "Red",
+ lineWidth = 5f
+ )
+ }
+}
diff --git a/android/build.gradle b/android/build.gradle
index f61c255b..86e3ecec 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -1,7 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
- id 'com.android.application' version '8.1.2' apply false
- id 'com.android.library' version '8.1.2' apply false
+ id 'com.android.application' version '8.2.0' apply false
+ id 'com.android.library' version '8.2.0' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
id 'com.github.willir.rust.cargo-ndk-android' version '0.3.4' apply false
}
\ No newline at end of file
diff --git a/android/compose-ui/build.gradle b/android/compose-ui/build.gradle
new file mode 100644
index 00000000..fe4a739a
--- /dev/null
+++ b/android/compose-ui/build.gradle
@@ -0,0 +1,59 @@
+plugins {
+ id 'com.android.library'
+ id 'org.jetbrains.kotlin.android'
+}
+
+android {
+ namespace 'com.stadiamaps.ferrostar.maplibreui'
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 29
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion '1.3.2'
+ }
+}
+
+dependencies {
+ implementation 'androidx.core:core-ktx:1.9.0'
+ implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'androidx.activity:activity-compose:1.7.2'
+ implementation platform('androidx.compose:compose-bom:2022.10.00')
+ implementation 'androidx.compose.ui:ui'
+ implementation 'androidx.compose.ui:ui-graphics'
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ implementation 'androidx.compose.material3:material3'
+
+ implementation project(':core')
+
+ testImplementation 'junit:junit:4.13.2'
+
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+ androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
+
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+}
\ No newline at end of file
diff --git a/android/compose-ui/consumer-rules.pro b/android/compose-ui/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/android/compose-ui/proguard-rules.pro b/android/compose-ui/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/android/compose-ui/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/android/compose-ui/src/main/AndroidManifest.xml b/android/compose-ui/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/android/compose-ui/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt b/android/compose-ui/src/main/java/com/stadiamaps/ferrostar/compose/BannerView.kt
similarity index 97%
rename from android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt
rename to android/compose-ui/src/main/java/com/stadiamaps/ferrostar/compose/BannerView.kt
index a06d92df..bc6a7294 100644
--- a/android/MapLibreUI/src/main/java/com/stadiamaps/ferrostar/maplibreui/BannerView.kt
+++ b/android/compose-ui/src/main/java/com/stadiamaps/ferrostar/compose/BannerView.kt
@@ -1,4 +1,4 @@
-package com.stadiamaps.ferrostar.maplibreui
+package com.stadiamaps.ferrostar.compose
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
-import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -83,7 +82,7 @@ fun BannerView(instructions: VisualInstruction, distanceToNextManeuver: Double?)
}
}
-@Preview()
+@Preview
@Composable
fun PreviewBannerView() {
val instructions = VisualInstruction(
diff --git a/android/compose-ui/src/test/java/com/stadiamaps/ferrostar/compose/ExampleUnitTest.kt b/android/compose-ui/src/test/java/com/stadiamaps/ferrostar/compose/ExampleUnitTest.kt
new file mode 100644
index 00000000..4251dc78
--- /dev/null
+++ b/android/compose-ui/src/test/java/com/stadiamaps/ferrostar/compose/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.stadiamaps.ferrostar.compose
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
\ No newline at end of file
diff --git a/android/core/build.gradle b/android/core/build.gradle
index 28767fb4..9a8a39ca 100644
--- a/android/core/build.gradle
+++ b/android/core/build.gradle
@@ -8,12 +8,12 @@ plugins {
android {
namespace 'com.stadiamaps.ferrostar.core'
- compileSdk 33
+ compileSdk 34
ndkVersion "25.2.9519653"
defaultConfig {
minSdk 29
- targetSdk 33
+ targetSdk 34
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
diff --git a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt
index 86bc8299..b6b5eb73 100644
--- a/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt
+++ b/android/core/src/main/java/com/stadiamaps/ferrostar/core/NavigationViewModel.kt
@@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import uniffi.ferrostar.Disposable
+import uniffi.ferrostar.GeographicCoordinate
import uniffi.ferrostar.NavigationControllerInterface
import uniffi.ferrostar.TripState
import uniffi.ferrostar.UserLocation
@@ -13,7 +14,8 @@ import java.util.concurrent.Executors
data class NavigationUiState(
val snappedLocation: UserLocation,
- val heading: Float?
+ val heading: Float?,
+ val routeGeometry: List
)
/**
@@ -29,12 +31,20 @@ class NavigationViewModel(
private val navigationController: NavigationControllerInterface,
private val locationProvider: LocationProvider,
initialUserLocation: Location,
+ routeGeometry: List,
) : ViewModel(), LocationUpdateListener {
// TODO: Is this the best executor?
private val _executor = Executors.newSingleThreadExecutor()
private var _state = navigationController.getInitialState(initialUserLocation.userLocation())
- // TODO: UI state flow?
- private val _uiState = MutableStateFlow(NavigationUiState(snappedLocation = initialUserLocation.userLocation(), heading = null))
+
+ private val _uiState = MutableStateFlow(
+ NavigationUiState(
+ snappedLocation = initialUserLocation.userLocation(),
+ // TODO: Heading/course over ground
+ heading = null,
+ routeGeometry = routeGeometry
+ )
+ )
val uiState: StateFlow = _uiState.asStateFlow()
init {
@@ -51,7 +61,12 @@ class NavigationViewModel(
}
override fun onLocationUpdated(location: Location) {
- update(newState = navigationController.updateUserLocation(location = location.userLocation(), state = _state), location = location)
+ update(
+ newState = navigationController.updateUserLocation(
+ location = location.userLocation(),
+ state = _state
+ ), location = location
+ )
}
override fun onHeadingUpdated(heading: Float) {
diff --git a/android/demo-app/build.gradle b/android/demo-app/build.gradle
index b70325fe..8c6d410e 100644
--- a/android/demo-app/build.gradle
+++ b/android/demo-app/build.gradle
@@ -57,6 +57,7 @@ dependencies {
implementation 'androidx.compose.material3:material3'
implementation project(':core')
+ implementation project(':MapLibreUI')
implementation platform("com.squareup.okhttp3:okhttp-bom:4.10.0")
implementation 'com.squareup.okhttp3:okhttp'
diff --git a/android/demo-app/src/main/java/com/stadiamaps/ferrostar/MainActivity.kt b/android/demo-app/src/main/java/com/stadiamaps/ferrostar/MainActivity.kt
index 7a1d5be8..5866279c 100644
--- a/android/demo-app/src/main/java/com/stadiamaps/ferrostar/MainActivity.kt
+++ b/android/demo-app/src/main/java/com/stadiamaps/ferrostar/MainActivity.kt
@@ -6,30 +6,73 @@ import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.tooling.preview.Preview
import com.stadiamaps.ferrostar.core.FerrostarCore
+import com.stadiamaps.ferrostar.core.NavigationViewModel
+import com.stadiamaps.ferrostar.core.SimulatedLocation
import com.stadiamaps.ferrostar.core.SimulatedLocationProvider
+import com.stadiamaps.ferrostar.maplibreui.NavigationMapView
import com.stadiamaps.ferrostar.ui.theme.FerrostarTheme
import okhttp3.OkHttpClient
+import uniffi.ferrostar.GeographicCoordinate
+import uniffi.ferrostar.ManeuverModifier
+import uniffi.ferrostar.ManeuverType
+import uniffi.ferrostar.NavigationController
+import uniffi.ferrostar.NavigationControllerConfig
+import uniffi.ferrostar.Route
+import uniffi.ferrostar.RouteStep
+import uniffi.ferrostar.StepAdvanceMode
+import uniffi.ferrostar.VisualInstruction
+import uniffi.ferrostar.VisualInstructionContent
import java.net.URL
+import java.time.Instant
class MainActivity : ComponentActivity() {
- // TODO: Create a view model instead
- val locationProvider = SimulatedLocationProvider()
- val httpClient = OkHttpClient.Builder().build()
+ // TODO: Move to some sort of test fixtures
+ private val geom = listOf(
+ GeographicCoordinate(-122.41970699999999, 37.807770999999995),
+ GeographicCoordinate(-122.42041599999999, 37.807680999999995),
+ GeographicCoordinate(-122.42040399999999, 37.807623),
+ GeographicCoordinate(-122.420678, 37.807587),
+ )
+ // Maybe the core should create the view model and expose it via a property...
+ private val simulatedLocation =
+ SimulatedLocation(geom.first(), 6.0, null, Instant.now())
+ private val locationProvider = SimulatedLocationProvider()
+ private val httpClient = OkHttpClient.Builder().build()
+
// TODO: Something useful. This is just a placeholder that essentially checks our ability to load the Rust library
val core = FerrostarCore(
- valhallaEndpointURL = URL("https://api.stadiamaps.com/navigate/v1?api_key=YOUR-KEY-HERE"),
+ valhallaEndpointURL = URL("https://api.stadiamaps.com/route/v1?api_key=YOUR-KEY-HERE"),
profile = "pedestrian",
httpClient = httpClient
)
+ private val route = Route(
+ geometry = geom,
+ distance = 100.0,
+ steps = listOf(
+ RouteStep(
+ geom, 100.0, "Jefferson St.", "Walk west on Jefferson St.", listOf(
+ VisualInstruction(
+ VisualInstructionContent(
+ "Hyde Street",
+ ManeuverType.TURN,
+ ManeuverModifier.LEFT,
+ null
+ ),
+ null,
+ 42.0
+ )
+ ),
+ listOf()
+ )
+ ),
+ waypoints = listOf(geom.first(), geom.last())
+ )
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
setContent {
FerrostarTheme {
// A surface container using the 'background' color from the theme
@@ -37,26 +80,25 @@ class MainActivity : ComponentActivity() {
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
- Greeting("Android")
+ NavigationMapView(
+ // TODO: This constructor pattern is pretty whack. It's also probably the wrong way to create the ViewModel.
+ viewModel = NavigationViewModel(
+ NavigationController(
+ route = route,
+ config = NavigationControllerConfig(
+ StepAdvanceMode.RelativeLineStringDistance(
+ 40U,
+ 15U
+ )
+ )
+ ),
+ locationProvider,
+ simulatedLocation,
+ geom
+ )
+ )
}
}
}
}
}
-
-@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
- Text(
- text = "Hello $name! 🦀 says 2 + 2 = 4",
- modifier = modifier,
- textAlign = TextAlign.Center,
- )
-}
-
-@Preview(showBackground = true)
-@Composable
-fun GreetingPreview() {
- FerrostarTheme {
- Greeting("Android")
- }
-}
\ No newline at end of file
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 8c63f280..f42067c0 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
#Thu Apr 27 20:24:21 KST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/android/settings.gradle b/android/settings.gradle
index c756bfe3..4d4fd94e 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -14,5 +14,6 @@ dependencyResolutionManagement {
}
rootProject.name = "Ferrostar"
include ':core'
+include ':compose-ui'
include ':demo-app'
include ':MapLibreUI'