From 1d8119854d3b1b05cad5d6f2d3006dc2bed91c76 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 16:51:51 -0400 Subject: [PATCH 01/12] feat: Add a ScaleBar composable component --- app/src/main/AndroidManifest.xml | 3 + .../maps/android/compose/MainActivity.kt | 7 + .../maps/android/compose/ScaleBarActivity.kt | 99 +++++++ app/src/main/res/values/strings.xml | 1 + maps-compose/build.gradle | 4 +- .../android/compose/components/ScaleBar.kt | 279 ++++++++++++++++++ 6 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt create mode 100644 maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 69904cab..7532eb52 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,6 +52,9 @@ + diff --git a/app/src/main/java/com/google/maps/android/compose/MainActivity.kt b/app/src/main/java/com/google/maps/android/compose/MainActivity.kt index ad7a75b0..4ca344be 100644 --- a/app/src/main/java/com/google/maps/android/compose/MainActivity.kt +++ b/app/src/main/java/com/google/maps/android/compose/MainActivity.kt @@ -81,6 +81,13 @@ class MainActivity : ComponentActivity() { }) { Text(getString(R.string.location_tracking_activity)) } + Spacer(modifier = Modifier.padding(5.dp)) + Button( + onClick = { + context.startActivity(Intent(context, ScaleBarActivity::class.java)) + }) { + Text(getString(R.string.scale_bar_activity)) + } } } } diff --git a/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt b/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt new file mode 100644 index 00000000..daf18935 --- /dev/null +++ b/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt @@ -0,0 +1,99 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.maps.android.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.components.DisappearingScaleBar +import com.google.maps.android.compose.components.ScaleBar + +private const val TAG = "ScaleBarActivity" + +private const val zoom = 8f +private val singapore = LatLng(1.35, 103.87) +private val defaultCameraPosition = CameraPosition.fromLatLngZoom(singapore, zoom) + +class ScaleBarActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + var isMapLoaded by remember { mutableStateOf(false) } + + // To control and observe the map camera + val cameraPositionState = rememberCameraPositionState { + position = defaultCameraPosition + } + + Box(Modifier.fillMaxSize()) { + GoogleMap( + modifier = Modifier.matchParentSize(), + cameraPositionState = cameraPositionState, + onMapLoaded = { + isMapLoaded = true + } + ) + DisappearingScaleBar( + modifier = Modifier + .padding(top = 5.dp, end = 15.dp) + .align(Alignment.TopStart), + cameraPositionState = cameraPositionState + ) + ScaleBar( + modifier = Modifier + .padding(top = 5.dp, end = 15.dp) + .align(Alignment.TopEnd), + cameraPositionState = cameraPositionState + ) + if (!isMapLoaded) { + AnimatedVisibility( + modifier = Modifier + .matchParentSize(), + visible = !isMapLoaded, + enter = EnterTransition.None, + exit = fadeOut() + ) { + CircularProgressIndicator( + modifier = Modifier + .background(MaterialTheme.colors.background) + .wrapContentSize() + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 49f6e6e9..51a91f28 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -21,4 +21,5 @@ Map In Column Map Clustering Location Tracking + Scale Bar \ No newline at end of file diff --git a/maps-compose/build.gradle b/maps-compose/build.gradle index a29ec320..696137a3 100644 --- a/maps-compose/build.gradle +++ b/maps-compose/build.gradle @@ -36,9 +36,11 @@ android { dependencies { implementation "androidx.compose.foundation:foundation:$compose_version" + implementation 'androidx.compose.material:material:1.1.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'com.google.android.gms:play-services-maps:18.0.2' - implementation 'com.google.maps.android:maps-ktx:3.3.0' + implementation 'com.google.maps.android:maps-ktx:3.4.0' + implementation 'com.google.maps.android:maps-utils-ktx:3.3.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt new file mode 100644 index 00000000..f6575120 --- /dev/null +++ b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt @@ -0,0 +1,279 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.maps.android.compose.components + +import android.graphics.Point +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.core.MutableTransitionState +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment.Companion.End +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shadow +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.CameraPositionState +import com.google.maps.android.ktx.utils.sphericalDistance +import kotlinx.coroutines.delay + +private val DarkGray = Color(0xFF3a3c3b) + +/** + * A scale bar composable that shows the current scale of the map in feet and meters when zoomed in + * to map, changing to miles and kilometers, respectively, when zooming out. + * + * Implement your own observer on camera move events using [CameraPositionState] and pass it in + * as [cameraPositionState]. + */ +@Composable +public fun ScaleBar( + modifier: Modifier = Modifier, + cameraPositionState: CameraPositionState, + textColor: Color = DarkGray, + lineColor: Color = DarkGray, + shadowColor: Color = Color.White, +) { + Box( + modifier = modifier + .size(width = 65.dp, height = 50.dp) + ) { + var horizontalLineWidthMeters by remember { + mutableStateOf(0) + } + + Canvas( + modifier = Modifier.fillMaxSize(), + onDraw = { + // Get width of canvas in meters + val upperLeftLatLng = + cameraPositionState.projection?.fromScreenLocation(Point(0, 0)) + ?: LatLng(0.0, 0.0) + val upperRightLatLng = + cameraPositionState.projection?.fromScreenLocation(Point(0, size.width.toInt())) + ?: LatLng(0.0, 0.0) + val canvasWidthMeters = upperLeftLatLng.sphericalDistance(upperRightLatLng) + val eightNinthsCanvasMeters = (canvasWidthMeters * 8 / 9).toInt() + + horizontalLineWidthMeters = eightNinthsCanvasMeters + + val oneNinthWidth = size.width / 9 + val midHeight = size.height / 2 + val oneThirdHeight = size.height / 3 + val twoThirdsHeight = size.height * 2 / 3 + val strokeWidth = 4f + val shadowStrokeWidth = strokeWidth + 3 + + // Middle horizontal line shadow (drawn under main lines) + drawLine( + color = shadowColor, + start = Offset(oneNinthWidth, midHeight), + end = Offset(size.width, midHeight), + strokeWidth = shadowStrokeWidth, + cap = StrokeCap.Round + ) + // Top vertical line shadow (drawn under main lines) + drawLine( + color = shadowColor, + start = Offset(oneNinthWidth, oneThirdHeight), + end = Offset(oneNinthWidth, midHeight), + strokeWidth = shadowStrokeWidth, + cap = StrokeCap.Round + ) + // Bottom vertical line shadow (drawn under main lines) + drawLine( + color = shadowColor, + start = Offset(oneNinthWidth, midHeight), + end = Offset(oneNinthWidth, twoThirdsHeight), + strokeWidth = shadowStrokeWidth, + cap = StrokeCap.Round + ) + + // Middle horizontal line + drawLine( + color = lineColor, + start = Offset(oneNinthWidth, midHeight), + end = Offset(size.width, midHeight), + strokeWidth = strokeWidth, + cap = StrokeCap.Round + ) + // Top vertical line + drawLine( + color = lineColor, + start = Offset(oneNinthWidth, oneThirdHeight), + end = Offset(oneNinthWidth, midHeight), + strokeWidth = strokeWidth, + cap = StrokeCap.Round + ) + // Bottom vertical line + drawLine( + color = lineColor, + start = Offset(oneNinthWidth, midHeight), + end = Offset(oneNinthWidth, twoThirdsHeight), + strokeWidth = strokeWidth, + cap = StrokeCap.Round + ) + } + ) + Column( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.SpaceAround + ) { + var metricUnits = "m" + var metricDistance = horizontalLineWidthMeters + if (horizontalLineWidthMeters > METERS_IN_KILOMETER) { + // Switch from meters to kilometers as unit + metricUnits = "km" + metricDistance /= METERS_IN_KILOMETER.toInt() + } + + var imperialUnits = "ft" + var imperialDistance = toFeet(horizontalLineWidthMeters.toDouble()) + if (imperialDistance > FEET_IN_MILE) { + // Switch from ft to miles as unit + imperialUnits = "mi" + imperialDistance = toMiles(imperialDistance) + } + + ScaleText( + modifier = Modifier.align(End), + textColor = textColor, + shadowColor = shadowColor, + text = "${imperialDistance.toInt()} $imperialUnits" + ) + ScaleText( + modifier = Modifier.align(End), + textColor = textColor, + shadowColor = shadowColor, + text = "$metricDistance $metricUnits" + ) + } + } +} + +/** + * An animated scale bar that appears when the zoom level of the map changes, and then disappears + * after [visibilityTimeoutMs]. This composable wraps [ScaleBar] with visibility animations. + * + * Implement your own observer on camera move events using [CameraPositionState] and pass it in + * as [cameraPositionState]. + */ +@Composable +public fun DisappearingScaleBar( + modifier: Modifier = Modifier, + cameraPositionState: CameraPositionState, + textColor: Color = DarkGray, + lineColor: Color = DarkGray, + shadowColor: Color = Color.White, + visibilityTimeoutMs: Long = 3_000, + enterTransition: EnterTransition = fadeIn(), + exitTransition: ExitTransition = fadeOut(), +) { + val visible = remember { + MutableTransitionState(true) + } + + LaunchedEffect(key1 = cameraPositionState.position.zoom) { + if (visible.isIdle && !visible.currentState) { + // Show ScaleBar + visible.targetState = true + } else if (visible.isIdle && visible.currentState) { + // Hide ScaleBar after timeout period + delay(visibilityTimeoutMs) + visible.targetState = false + } + } + + AnimatedVisibility( + visibleState = visible, + enter = enterTransition, + exit = exitTransition + ) { + ScaleBar( + modifier = modifier, + cameraPositionState = cameraPositionState, + textColor = textColor, + lineColor = lineColor, + shadowColor = shadowColor + ) + } +} + +@Composable +private fun ScaleText( + modifier: Modifier = Modifier, + text: String, + textColor: Color = DarkGray, + shadowColor: Color = Color.White, +) { + Text( + text = text, + fontSize = 12.sp, + color = textColor, + textAlign = TextAlign.End, + modifier = modifier, + style = MaterialTheme.typography.h4.copy( + shadow = Shadow( + color = shadowColor, + offset = Offset(2f, 2f), + blurRadius = 1f + ) + ) + ) +} + +/** + * Converts the provide value in meters to the corresponding value in feet + * @param meters value in meters to convert to feet + * @return the provided meters value converted to feet + */ +internal fun toFeet(meters: Double): Double { + return meters * CENTIMETERS_IN_METER / CENTIMETERS_IN_INCH / INCHES_IN_FOOT +} + +/** + * Converts the provide value in feet to the corresponding value in miles + * @param feet value in feet to convert to miles + * @return the provided feet value converted to miles + */ +internal fun toMiles(feet: Double): Double { + return feet / FEET_IN_MILE +} + +private const val CENTIMETERS_IN_METER: Double = 100.0 +private const val METERS_IN_KILOMETER: Double = 1000.0 +private const val CENTIMETERS_IN_INCH: Double = 2.54 +private const val INCHES_IN_FOOT: Double = 12.0 +private const val FEET_IN_MILE: Double = 5280.0 \ No newline at end of file From 8d877194d8fcdc8cae249082ad6e77c5411e3d7c Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 16:56:15 -0400 Subject: [PATCH 02/12] chore: Fix typo in docs --- .../java/com/google/maps/android/compose/components/ScaleBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt index f6575120..9b92859f 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt @@ -53,7 +53,7 @@ private val DarkGray = Color(0xFF3a3c3b) /** * A scale bar composable that shows the current scale of the map in feet and meters when zoomed in - * to map, changing to miles and kilometers, respectively, when zooming out. + * to the map, changing to miles and kilometers, respectively, when zooming out. * * Implement your own observer on camera move events using [CameraPositionState] and pass it in * as [cameraPositionState]. From 898528770d3e39e571c0c89cbd15d07e049e6e1a Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 17:11:08 -0400 Subject: [PATCH 03/12] docs: Add documentation in README --- README.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/README.md b/README.md index 6bfb3245..615a34d6 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,40 @@ and don't use `Marker` composables (unless you don't care about `onClick` events). Clustering is the only use-case tested with `MapEffect`, there may be gotchas depending on what features you use in the utility library. +## Components + +This library also provides composable components that you can use alongside the `GoogleMap` composable. + +### ScaleBar + +This composable shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. A `DisappearingScaleBar` is also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period. + +A new `ScaleBarActivity` demonstrates both of these, with the `DisappearingScaleBar` in the upper left corner and the normal base `ScaleBar` in the upper right: + +![maps-compose-scale-bar-cropped](https://user-images.githubusercontent.com/928045/175665891-a0635004-2201-4392-83b3-0c6553b96926.gif) + +Both components leverage the `CameraPositionState` in maps-compose and therefore are very simple to configure with their defaults: + +```kotlin +ScaleBar( + modifier = Modifier + .padding(top = 5.dp, end = 15.dp) + .align(Alignment.TopEnd), + cameraPositionState = cameraPositionState +) + +DisappearingScaleBar( + modifier = Modifier + .padding(top = 5.dp, end = 15.dp) + .align(Alignment.TopStart), + cameraPositionState = cameraPositionState +) +``` + +The colors of the text, line, and shadow are also all configurable (e.g., if you want to configure based on `isSystemInDarkTheme()`). Similarly, the `DisappearingScaleBar` animations can be configured. + +See [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt) in the demo app for an example. + ## Sample App This repository includes a [sample app](app). From 83454c68e9b21c5abd14a2405038baae44dbc1f7 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 17:12:29 -0400 Subject: [PATCH 04/12] docs: Tweak docs --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 615a34d6..5341cbec 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ This library also provides composable components that you can use alongside the This composable shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. A `DisappearingScaleBar` is also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period. -A new `ScaleBarActivity` demonstrates both of these, with the `DisappearingScaleBar` in the upper left corner and the normal base `ScaleBar` in the upper right: +A new [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt) demonstrates both of these, with the `DisappearingScaleBar` in the upper left corner and the normal base `ScaleBar` in the upper right: ![maps-compose-scale-bar-cropped](https://user-images.githubusercontent.com/928045/175665891-a0635004-2201-4392-83b3-0c6553b96926.gif) @@ -231,8 +231,6 @@ DisappearingScaleBar( The colors of the text, line, and shadow are also all configurable (e.g., if you want to configure based on `isSystemInDarkTheme()`). Similarly, the `DisappearingScaleBar` animations can be configured. -See [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt) in the demo app for an example. - ## Sample App This repository includes a [sample app](app). From d19596a69e6aa628bf52fd1840cb1932a98ccd17 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 17:18:41 -0400 Subject: [PATCH 05/12] docs: Tweak docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5341cbec..ede1f866 100644 --- a/README.md +++ b/README.md @@ -207,7 +207,7 @@ This library also provides composable components that you can use alongside the This composable shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. A `DisappearingScaleBar` is also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period. -A new [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt) demonstrates both of these, with the `DisappearingScaleBar` in the upper left corner and the normal base `ScaleBar` in the upper right: +The [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt) demonstrates both of these, with the `DisappearingScaleBar` in the upper left corner and the normal base `ScaleBar` in the upper right: ![maps-compose-scale-bar-cropped](https://user-images.githubusercontent.com/928045/175665891-a0635004-2201-4392-83b3-0c6553b96926.gif) From 1215f459437a5600eb755e545fc1d46b02e28b96 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 17:26:45 -0400 Subject: [PATCH 06/12] chore: Make DarkGray public for ScaleBar configuration For example, if users want to specify it or different colors based on isSystemInDarkTheme() --- .../java/com/google/maps/android/compose/components/ScaleBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt index 9b92859f..70d51eee 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt @@ -49,7 +49,7 @@ import com.google.maps.android.compose.CameraPositionState import com.google.maps.android.ktx.utils.sphericalDistance import kotlinx.coroutines.delay -private val DarkGray = Color(0xFF3a3c3b) +public val DarkGray: Color = Color(0xFF3a3c3b) /** * A scale bar composable that shows the current scale of the map in feet and meters when zoomed in From f2a67ae1b37d18c278862f058ffa9373ecec53b3 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Fri, 24 Jun 2022 17:28:35 -0400 Subject: [PATCH 07/12] docs: Tweak docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ede1f866..f84b7de9 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ DisappearingScaleBar( ) ``` -The colors of the text, line, and shadow are also all configurable (e.g., if you want to configure based on `isSystemInDarkTheme()`). Similarly, the `DisappearingScaleBar` animations can be configured. +The colors of the text, line, and shadow are also all configurable (e.g., based on `isSystemInDarkTheme()` on a dark map). Similarly, the `DisappearingScaleBar` animations can be configured. ## Sample App From 63c14e5ac92bb0f15902aff090bb6658d9b537e0 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Tue, 28 Jun 2022 14:51:36 -0400 Subject: [PATCH 08/12] chore: Allow setting width and height --- .../maps/android/compose/components/ScaleBar.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt index 70d51eee..e08772c4 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt @@ -42,6 +42,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shadow import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.google.android.gms.maps.model.LatLng @@ -50,6 +51,8 @@ import com.google.maps.android.ktx.utils.sphericalDistance import kotlinx.coroutines.delay public val DarkGray: Color = Color(0xFF3a3c3b) +private val defaultWidth: Dp = 65.dp +private val defaultHeight: Dp = 50.dp /** * A scale bar composable that shows the current scale of the map in feet and meters when zoomed in @@ -61,6 +64,8 @@ public val DarkGray: Color = Color(0xFF3a3c3b) @Composable public fun ScaleBar( modifier: Modifier = Modifier, + width: Dp = defaultWidth, + height: Dp = defaultHeight, cameraPositionState: CameraPositionState, textColor: Color = DarkGray, lineColor: Color = DarkGray, @@ -68,7 +73,7 @@ public fun ScaleBar( ) { Box( modifier = modifier - .size(width = 65.dp, height = 50.dp) + .size(width = width, height = height) ) { var horizontalLineWidthMeters by remember { mutableStateOf(0) @@ -193,6 +198,8 @@ public fun ScaleBar( @Composable public fun DisappearingScaleBar( modifier: Modifier = Modifier, + width: Dp = defaultWidth, + height: Dp = defaultHeight, cameraPositionState: CameraPositionState, textColor: Color = DarkGray, lineColor: Color = DarkGray, @@ -223,6 +230,8 @@ public fun DisappearingScaleBar( ) { ScaleBar( modifier = modifier, + width = width, + height = height, cameraPositionState = cameraPositionState, textColor = textColor, lineColor = lineColor, From 6ba6d1a885e3e97b29a27fed302f3cf6b9abd3ca Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Tue, 28 Jun 2022 15:12:13 -0400 Subject: [PATCH 09/12] refactor: Rename duration variable and change to Int This more closely matches other compose duration parameters --- .../com/google/maps/android/compose/components/ScaleBar.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt index e08772c4..3874178a 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt @@ -190,7 +190,7 @@ public fun ScaleBar( /** * An animated scale bar that appears when the zoom level of the map changes, and then disappears - * after [visibilityTimeoutMs]. This composable wraps [ScaleBar] with visibility animations. + * after [visibilityDurationMillis]. This composable wraps [ScaleBar] with visibility animations. * * Implement your own observer on camera move events using [CameraPositionState] and pass it in * as [cameraPositionState]. @@ -204,7 +204,7 @@ public fun DisappearingScaleBar( textColor: Color = DarkGray, lineColor: Color = DarkGray, shadowColor: Color = Color.White, - visibilityTimeoutMs: Long = 3_000, + visibilityDurationMillis: Int = 3_000, enterTransition: EnterTransition = fadeIn(), exitTransition: ExitTransition = fadeOut(), ) { @@ -218,7 +218,7 @@ public fun DisappearingScaleBar( visible.targetState = true } else if (visible.isIdle && visible.currentState) { // Hide ScaleBar after timeout period - delay(visibilityTimeoutMs) + delay(visibilityDurationMillis.toLong()) visible.targetState = false } } From e74952690a29f2ac8ca8db7fc77c90a816dbe13e Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Tue, 28 Jun 2022 15:21:43 -0400 Subject: [PATCH 10/12] refactor: Convert unit conversion functions to extension functions --- .../android/compose/components/ScaleBar.kt | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt index 3874178a..659a56b7 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt +++ b/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt @@ -165,11 +165,11 @@ public fun ScaleBar( } var imperialUnits = "ft" - var imperialDistance = toFeet(horizontalLineWidthMeters.toDouble()) + var imperialDistance = horizontalLineWidthMeters.toDouble().toFeet() if (imperialDistance > FEET_IN_MILE) { // Switch from ft to miles as unit imperialUnits = "mi" - imperialDistance = toMiles(imperialDistance) + imperialDistance = imperialDistance.toMiles() } ScaleText( @@ -264,21 +264,19 @@ private fun ScaleText( } /** - * Converts the provide value in meters to the corresponding value in feet - * @param meters value in meters to convert to feet - * @return the provided meters value converted to feet + * Converts [this] value in meters to the corresponding value in feet + * @return [this] meters value converted to feet */ -internal fun toFeet(meters: Double): Double { - return meters * CENTIMETERS_IN_METER / CENTIMETERS_IN_INCH / INCHES_IN_FOOT +internal fun Double.toFeet(): Double { + return this * CENTIMETERS_IN_METER / CENTIMETERS_IN_INCH / INCHES_IN_FOOT } /** - * Converts the provide value in feet to the corresponding value in miles - * @param feet value in feet to convert to miles - * @return the provided feet value converted to miles + * Converts [this] value in feet to the corresponding value in miles + * @return [this] feet value converted to miles */ -internal fun toMiles(feet: Double): Double { - return feet / FEET_IN_MILE +internal fun Double.toMiles(): Double { + return this / FEET_IN_MILE } private const val CENTIMETERS_IN_METER: Double = 100.0 From 32e5a4e4350774c3c7bca05dce23d2b907617e6e Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Tue, 28 Jun 2022 16:15:58 -0400 Subject: [PATCH 11/12] refactor: ScaleBar into maps-compose-widgets package --- README.md | 13 +++-- app/build.gradle | 2 + .../maps/android/compose/ScaleBarActivity.kt | 4 +- build.gradle | 2 +- maps-compose-widgets/.gitignore | 1 + maps-compose-widgets/build.gradle | 51 +++++++++++++++++++ maps-compose-widgets/consumer-rules.pro | 0 maps-compose-widgets/proguard-rules.pro | 21 ++++++++ .../widgets/ExampleInstrumentedTest.kt | 36 +++++++++++++ .../src/main/AndroidManifest.xml | 20 ++++++++ .../maps/android/compose/widgets}/ScaleBar.kt | 2 +- maps-compose/build.gradle | 2 - settings.gradle | 1 + 13 files changed, 144 insertions(+), 11 deletions(-) create mode 100644 maps-compose-widgets/.gitignore create mode 100644 maps-compose-widgets/build.gradle create mode 100644 maps-compose-widgets/consumer-rules.pro create mode 100644 maps-compose-widgets/proguard-rules.pro create mode 100644 maps-compose-widgets/src/androidTest/java/com/google/maps/android/compose/widgets/ExampleInstrumentedTest.kt create mode 100644 maps-compose-widgets/src/main/AndroidManifest.xml rename {maps-compose/src/main/java/com/google/maps/android/compose/components => maps-compose-widgets/src/main/java/com/google/maps/android/compose/widgets}/ScaleBar.kt (99%) diff --git a/README.md b/README.md index f84b7de9..d31dcb5c 100644 --- a/README.md +++ b/README.md @@ -199,19 +199,19 @@ and don't use `Marker` composables (unless you don't care about `onClick` events). Clustering is the only use-case tested with `MapEffect`, there may be gotchas depending on what features you use in the utility library. -## Components +## Widgets -This library also provides composable components that you can use alongside the `GoogleMap` composable. +This library also provides optional composable widgets in the `maps-compose-widgets` library that you can use alongside the `GoogleMap` composable. ### ScaleBar -This composable shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. A `DisappearingScaleBar` is also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period. +This widget shows the current scale of the map in feet and meters when zoomed into the map, changing to miles and kilometers, respectively, when zooming out. A `DisappearingScaleBar` is also included, which appears when the zoom level of the map changes, and then disappears after a configurable timeout period. The [ScaleBarActivity](app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt) demonstrates both of these, with the `DisappearingScaleBar` in the upper left corner and the normal base `ScaleBar` in the upper right: ![maps-compose-scale-bar-cropped](https://user-images.githubusercontent.com/928045/175665891-a0635004-2201-4392-83b3-0c6553b96926.gif) -Both components leverage the `CameraPositionState` in maps-compose and therefore are very simple to configure with their defaults: +Both versions of this widget leverage the `CameraPositionState` in `maps-compose` and therefore are very simple to configure with their defaults: ```kotlin ScaleBar( @@ -250,7 +250,10 @@ dependencies { implementation 'com.google.android.gms:play-services-maps:18.0.2' // Also include Compose version `1.2.0-alpha03` or higher - for example: - implementation "androidx.compose.foundation:foundation:2.4.0-alpha03" + implementation 'androidx.compose.foundation:foundation:2.4.0-alpha03' + + // Optionally, you can include the widgets library if you want to use ScaleBar, etc. + implementation 'com.google.maps.android:maps-compose-widgets:1.0.0' } ``` diff --git a/app/build.gradle b/app/build.gradle index 68f34789..c979f201 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -59,6 +59,8 @@ dependencies { // declaration if you want to test the sample app with a Maven Central release of the library. //implementation "com.google.maps.android:maps-compose:2.2.1" implementation project(':maps-compose') + //implementation "com.google.maps.android:maps-compose-widgets:1.0.0" + implementation project(':maps-compose-widgets') implementation 'com.google.android.gms:play-services-maps:18.0.2' } diff --git a/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt b/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt index daf18935..ca3e7266 100644 --- a/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt +++ b/app/src/main/java/com/google/maps/android/compose/ScaleBarActivity.kt @@ -36,8 +36,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.LatLng -import com.google.maps.android.compose.components.DisappearingScaleBar -import com.google.maps.android.compose.components.ScaleBar +import com.google.maps.android.compose.widgets.DisappearingScaleBar +import com.google.maps.android.compose.widgets.ScaleBar private const val TAG = "ScaleBarActivity" diff --git a/build.gradle b/build.gradle index cb30c3ae..0566fd47 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ plugins { } ext.projectArtifactId = { project -> - if (project.name == 'maps-compose') { + if (project.name == 'maps-compose' || project.name == 'maps-compose-widgets') { return project.name } else { return null diff --git a/maps-compose-widgets/.gitignore b/maps-compose-widgets/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/maps-compose-widgets/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/maps-compose-widgets/build.gradle b/maps-compose-widgets/build.gradle new file mode 100644 index 00000000..c7c2f279 --- /dev/null +++ b/maps-compose-widgets/build.gradle @@ -0,0 +1,51 @@ +plugins { + id 'kotlin-android' + id 'kotlin-parcelize' +} + +android { + compileSdk 31 + + defaultConfig { + minSdk 21 + targetSdk 31 + versionCode 1 + versionName "1.0" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + composeOptions { + kotlinCompilerExtensionVersion "$compose_version" + } + + buildFeatures { + buildConfig false + compose true + } + + kotlinOptions { + jvmTarget = '1.8' + freeCompilerArgs += '-Xexplicit-api=strict' + freeCompilerArgs += '-Xopt-in=kotlin.RequiresOptIn' + } +} + +dependencies { + implementation "androidx.compose.foundation:foundation:$compose_version" + implementation 'com.google.maps.android:maps-compose:2.2.1' + implementation 'androidx.compose.material:material:1.1.1' + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'com.google.android.gms:play-services-maps:18.0.2' + implementation 'com.google.maps.android:maps-ktx:3.4.0' + implementation 'com.google.maps.android:maps-utils-ktx:3.3.0' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation "androidx.core:core-ktx:1.7.0" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} \ No newline at end of file diff --git a/maps-compose-widgets/consumer-rules.pro b/maps-compose-widgets/consumer-rules.pro new file mode 100644 index 00000000..e69de29b diff --git a/maps-compose-widgets/proguard-rules.pro b/maps-compose-widgets/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/maps-compose-widgets/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/maps-compose-widgets/src/androidTest/java/com/google/maps/android/compose/widgets/ExampleInstrumentedTest.kt b/maps-compose-widgets/src/androidTest/java/com/google/maps/android/compose/widgets/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..509b3912 --- /dev/null +++ b/maps-compose-widgets/src/androidTest/java/com/google/maps/android/compose/widgets/ExampleInstrumentedTest.kt @@ -0,0 +1,36 @@ +// Copyright 2021 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.maps.android.compose.widgets + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +internal class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.google.maps.android.compose.widgets.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/maps-compose-widgets/src/main/AndroidManifest.xml b/maps-compose-widgets/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7931c287 --- /dev/null +++ b/maps-compose-widgets/src/main/AndroidManifest.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt b/maps-compose-widgets/src/main/java/com/google/maps/android/compose/widgets/ScaleBar.kt similarity index 99% rename from maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt rename to maps-compose-widgets/src/main/java/com/google/maps/android/compose/widgets/ScaleBar.kt index 659a56b7..d988268d 100644 --- a/maps-compose/src/main/java/com/google/maps/android/compose/components/ScaleBar.kt +++ b/maps-compose-widgets/src/main/java/com/google/maps/android/compose/widgets/ScaleBar.kt @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package com.google.maps.android.compose.components +package com.google.maps.android.compose.widgets import android.graphics.Point import androidx.compose.animation.AnimatedVisibility diff --git a/maps-compose/build.gradle b/maps-compose/build.gradle index 696137a3..c1d45183 100644 --- a/maps-compose/build.gradle +++ b/maps-compose/build.gradle @@ -36,11 +36,9 @@ android { dependencies { implementation "androidx.compose.foundation:foundation:$compose_version" - implementation 'androidx.compose.material:material:1.1.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'com.google.android.gms:play-services-maps:18.0.2' implementation 'com.google.maps.android:maps-ktx:3.4.0' - implementation 'com.google.maps.android:maps-utils-ktx:3.3.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' diff --git a/settings.gradle b/settings.gradle index 797e7c77..5f7603c4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,3 +9,4 @@ dependencyResolutionManagement { rootProject.name = "android-maps-compose" include ':app' include ':maps-compose' +include ':maps-compose-widgets' From 33990bb781f9219e801eb5deb4bfe0c7207e4d62 Mon Sep 17 00:00:00 2001 From: Sean Barbeau Date: Wed, 29 Jun 2022 17:31:32 -0400 Subject: [PATCH 12/12] chore: Switch to local module so artifact is always tied to same version as maps-compose --- maps-compose-widgets/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maps-compose-widgets/build.gradle b/maps-compose-widgets/build.gradle index c7c2f279..a3dc1b8f 100644 --- a/maps-compose-widgets/build.gradle +++ b/maps-compose-widgets/build.gradle @@ -36,7 +36,7 @@ android { dependencies { implementation "androidx.compose.foundation:foundation:$compose_version" - implementation 'com.google.maps.android:maps-compose:2.2.1' + implementation project(':maps-compose') implementation 'androidx.compose.material:material:1.1.1' implementation 'androidx.core:core-ktx:1.7.0' implementation 'com.google.android.gms:play-services-maps:18.0.2'