From a2cec4a7c7a2164843a33ff0afbb1f0e8dd67a68 Mon Sep 17 00:00:00 2001 From: Ash Davies <1892070+ashdavies@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:54:57 +0100 Subject: [PATCH 1/8] Create computeable callable --- app-launcher/android/build.gradle.kts | 7 + .../ashdavies/playground/events/EventPager.kt | 6 +- .../playground/events/EventsRemoteMediator.kt | 6 +- .../playground/events/EventsService.kt | 42 ------ .../playground/events/GetEventsCallable.kt | 61 ++++++++ gradle/libs.versions.toml | 5 +- .../kotlin/io/ashdavies/http/UnaryCallable.kt | 5 + map-routes/build.gradle.kts | 15 ++ .../io/ashdavies/routes/RouteMap.android.kt | 9 ++ .../io/ashdavies/routes/RouteFactory.kt | 34 +++-- .../kotlin/io/ashdavies/routes/RouteMap.kt | 7 +- .../io/ashdavies/routes/RoutePresenter.kt | 39 ++++- .../kotlin/io/ashdavies/routes/RouteScreen.kt | 3 +- maps-routing/build.gradle.kts | 20 +++ .../src/androidMain/AndroidManifest.xml | 2 + .../routing/ComputeRoutesCallable.kt | 141 ++++++++++++++++++ settings.gradle.kts | 1 + 17 files changed, 336 insertions(+), 67 deletions(-) delete mode 100644 events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsService.kt create mode 100644 events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt create mode 100644 http-common/src/commonMain/kotlin/io/ashdavies/http/UnaryCallable.kt create mode 100644 maps-routing/build.gradle.kts create mode 100644 maps-routing/src/androidMain/AndroidManifest.xml create mode 100644 maps-routing/src/commonMain/kotlin/io/ashdavies/routing/ComputeRoutesCallable.kt diff --git a/app-launcher/android/build.gradle.kts b/app-launcher/android/build.gradle.kts index cabd0db4e..7a12da26b 100644 --- a/app-launcher/android/build.gradle.kts +++ b/app-launcher/android/build.gradle.kts @@ -20,6 +20,13 @@ android { versionCode = 1 } + packaging { + resources.excludes += setOf( + "META-INF/DEPENDENCIES", // com.google.maps:google-maps-routing + "META-INF/INDEX.LIST", // com.google.maps:google-maps-routing + ) + } + namespace = "io.ashdavies.playground" } diff --git a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventPager.kt b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventPager.kt index 009653392..d86f8ecf3 100644 --- a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventPager.kt +++ b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventPager.kt @@ -19,14 +19,14 @@ private const val DEFAULT_PAGE_SIZE = 10 @MultipleReferenceWarning internal fun rememberEventPager( eventsQueries: EventsQueries = rememberPlaygroundDatabase().eventsQueries, - eventsService: EventsService = EventsService(LocalHttpClient.current), + eventsCallable: GetEventsCallable = GetEventsCallable(LocalHttpClient.current), initialKey: String = todayAsString(), pageSize: Int = DEFAULT_PAGE_SIZE, -): Pager = remember(eventsQueries, eventsService) { +): Pager = remember(eventsQueries, eventsCallable) { Pager( config = PagingConfig(pageSize), initialKey = initialKey, - remoteMediator = EventsRemoteMediator(eventsQueries, eventsService), + remoteMediator = EventsRemoteMediator(eventsQueries, eventsCallable), pagingSourceFactory = { EventsPagingSource(eventsQueries) }, ) } diff --git a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsRemoteMediator.kt b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsRemoteMediator.kt index 15145e20b..e143388ff 100644 --- a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsRemoteMediator.kt +++ b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsRemoteMediator.kt @@ -13,7 +13,7 @@ import io.ashdavies.playground.Event as DatabaseEvent @OptIn(ExperimentalPagingApi::class) internal class EventsRemoteMediator( private val eventsQueries: EventsQueries, - private val eventsService: EventsService, + private val eventsCallable: GetEventsCallable, ) : RemoteMediator() { override suspend fun load( @@ -27,10 +27,10 @@ internal class EventsRemoteMediator( } val result: List = try { - eventsService.getEvents(loadKey) + eventsCallable(GetEventsRequest(loadKey)) } catch (exception: SocketTimeoutException) { return MediatorResult.Error(exception) - } catch (exception: ErrorResponse) { + } catch (exception: GetEventsError) { return MediatorResult.Error(exception) } diff --git a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsService.kt b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsService.kt deleted file mode 100644 index 427add167..000000000 --- a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/EventsService.kt +++ /dev/null @@ -1,42 +0,0 @@ -package io.ashdavies.playground.events - -import io.ashdavies.http.common.models.Event -import io.ktor.client.HttpClient -import io.ktor.client.call.body -import io.ktor.client.request.get -import io.ktor.http.HttpStatusCode -import kotlinx.serialization.Serializable -import io.ashdavies.http.common.models.Event as ApiEvent - -private const val PLAYGROUND_API_HOST = "playground.ashdavies.dev" - -private const val NETWORK_PAGE_SIZE = 100 - -internal interface EventsService { - suspend fun getEvents( - startAt: String? = null, - limit: Int = NETWORK_PAGE_SIZE, - ): List -} - -internal fun EventsService(client: HttpClient): EventsService = object : EventsService { - override suspend fun getEvents(startAt: String?, limit: Int): List { - val query = buildList { - if (startAt != null) add("startAt=$startAt") - add("limit=$limit") - } - - val response = client.get("https://$PLAYGROUND_API_HOST/events?startAt=${query.joinToString("&")}") - if (response.status == HttpStatusCode.OK) { - return response.body() - } - - throw response.body() - } -} - -@Serializable -internal data class ErrorResponse( - override val message: String, - val code: Int, -) : Throwable() diff --git a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt new file mode 100644 index 000000000..44bcdaef0 --- /dev/null +++ b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt @@ -0,0 +1,61 @@ +package io.ashdavies.playground.events + +import io.ashdavies.http.DefaultHttpClient +import io.ashdavies.http.UnaryCallable +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.DefaultRequest +import io.ktor.client.plugins.HttpCallValidator +import io.ktor.client.request.get +import kotlinx.serialization.Serializable +import io.ashdavies.http.common.models.Event as ApiEvent + +private const val NETWORK_PAGE_SIZE = 100 +private const val PLAYGROUND_API_HOST = "playground.ashdavies.dev" + +@Serializable +internal data class GetEventsRequest( + val startAt: String? = null, + val limit: Int = NETWORK_PAGE_SIZE, +) + +internal typealias GetEventsResponse = List + +internal class GetEventsCallable( + httpClient: HttpClient = DefaultHttpClient(), +) : UnaryCallable { + + private val httpClient = httpClient.config { + install(DefaultRequest) { + host = PLAYGROUND_API_HOST + } + + install(HttpCallValidator) { + handleResponseExceptionWithRequest { exception, _ -> + if (exception is ClientRequestException) { + throw exception.response.body() + } + } + } + + expectSuccess = true + } + + override suspend fun invoke(request: GetEventsRequest): GetEventsResponse { + val queryAsString = buildList { + if (request.startAt != null) add("startAt=${request.startAt}") + add("limit=${request.limit}") + }.joinToString("&") + + return httpClient + .get("events?$queryAsString") + .body() + } +} + +@Serializable +public data class GetEventsError( + override val message: String, + val code: Int, +) : Throwable() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 73aa4a7e9..ce842bfcb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -64,8 +64,9 @@ 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" +google-maps-routing = "com.google.maps:google-maps-routing:1.19.0" kotlinx-cli = "org.jetbrains.kotlinx:kotlinx-cli:0.3.6" kotlinx-collections-immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7" diff --git a/http-common/src/commonMain/kotlin/io/ashdavies/http/UnaryCallable.kt b/http-common/src/commonMain/kotlin/io/ashdavies/http/UnaryCallable.kt new file mode 100644 index 000000000..ac4cd9158 --- /dev/null +++ b/http-common/src/commonMain/kotlin/io/ashdavies/http/UnaryCallable.kt @@ -0,0 +1,5 @@ +package io.ashdavies.http + +public fun interface UnaryCallable { + public suspend operator fun invoke(request: Request): Response +} diff --git a/map-routes/build.gradle.kts b/map-routes/build.gradle.kts index 3a30666bd..b3ff36df2 100644 --- a/map-routes/build.gradle.kts +++ b/map-routes/build.gradle.kts @@ -3,6 +3,8 @@ plugins { id("io.ashdavies.default") id("io.ashdavies.parcelable") id("io.ashdavies.properties") + + alias(libs.plugins.build.config) } android { @@ -15,14 +17,26 @@ 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.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) } @@ -30,6 +44,7 @@ kotlin { 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) } } diff --git a/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt b/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt index 356e5d6bf..f72817d5b 100644 --- a/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt +++ b/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt @@ -9,9 +9,11 @@ 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 @@ -47,6 +49,13 @@ internal actual fun RouteMap(state: RouteMapState, modifier: Modifier) { title = "End", ) } + if (state.routes.isNotEmpty()) { + Polyline( + points = state.routes.flatMap { + PolyUtil.decode(it.polyline.encodedPolyline) + }, + ) + } } } diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt index 045c789ad..dfad776da 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt @@ -1,28 +1,40 @@ package io.ashdavies.routes +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.slack.circuit.runtime.CircuitUiState +import com.slack.circuit.runtime.Navigator import com.slack.circuit.runtime.presenter.Presenter import com.slack.circuit.runtime.presenter.presenterOf +import com.slack.circuit.runtime.screen.Screen import com.slack.circuit.runtime.ui.Ui import com.slack.circuit.runtime.ui.ui import io.ashdavies.content.PlatformContext public fun RoutePresenterFactory(context: PlatformContext): Presenter.Factory { - val locationService = LocationService(context) + return presenterFactoryOf { _, _ -> + RoutePresenter(remember(context) { LocationService(context) }) + } +} - return Presenter.Factory { screen, _, _ -> - when (screen) { - is RouteScreen -> presenterOf { RoutePresenter(locationService) } - else -> null - } +public fun RouteUiFactory(): Ui.Factory { + return uiFactoryOf { _, state, modifier -> + RouteScreen(state, modifier) } } -public fun RouteUiFactory(): Ui.Factory = Ui.Factory { screen, _ -> - when (screen) { - is RouteScreen -> ui { state, modifier -> - RouteScreen(state, modifier) - } +private inline fun presenterFactoryOf( + crossinline body: @Composable (UiScreen, Navigator) -> UiState, +): Presenter.Factory = Presenter.Factory { screen, navigator, _ -> + if (screen is UiScreen) presenterOf { body(screen, navigator) } else null +} +private inline fun uiFactoryOf( + crossinline body: @Composable (screen: UiScreen, state: UiState, modifier: Modifier) -> Unit, +): Ui.Factory = Ui.Factory { screen, _ -> + when (screen) { + is UiScreen -> ui { state, modifier -> body(screen, state, modifier) } else -> null } } diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt index 30e71f0d5..a081d1106 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt @@ -6,10 +6,12 @@ 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 routes: List = emptyList(), val zoomLevel: Float = 12f, ) { @@ -25,4 +27,7 @@ internal expect fun RouteMap( public expect class LatLng( latitude: Double, longitude: Double, -) +) { + public val latitude: Double + public val longitude: Double +} diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt index 510211ce3..b09039e07 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt @@ -7,9 +7,17 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import com.slack.circuit.runtime.CircuitUiState +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): CircuitUiState { +internal fun RoutePresenter( + locationService: LocationService, + httpClient: HttpClient = LocalHttpClient.current, +): RouteScreen.State { var startPosition by remember { mutableStateOf(KnownLocations.Berlin) } val locationPermissionState = rememberLocationPermissionState() @@ -20,9 +28,32 @@ internal fun RoutePresenter(locationService: LocationService): CircuitUiState { } } + 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) +} diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt index 66449d7a8..7b5154731 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt @@ -12,7 +12,8 @@ public object RouteScreen : Screen { public sealed interface Event : CircuitUiEvent internal data class State( - val mapState: RouteMapState, + val mapState: RouteMapState = RouteMapState(), + val errorMessage: String? = null, val eventSink: (Event) -> Unit, ) : CircuitUiState } diff --git a/maps-routing/build.gradle.kts b/maps-routing/build.gradle.kts new file mode 100644 index 000000000..0f9991dbf --- /dev/null +++ b/maps-routing/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + id("io.ashdavies.default") +} + +android { + namespace = "io.ashdavies.routing" +} + +kotlin { + commonMain.dependencies { + implementation(projects.httpClient) + implementation(projects.httpCommon) + + compileOnly(libs.google.maps.routing) + + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.datetime) + implementation(libs.ktor.client.core) + } +} diff --git a/maps-routing/src/androidMain/AndroidManifest.xml b/maps-routing/src/androidMain/AndroidManifest.xml new file mode 100644 index 000000000..8072ee00d --- /dev/null +++ b/maps-routing/src/androidMain/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/maps-routing/src/commonMain/kotlin/io/ashdavies/routing/ComputeRoutesCallable.kt b/maps-routing/src/commonMain/kotlin/io/ashdavies/routing/ComputeRoutesCallable.kt new file mode 100644 index 000000000..9c0f790c1 --- /dev/null +++ b/maps-routing/src/commonMain/kotlin/io/ashdavies/routing/ComputeRoutesCallable.kt @@ -0,0 +1,141 @@ +package io.ashdavies.routing + +import io.ashdavies.http.DefaultHttpClient +import io.ashdavies.http.UnaryCallable +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.ClientRequestException +import io.ktor.client.plugins.DefaultRequest +import io.ktor.client.plugins.HttpCallValidator +import io.ktor.client.request.header +import io.ktor.client.request.post +import io.ktor.client.request.setBody +import kotlinx.datetime.Clock +import kotlinx.serialization.Serializable + +private const val ROUTES_GOOGLE_APIS = "https://routes.googleapis.com" + +private const val HEADER_API_KEY = "X-Goog-Api-Key" +private const val HEADER_FIELD_MASK = "X-Goog-FieldMask" + +private const val FIELD_ENCODED_POLYLINE = "routes.distanceMeters,routes.duration,routes.polyline.encodedPolyline" + +public class ComputeRoutesCallable( + httpClient: HttpClient = DefaultHttpClient(), + apiKey: String, +) : UnaryCallable { + + private val httpClient = httpClient.config { + install(DefaultRequest) { + header(HEADER_API_KEY, apiKey) + header(HEADER_FIELD_MASK, FIELD_ENCODED_POLYLINE) + + url(ROUTES_GOOGLE_APIS) + } + + install(HttpCallValidator) { + handleResponseExceptionWithRequest { exception, _ -> + if (exception is ClientRequestException) { + throw exception.response.body() + } + } + } + + expectSuccess = true + } + + override suspend fun invoke( + request: ComputeRoutesRequest, + ): ComputeRoutesResponse = httpClient + .post("/directions/v2:computeRoutes") { setBody(request) } + .body() +} + +@Serializable +public data class ComputeRoutesRequest( + val origin: Origin, + val destination: Destination, + val travelMode: TravelMode, + val departureTime: String, + val languageCode: String, + val units: Units, +) { + + public constructor( + origin: LatLng, + destination: LatLng, + departureTime: String = "${Clock.System.now()}", + ) : this( + origin = Origin(Location(origin)), + destination = Destination(Location(destination)), + travelMode = TravelMode.WALK, + departureTime = departureTime, + languageCode = "en-GB", + units = Units.METRIC, + ) + + @Serializable + public data class Origin( + val location: Location, + ) + + @Serializable + public data class Destination( + val location: Location, + ) + + @Serializable + public data class Location( + val latLng: LatLng, + ) + + @Serializable + public data class LatLng( + val latitude: Double, + val longitude: Double, + ) + + @Serializable + public enum class TravelMode { + WALK, + } + + @Serializable + public enum class Units { + METRIC, + } +} + +@Serializable +public data class ComputeRoutesResponse( + val routes: List, +) { + + @Serializable + public data class Route( + val distanceMeters: Int, + val duration: String, + val polyline: Polyline, + ) + + @Serializable + public data class Polyline( + val encodedPolyline: String, + ) +} + +@Serializable +public data class ComputeRoutesError( + val error: Error, +) : Throwable() { + + override val message: String + get() = error.message + + @Serializable + public data class Error( + val code: Int, + val message: String, + val status: String, + ) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 1a43046f2..d9e24542b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -73,6 +73,7 @@ include( ":kotlin-gb", ":local-storage", ":map-routes", + ":maps-routing", ":micro-yaml", ":notion-client", ":nsd-manager", From 6c8976f4f2830a4f3d361991fb4aae0e4c608f0d Mon Sep 17 00:00:00 2001 From: Ash Davies <1892070+ashdavies@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:09:47 +0100 Subject: [PATCH 2/8] Reduce visibility of error serializable --- .../kotlin/io/ashdavies/playground/events/GetEventsCallable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt index 44bcdaef0..600c12cc7 100644 --- a/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt +++ b/events-app/src/commonMain/kotlin/io/ashdavies/playground/events/GetEventsCallable.kt @@ -55,7 +55,7 @@ internal class GetEventsCallable( } @Serializable -public data class GetEventsError( +internal data class GetEventsError( override val message: String, val code: Int, ) : Throwable() From 7bd713bf1c6321b13c199d147537af98de983702 Mon Sep 17 00:00:00 2001 From: Ash Davies <1892070+ashdavies@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:14:48 +0100 Subject: [PATCH 3/8] Remove routing dependency --- app-launcher/android/build.gradle.kts | 7 ------- gradle/libs.versions.toml | 2 -- maps-routing/build.gradle.kts | 2 -- 3 files changed, 11 deletions(-) diff --git a/app-launcher/android/build.gradle.kts b/app-launcher/android/build.gradle.kts index 7a12da26b..cabd0db4e 100644 --- a/app-launcher/android/build.gradle.kts +++ b/app-launcher/android/build.gradle.kts @@ -20,13 +20,6 @@ android { versionCode = 1 } - packaging { - resources.excludes += setOf( - "META-INF/DEPENDENCIES", // com.google.maps:google-maps-routing - "META-INF/INDEX.LIST", // com.google.maps:google-maps-routing - ) - } - namespace = "io.ashdavies.playground" } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c4d14995e..6e9483926 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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.22.0" @@ -66,7 +65,6 @@ google-guava-jre = "com.google.guava:guava:33.0.0-jre" 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" -google-maps-routing = "com.google.maps:google-maps-routing:1.19.0" kotlinx-cli = "org.jetbrains.kotlinx:kotlinx-cli:0.3.6" kotlinx-collections-immutable = "org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7" diff --git a/maps-routing/build.gradle.kts b/maps-routing/build.gradle.kts index 0f9991dbf..8bc5bd1d6 100644 --- a/maps-routing/build.gradle.kts +++ b/maps-routing/build.gradle.kts @@ -11,8 +11,6 @@ kotlin { implementation(projects.httpClient) implementation(projects.httpCommon) - compileOnly(libs.google.maps.routing) - implementation(libs.kotlinx.serialization.core) implementation(libs.kotlinx.datetime) implementation(libs.ktor.client.core) From ce7ec336d69c9314410e62d83178092b4f747bb8 Mon Sep 17 00:00:00 2001 From: "playground-manager[bot]" <126197455+playground-manager[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 09:47:58 +0100 Subject: [PATCH 4/8] chore(deps): update terraform google to v5.16.0 (#844) Co-authored-by: playground-manager[bot] <126197455+playground-manager[bot]@users.noreply.github.com> --- terraform/versions.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/versions.tf b/terraform/versions.tf index e1c142c11..1653442b2 100644 --- a/terraform/versions.tf +++ b/terraform/versions.tf @@ -12,7 +12,7 @@ terraform { google = { source = "hashicorp/google" - version = "5.15.0" + version = "5.16.0" } } From 4ea8bf8cf515063659e5757908a664eed5bbe3a4 Mon Sep 17 00:00:00 2001 From: "playground-manager[bot]" <126197455+playground-manager[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 19:32:05 +0100 Subject: [PATCH 5/8] fix(deps): update slack.circuit to v0.19.1 (#843) * fix(deps): update slack.circuit to v0.19.1 * Adjust saveable back stack with initial screen * Provide fake navigator with initial screen --------- Co-authored-by: playground-manager[bot] <126197455+playground-manager[bot]@users.noreply.github.com> Co-authored-by: Ash Davies <1892070+ashdavies@users.noreply.github.com> --- .../io/ashdavies/playground/LauncherPresenter.kt | 12 ++++++------ .../io/ashdavies/playground/LauncherPresenterTest.kt | 2 +- gradle/libs.versions.toml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app-launcher/common/src/commonMain/kotlin/io/ashdavies/playground/LauncherPresenter.kt b/app-launcher/common/src/commonMain/kotlin/io/ashdavies/playground/LauncherPresenter.kt index a333a515b..819b8d97e 100644 --- a/app-launcher/common/src/commonMain/kotlin/io/ashdavies/playground/LauncherPresenter.kt +++ b/app-launcher/common/src/commonMain/kotlin/io/ashdavies/playground/LauncherPresenter.kt @@ -38,14 +38,14 @@ internal fun LauncherPresenter(navigator: Navigator): LauncherScreen.State { } @Composable -public fun rememberSaveableBackStack(initialScreenName: String? = null): SaveableBackStack = rememberSaveableBackStack { - when (val initialScreen = initialScreenOrNull(initialScreenName)) { - is Screen -> listOf(LauncherScreen, initialScreen) - else -> listOf(LauncherScreen) - }.forEach(::push) +public fun rememberSaveableBackStack(nextScreenName: String? = null): SaveableBackStack { + return rememberSaveableBackStack(LauncherScreen) { + val nextScreen = screenOrNull(nextScreenName) + if (nextScreen != null) push(nextScreen) + } } -private fun initialScreenOrNull(name: String? = null): Screen? = name?.let { +private fun screenOrNull(name: String? = null): Screen? = name?.let { return enumValues() .firstOrNull { it.name.lowercase() == name.lowercase() } ?.screen diff --git a/app-launcher/common/src/jvmTest/kotlin/io/ashdavies/playground/LauncherPresenterTest.kt b/app-launcher/common/src/jvmTest/kotlin/io/ashdavies/playground/LauncherPresenterTest.kt index 35cd24ab8..2593df2e3 100644 --- a/app-launcher/common/src/jvmTest/kotlin/io/ashdavies/playground/LauncherPresenterTest.kt +++ b/app-launcher/common/src/jvmTest/kotlin/io/ashdavies/playground/LauncherPresenterTest.kt @@ -10,7 +10,7 @@ import kotlin.test.assertEquals internal class LauncherPresenterTest { - private val navigator = FakeNavigator() + private val navigator = FakeNavigator(LauncherScreen) @Test fun `should navigate to after party screen`() = runTest { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cb2dd1d72..4604f99f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ kotlinx-serialization = "1.6.2" ktor = "2.3.8" paging-compose = "3.2.0-alpha05-0.2.3" pinterest-ktlint = "0.50.0" -slack-circuit = "0.19.0" +slack-circuit = "0.19.1" [libraries] androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity" } From ca4f41d8ddcff51fdc43bcec1dcf7d837a3e4bbb Mon Sep 17 00:00:00 2001 From: Ash Davies <1892070+ashdavies@users.noreply.github.com> Date: Tue, 13 Feb 2024 21:30:30 +0100 Subject: [PATCH 6/8] Adjust lat lng state and callback --- .../io/ashdavies/routes/RouteMap.android.kt | 30 ++++++++++++++----- .../kotlin/io/ashdavies/routes/RouteMap.kt | 21 +++++-------- .../kotlin/io/ashdavies/routes/RouteScreen.kt | 2 +- .../io/ashdavies/routes/RouteMap.jvm.kt | 11 ++++--- 4 files changed, 36 insertions(+), 28 deletions(-) diff --git a/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt b/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt index f72817d5b..d6e6fcb5e 100644 --- a/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt +++ b/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt @@ -15,28 +15,33 @@ 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", ) @@ -44,11 +49,12 @@ internal actual fun RouteMap(state: RouteMapState, modifier: Modifier) { 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 { @@ -68,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, +) diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt index a081d1106..7d8483e3c 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteMap.kt @@ -2,32 +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 = emptyList(), val zoomLevel: Float = 12f, -) { - - var endPosition by mutableStateOf(null) -} +) @Composable internal expect fun RouteMap( state: RouteMapState, modifier: Modifier = Modifier, + onEndPosition: (LatLng) -> Unit, ) -public expect class LatLng( - latitude: Double, - longitude: Double, -) { - public val latitude: Double - public val longitude: Double -} +internal data class LatLng( + val latitude: Double, + val longitude: Double, +) diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt index 6faaa5e5e..7093f131a 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt @@ -27,6 +27,6 @@ internal fun RouteScreen( ) { RouteMap( state = state.mapState, - modifier = modifier, + modifier = modifier,, ) } diff --git a/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt b/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt index 56d98c1aa..eb86035ae 100644 --- a/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt +++ b/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt @@ -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") } From 7f31f524f099e8a06da994e51761984481c58ae4 Mon Sep 17 00:00:00 2001 From: Ash Davies <1892070+ashdavies@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:14:07 +0100 Subject: [PATCH 7/8] Move state hoisting to expect function --- .../commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt | 2 +- .../commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt index c81718b6a..5d7c44c76 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteFactory.kt @@ -15,6 +15,6 @@ public fun RoutePresenterFactory(context: PlatformContext): Presenter.Factory { public fun RouteUiFactory(): Ui.Factory { return uiFactoryOf { _, state, modifier -> - RouteScreen(state, modifier) + RouteScreen(state, modifier) { state.eventSink(RouteScreen.Event.OnEndPosition(it)) } } } diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt index 7093f131a..d4705af69 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RouteScreen.kt @@ -11,7 +11,9 @@ 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 = RouteMapState(), @@ -24,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,, + modifier = modifier, + onEndPosition = onEndPosition, ) } From ac293afb0c2f2fcfda864fecef8f64090cd7f2f6 Mon Sep 17 00:00:00 2001 From: Ash Davies <1892070+ashdavies@users.noreply.github.com> Date: Tue, 13 Feb 2024 23:27:37 +0100 Subject: [PATCH 8/8] Commas --- .../androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt | 2 +- .../src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt | 1 - .../src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt b/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt index d6e6fcb5e..1fc6f13a5 100644 --- a/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt +++ b/map-routes/src/androidMain/kotlin/io/ashdavies/routes/RouteMap.android.kt @@ -23,7 +23,7 @@ private const val CAMERA_ANIMATE_DURATION = 2_000 internal actual fun RouteMap( state: RouteMapState, modifier: Modifier, - onEndPosition: (LatLng) -> Unit + onEndPosition: (LatLng) -> Unit, ) { val cameraPositionState = rememberCameraPositionState() diff --git a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt index b09039e07..80164f5af 100644 --- a/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt +++ b/map-routes/src/commonMain/kotlin/io/ashdavies/routes/RoutePresenter.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import com.slack.circuit.runtime.CircuitUiState import io.ashdavies.http.LocalHttpClient import io.ashdavies.routing.ComputeRoutesCallable import io.ashdavies.routing.ComputeRoutesError diff --git a/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt b/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt index eb86035ae..c5f605fbd 100644 --- a/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt +++ b/map-routes/src/jvmMain/kotlin/io/ashdavies/routes/RouteMap.jvm.kt @@ -8,7 +8,7 @@ import androidx.compose.ui.Modifier internal actual fun RouteMap( state: RouteMapState, modifier: Modifier, - onEndPosition: (LatLng) -> Unit + onEndPosition: (LatLng) -> Unit, ) { Text("Unsupported Platform") }