Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create computeable callable #820

Merged
merged 13 commits into from
Feb 13, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ internal class GetEventsCallable(
}

@Serializable
public data class GetEventsError(
internal data class GetEventsError(
override val message: String,
val code: Int,
) : Throwable()
5 changes: 2 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ google-accompanist-placeholderMaterial = { module = "com.google.accompanist:acco

google-android-identity = "com.google.android.libraries.identity.googleid:googleid:1.1.0"
google-android-location = "com.google.android.gms:play-services-location:21.1.0"
google-android-maps = "com.google.android.gms:play-services-maps:18.2.0"
google-android-material = "com.google.android.material:material:1.11.0"

google-auth-http = "com.google.auth:google-auth-library-oauth2-http:1.23.0"
Expand All @@ -64,8 +63,8 @@ google-firebase-appcheck-playintegrity = { module = "com.google.firebase:firebas

google-guava-jre = "com.google.guava:guava:33.0.0-jre"

google-maps-android-compose = { module = "com.google.maps.android:maps-compose", version.ref = "google-maps-compose" }
google-maps-android-compose-widgets = { module = "com.google.maps.android:maps-compose-widgets", version.ref = "google-maps-compose" }
google-maps-android-compose = "com.google.maps.android:maps-compose:4.3.2"
google-maps-android-utils = "com.google.maps.android:android-maps-utils:3.8.2"

kotlinx-cli = "org.jetbrains.kotlinx:kotlinx-cli:0.3.6"
kotlinx-collections-immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7"
Expand Down
15 changes: 15 additions & 0 deletions map-routes/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ plugins {
id("io.ashdavies.default")
id("io.ashdavies.parcelable")
id("io.ashdavies.properties")

alias(libs.plugins.build.config)
}

android {
Expand All @@ -15,22 +17,35 @@ android {
namespace = "io.ashdavies.routes"
}

buildConfig {
val androidApiKey by stringProperty { value ->
buildConfigField("ANDROID_API_KEY", value)
}

packageName.set(android.namespace)
}

kotlin {
commonMain.dependencies {
implementation(projects.circuitSupport)
implementation(projects.httpClient)
implementation(projects.httpCommon)
implementation(projects.mapsRouting)
implementation(projects.platformSupport)

implementation(compose.material3)
implementation(compose.runtime)

implementation(libs.androidx.annotation)
implementation(libs.ktor.client.core)
implementation(libs.slack.circuit.foundation)
}

androidMain.dependencies {
implementation(libs.google.accompanist.permissions)
implementation(libs.google.android.location)
implementation(libs.google.maps.android.compose)
implementation(libs.google.maps.android.utils)
implementation(libs.kotlinx.coroutines.play.services)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,59 @@ import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.CameraPosition
import com.google.maps.android.PolyUtil
import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.MarkerState
import com.google.maps.android.compose.Polyline
import com.google.maps.android.compose.rememberCameraPositionState

public actual typealias LatLng = com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLng as GmsLatLng

private const val CAMERA_ANIMATE_DURATION = 2_000

@Composable
internal actual fun RouteMap(state: RouteMapState, modifier: Modifier) {
internal actual fun RouteMap(
state: RouteMapState,
modifier: Modifier,
onEndPosition: (LatLng) -> Unit,
) {
val cameraPositionState = rememberCameraPositionState()

LaunchedEffect(state.startPosition, state.zoomLevel) {
val cameraPosition = CameraPosition.fromLatLngZoom(state.startPosition, state.zoomLevel)
val startPosition = state.startPosition.asGmsLatLng()
val cameraPosition = CameraPosition.fromLatLngZoom(startPosition, state.zoomLevel)
val cameraUpdate = CameraUpdateFactory.newCameraPosition(cameraPosition)

cameraPositionState.animate(cameraUpdate, CAMERA_ANIMATE_DURATION)
}

GoogleMap(
cameraPositionState = cameraPositionState,
modifier = modifier.fillMaxSize(),
onMapClick = { state.endPosition = it },
onMapClick = { onEndPosition(it.asLatLng()) },
) {
Marker(
state = MarkerState(state.startPosition),
state = MarkerState(state.startPosition.asGmsLatLng()),
icon = rememberGreenMarker(),
title = "Start",
)

val endPosition = state.endPosition
if (endPosition != null) {
Marker(
state = MarkerState(endPosition),
state = MarkerState(endPosition.asGmsLatLng()),
icon = rememberRedMarker(),
title = "End",
)
}

if (state.routes.isNotEmpty()) {
Polyline(
points = state.routes.flatMap {
PolyUtil.decode(it.polyline.encodedPolyline)
},
)
}
}
}

Expand All @@ -59,3 +74,13 @@ private fun rememberGreenMarker(): BitmapDescriptor = remember {
private fun rememberRedMarker(): BitmapDescriptor = remember {
BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED)
}

private fun GmsLatLng.asLatLng() = LatLng(
latitude = latitude,
longitude = longitude,
)

private fun LatLng.asGmsLatLng() = GmsLatLng(
/* latitude = */ latitude,
/* longitude = */ longitude,
)
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package io.ashdavies.routes

import androidx.compose.runtime.remember
import com.slack.circuit.runtime.presenter.Presenter
import com.slack.circuit.runtime.ui.Ui
import io.ashdavies.circuit.presenterFactoryOf
import io.ashdavies.circuit.uiFactoryOf
import io.ashdavies.content.PlatformContext

public fun RoutePresenterFactory(context: PlatformContext): Presenter.Factory {
val locationService = LocationService(context)
return presenterFactoryOf<RouteScreen> { _, _ ->
RoutePresenter(locationService)
RoutePresenter(remember(context) { LocationService(context) })
}
}

public fun RouteUiFactory(): Ui.Factory {
return uiFactoryOf<RouteScreen, RouteScreen.State> { _, state, modifier ->
RouteScreen(state, modifier)
RouteScreen(state, modifier) { state.eventSink(RouteScreen.Event.OnEndPosition(it)) }
}
}
18 changes: 8 additions & 10 deletions map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ package io.ashdavies.routes

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import io.ashdavies.routing.ComputeRoutesResponse

@Stable
internal data class RouteMapState(
val startPosition: LatLng = KnownLocations.Berlin,
val endPosition: LatLng? = null,
val routes: List<ComputeRoutesResponse.Route> = emptyList(),
val zoomLevel: Float = 12f,
) {

var endPosition by mutableStateOf<LatLng?>(null)
}
)

@Composable
internal expect fun RouteMap(
state: RouteMapState,
modifier: Modifier = Modifier,
onEndPosition: (LatLng) -> Unit,
)

public expect class LatLng(
latitude: Double,
longitude: Double,
internal data class LatLng(
val latitude: Double,
val longitude: Double,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,17 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import io.ashdavies.http.LocalHttpClient
import io.ashdavies.routing.ComputeRoutesCallable
import io.ashdavies.routing.ComputeRoutesError
import io.ashdavies.routing.ComputeRoutesRequest
import io.ktor.client.HttpClient

@Composable
internal fun RoutePresenter(locationService: LocationService): RouteScreen.State {
internal fun RoutePresenter(
locationService: LocationService,
httpClient: HttpClient = LocalHttpClient.current,
): RouteScreen.State {
var startPosition by remember { mutableStateOf(KnownLocations.Berlin) }
val locationPermissionState = rememberLocationPermissionState()

Expand All @@ -19,9 +27,32 @@ internal fun RoutePresenter(locationService: LocationService): RouteScreen.State
}
}

val computeRoutes = remember { ComputeRoutesCallable(httpClient, BuildConfig.ANDROID_API_KEY) }
var mapState by remember { mutableStateOf(RouteMapState(startPosition)) }
var errorMessage = null as String?

LaunchedEffect(mapState.endPosition) {
val endPosition = mapState.endPosition ?: return@LaunchedEffect

val computeRoutesRequest = ComputeRoutesRequest(
origin = mapState.startPosition.asComputeRoutesRequestLatLng(),
destination = endPosition.asComputeRoutesRequestLatLng(),
)

try {
val computeRoutesResponse = computeRoutes(computeRoutesRequest)
mapState = mapState.copy(routes = computeRoutesResponse.routes)
} catch (exception: ComputeRoutesError) {
errorMessage = exception.message
}
}

return RouteScreen.State(
mapState = RouteMapState(
startPosition = startPosition,
),
mapState = mapState,
errorMessage = errorMessage,
) { }
}

private fun LatLng.asComputeRoutesRequestLatLng(): ComputeRoutesRequest.LatLng {
return ComputeRoutesRequest.LatLng(latitude, longitude)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ public fun RouteScreen(): Screen = RouteScreen

@Parcelize
internal object RouteScreen : Screen {
sealed interface Event : CircuitUiEvent
sealed interface Event : CircuitUiEvent {
data class OnEndPosition(val position: LatLng) : Event
}

data class State(
val mapState: RouteMapState,
val mapState: RouteMapState = RouteMapState(),
val errorMessage: String? = null,
val eventSink: (Event) -> Unit,
) : CircuitUiState
}
Expand All @@ -23,9 +26,11 @@ internal object RouteScreen : Screen {
internal fun RouteScreen(
state: RouteScreen.State,
modifier: Modifier = Modifier,
onEndPosition: (LatLng) -> Unit,
) {
RouteMap(
state = state.mapState,
modifier = modifier,
onEndPosition = onEndPosition,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier

public actual class LatLng actual constructor(
latitude: Double,
longitude: Double,
)

@Composable
internal actual fun RouteMap(state: RouteMapState, modifier: Modifier) {
internal actual fun RouteMap(
state: RouteMapState,
modifier: Modifier,
onEndPosition: (LatLng) -> Unit,
) {
Text("Unsupported Platform")
}
18 changes: 18 additions & 0 deletions maps-routing/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
id("io.ashdavies.default")
}

android {
namespace = "io.ashdavies.routing"
}

kotlin {
commonMain.dependencies {
implementation(projects.httpClient)
implementation(projects.httpCommon)

implementation(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.datetime)
implementation(libs.ktor.client.core)
}
}
2 changes: 2 additions & 0 deletions maps-routing/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Loading
Loading