Skip to content

Commit

Permalink
First *extremely* rough pass at Compose UI scaffolding for Android
Browse files Browse the repository at this point in the history
  • Loading branch information
ianthetechie committed Dec 17, 2023
1 parent d009f29 commit 65d0f1b
Show file tree
Hide file tree
Showing 15 changed files with 232 additions and 38 deletions.
3 changes: 3 additions & 0 deletions android/MapLibreUI/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down
Original file line number Diff line number Diff line change
@@ -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
)
}
}
4 changes: 2 additions & 2 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -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
}
59 changes: 59 additions & 0 deletions android/compose-ui/build.gradle
Original file line number Diff line number Diff line change
@@ -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'
}
Empty file.
21 changes: 21 additions & 0 deletions android/compose-ui/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions android/compose-ui/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.stadiamaps.ferrostar.maplibreui
package com.stadiamaps.ferrostar.compose

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
Expand All @@ -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
Expand Down Expand Up @@ -83,7 +82,7 @@ fun BannerView(instructions: VisualInstruction, distanceToNextManeuver: Double?)
}
}

@Preview()
@Preview
@Composable
fun PreviewBannerView() {
val instructions = VisualInstruction(
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
4 changes: 2 additions & 2 deletions android/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@ 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
import java.util.concurrent.Executors

data class NavigationUiState(
val snappedLocation: UserLocation,
val heading: Float?
val heading: Float?,
val routeGeometry: List<GeographicCoordinate>
)

/**
Expand All @@ -29,12 +31,20 @@ class NavigationViewModel(
private val navigationController: NavigationControllerInterface,
private val locationProvider: LocationProvider,
initialUserLocation: Location,
routeGeometry: List<GeographicCoordinate>,
) : 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<NavigationUiState> = _uiState.asStateFlow()

init {
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions android/demo-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,99 @@ 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
Surface(
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")
}
}
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 65d0f1b

Please sign in to comment.