summaryrefslogtreecommitdiff
path: root/platform/android/wearapp/src/main/java/com/mapbox/mapboxsdk/wearapp/ui/MapboxMapView.kt
diff options
context:
space:
mode:
Diffstat (limited to 'platform/android/wearapp/src/main/java/com/mapbox/mapboxsdk/wearapp/ui/MapboxMapView.kt')
-rw-r--r--platform/android/wearapp/src/main/java/com/mapbox/mapboxsdk/wearapp/ui/MapboxMapView.kt282
1 files changed, 282 insertions, 0 deletions
diff --git a/platform/android/wearapp/src/main/java/com/mapbox/mapboxsdk/wearapp/ui/MapboxMapView.kt b/platform/android/wearapp/src/main/java/com/mapbox/mapboxsdk/wearapp/ui/MapboxMapView.kt
new file mode 100644
index 0000000000..04fe3828cc
--- /dev/null
+++ b/platform/android/wearapp/src/main/java/com/mapbox/mapboxsdk/wearapp/ui/MapboxMapView.kt
@@ -0,0 +1,282 @@
+package com.example.mapbox.ui
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.Resources
+import android.location.Location
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import com.example.mapbox.R
+import com.example.mapbox.breadcrumb.ExerciseEngineState
+import com.example.mapbox.extensions.getBitmapFromDrawable
+import com.example.mapbox.extensions.latLong
+import com.mapbox.mapboxsdk.camera.CameraPosition
+import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
+import com.mapbox.mapboxsdk.geometry.LatLng
+import com.mapbox.mapboxsdk.location.LocationComponentOptions
+import com.mapbox.mapboxsdk.location.OnCameraTrackingChangedListener
+import com.mapbox.mapboxsdk.location.OnLocationCameraTransitionListener
+import com.mapbox.mapboxsdk.location.OnLocationStaleListener
+import com.mapbox.mapboxsdk.location.modes.CameraMode
+import com.mapbox.mapboxsdk.location.modes.RenderMode
+import com.mapbox.mapboxsdk.maps.*
+import com.soy.android.maps.breadcrumb.Breadcrumb
+import com.soy.android.maps.compass.WassCompassEngine
+import com.soy.android.maps.extensions.getActivatedLocationComponent
+import com.soy.android.maps.extensions.isCameraInTrackingMode
+import timber.log.Timber
+import kotlin.math.roundToInt
+
+internal const val INITIAL_CAMERA_ZOOM_LEVEL_INDEX_WITH_LOCATION = 14
+/**
+ * Map is centered at Helsinki when we don't have user's current or any last location
+ */
+private const val DEFAULT_INITIAL_CAMERA_POSITION_LATITUDE = 60.1699
+private const val DEFAULT_INITIAL_CAMERA_POSITION_LONGITUDE = 24.9384
+
+private const val CAMERA_ANIMATION_DURATION = 200L
+
+internal fun buildCameraPosition(
+ target: LatLng = LatLng(DEFAULT_INITIAL_CAMERA_POSITION_LATITUDE, DEFAULT_INITIAL_CAMERA_POSITION_LONGITUDE),
+ zoomIndex: Int = INITIAL_CAMERA_ZOOM_LEVEL_INDEX_WITH_LOCATION
+) = CameraPosition.Builder().target(target).zoom(zoomIndex.toDouble()).build()
+
+/**
+ * [MapboxMapOptions] can either be applied via xml or programmatically when [MapView] is
+ * constructed. This function provides a predefined set of options.
+ */
+private fun createMapboxMapOptions(resources: Resources, location: Location?): MapboxMapOptions {
+ val cameraPosition = if (location == null) {
+ // Use default location and zoom level
+ buildCameraPosition()
+ } else {
+ buildCameraPosition(LatLng(location.latitude, location.longitude), INITIAL_CAMERA_ZOOM_LEVEL_INDEX_WITH_LOCATION)
+ }
+ return MapboxMapOptions().apply {
+ val compassImageTopMargin = resources.getDimension(R.dimen.size_spacing_xxsmall).roundToInt()
+ compassMargins(intArrayOf(0, compassImageTopMargin, 0, 0))
+ compassEnabled(true)
+ compassImage(resources.getDrawable(R.drawable.ic_shape_north_indicator, null))
+ compassGravity(Gravity.CENTER)
+ textureMode(true)
+ attributionEnabled(false)
+ logoEnabled(false)
+ zoomGesturesEnabled(true)
+ compassFadesWhenFacingNorth(false)
+ rotateGesturesEnabled(false)
+ camera(cameraPosition)
+ }
+}
+
+internal const val START_SYMBOL_ICON_ID = "id-start-icon"
+internal const val PAUSE_LINE_PATTERN = "id-stale-line-pattern"
+
+/**
+ * This class extends [MapView] and is suggested to use when creating map programmatically with a
+ * given cameraPosition which is used to center the camera on map. This view doesn't allow touch
+ * interactions for the child views of [MapView] such as compass, logo, attribute.
+ */
+@SuppressLint("ViewConstructor")
+class MapboxMapView(
+ context: Context,
+ location: Location? = null,
+ private val isBreadcrumbEnabled: Boolean = true
+) : MapView(context, createMapboxMapOptions(context.resources, location)), OnLocationStaleListener,
+ OnCameraTrackingChangedListener, MapboxMap.OnMapClickListener, OnMapReadyCallback, MapboxMap.OnMapLongClickListener {
+
+ init {
+ id = View.generateViewId()
+ }
+
+ private var map: MapboxMap? = null
+
+ private var breadcrumb: Breadcrumb? = null
+
+ private var exerciseEngineState = ExerciseEngineState.Recording
+
+ /**
+ * A boolean which keeps track of location stale status. Initially location is assigned to be stale since there is
+ * no gps fix at start up. The value changes in two places:
+ * 1- When [onStaleStateChange] is invoked, the value is updated based on the parameter
+ * 2- Whenever there is new gps fix it is set to be false
+ */
+ private var isLocationStale = true
+
+ internal var compassEngine: WassCompassEngine? = null
+ @CameraMode.Mode
+ private var lastCameraTrackingMode = CameraMode.TRACKING_COMPASS
+ private var cameraZoomLevelIndex = INITIAL_CAMERA_ZOOM_LEVEL_INDEX_WITH_LOCATION
+ private var isDefaultCameraPositionInUse = false
+
+ override fun onMapReady(mapboxMap: MapboxMap) {
+ map = mapboxMap.apply {
+ setStyle(Style.Builder()
+ .withImage(START_SYMBOL_ICON_ID, resources.getBitmapFromDrawable(R.drawable.ic_map_start_pin))
+ .withImage(PAUSE_LINE_PATTERN, resources.getBitmapFromDrawable(R.drawable.ic_stale_track_dot))
+ .fromUrl(context.getString(R.string.asoy_mapbox_style))
+ ) { style ->
+ startLocationComponent(mapboxMap, style)
+ if (isBreadcrumbEnabled) {
+ breadcrumb = Breadcrumb(this@MapboxMapView, mapboxMap, style)
+ }
+ }
+ addOnMapLongClickListener(this@MapboxMapView)
+ addOnMapClickListener(this@MapboxMapView)
+ }
+ }
+
+ // Location
+ @SuppressLint("MissingPermission")
+ private fun startLocationComponent(mapboxMap: MapboxMap, style: Style) {
+ mapboxMap.locationComponent.apply {
+ // Activate with options. If location engine is null, push location updates to
+ // the component without any internal engine management. No engine is going to
+ // be initialized and you can push location updates with
+ // [LocationComponent#forceLocationUpdate(Location)].
+ activateLocationComponent(
+ context,
+ style,
+ null,
+ LocationComponentOptions.createFromAttributes(context, R.style.mapbox_location)
+ )
+ compassEngine = this@MapboxMapView.compassEngine
+ addOnCameraTrackingChangedListener(this@MapboxMapView)
+ isLocationComponentEnabled = true
+ Timber.d("Mapbox Location Component is enabled")
+ addOnLocationStaleListener(this@MapboxMapView)
+ renderMode = RenderMode.GPS
+ cameraMode = lastCameraTrackingMode
+ }
+ }
+
+ internal fun onNewLocation(location: Location) {
+ isLocationStale = false
+ map?.run {
+ getActivatedLocationComponent()?.run {
+ if (isDefaultCameraPositionInUse) {
+ updateMapCameraZoom(INITIAL_CAMERA_ZOOM_LEVEL_INDEX_WITH_LOCATION)
+ isDefaultCameraPositionInUse = false
+ }
+ forceLocationUpdate(location)
+ breadcrumb?.onNewLocation(location.latLong(), exerciseEngineState)
+ }
+ }
+ }
+
+ override fun onStaleStateChange(isStale: Boolean) {
+ Timber.d("Location stale state change. Is Stale: $isStale")
+ isLocationStale = isStale
+ map?.getActivatedLocationComponent()?.run {
+ renderMode = if (isStale) RenderMode.NORMAL else RenderMode.GPS
+ val locationComponentStyle = if (isStale) R.style.mapbox_stale_location else R.style.mapbox_location
+ applyStyle(LocationComponentOptions.createFromAttributes(context, locationComponentStyle))
+ }
+ }
+
+ // Camera
+ private fun updateMapCameraZoom(zoomLevelIndex: Int) = map?.getActivatedLocationComponent()?.run {
+ cameraZoomLevelIndex = zoomLevelIndex
+ if (cameraMode != CameraMode.NONE) {
+ zoomWhileTracking(getCameraZoomLevel(), CAMERA_ANIMATION_DURATION)
+ } else {
+ map?.easeCamera(CameraUpdateFactory.zoomTo(getCameraZoomLevel()))
+ }
+ }
+
+ private fun getCameraZoomLevel(): Double = map?.cameraPosition?.zoom ?: 0.0
+
+ override fun onCameraTrackingChanged(currentMode: Int) {
+ Timber.d("Camera tracking mode changed. Current mode: $currentMode")
+ if (map?.isCameraInTrackingMode() == true) {
+ lastCameraTrackingMode = currentMode
+ }
+ }
+
+ /**
+ * Invoked whenever camera tracking is broken.
+ */
+ override fun onCameraTrackingDismissed() {
+ Timber.d("Exiting from camera tracking mode")
+ updateCameraMode(CameraMode.NONE_COMPASS)
+ }
+
+ private fun updateCameraMode(@CameraMode.Mode mode: Int) {
+ // We need to
+ map?.getActivatedLocationComponent()?.apply {
+ if (cameraMode != CameraMode.NONE) {
+ setCameraMode(mode, object : OnLocationCameraTransitionListener {
+ override fun onLocationCameraTransitionFinished(cameraMode: Int) {
+ zoomWhileTracking(getCameraZoomLevel(), CAMERA_ANIMATION_DURATION)
+ }
+
+ override fun onLocationCameraTransitionCanceled(cameraMode: Int) {
+ // No impl.
+ }
+ })
+ } else {
+ cameraMode = mode
+ }
+ }
+ }
+
+ // Gestures
+ /**
+ * When [MapboxMap.OnMapClickListener.onMapClick] is called camera mode is updated:
+ * There are two camera tracking modes enabled:
+ * The [CameraMode.TRACKING_GPS_NORTH] sets the camera always to the north and the [CameraMode.TRACKING_COMPASS]
+ * sets the location indicator to north and map rotates according to heading
+ * If camera mode is [CameraMode.TRACKING_GPS_NORTH], onClick switches the mode to [CameraMode.TRACKING_COMPASS]
+ * If camera mode is [CameraMode.TRACKING_COMPASS] onClick switches the mode to [CameraMode.TRACKING_GPS_NORTH]
+ * If user starts panning while in tracking mode, camera switches to [CameraMode.NONE_COMPASS] mode and user is not
+ * tracked by camera. On click switches the camera back to last used tracking mode
+ */
+ override fun onMapClick(point: LatLng): Boolean {
+ map?.getActivatedLocationComponent()?.apply {
+ when (cameraMode) {
+ CameraMode.TRACKING_GPS_NORTH -> updateCameraMode(CameraMode.TRACKING_COMPASS)
+ CameraMode.TRACKING_COMPASS -> updateCameraMode(CameraMode.TRACKING_GPS_NORTH)
+ CameraMode.NONE_COMPASS,
+ CameraMode.NONE,
+ CameraMode.NONE_GPS -> updateCameraMode(lastCameraTrackingMode)
+ else -> Timber.w("Using unsupported camera mode")
+ }
+ }
+ return true
+ }
+
+ /**
+ * Used for mocking exercise pause / resmume controls
+ */
+ override fun onMapLongClick(point: LatLng): Boolean {
+ if (exerciseEngineState == ExerciseEngineState.Paused) {
+ exerciseEngineState = ExerciseEngineState.Recording
+ } else if (exerciseEngineState == ExerciseEngineState.Recording) {
+ exerciseEngineState = ExerciseEngineState.Paused
+ }
+ return true
+ }
+
+ /**
+ * By overriding this method, we prevent compass view component of the [MapView] from consuming the clicks
+ */
+ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+ super.onInterceptTouchEvent(ev)
+ return true
+ }
+
+ override fun onResume() {
+ super.onResume()
+ breadcrumb?.onResume()
+ }
+
+ override fun onStop() {
+ breadcrumb?.onStop()
+ super.onStop()
+ }
+
+ override fun onDestroy() {
+ breadcrumb?.onDestroy()
+ super.onDestroy()
+ }
+}