Skip to content

Commit e8e302e

Browse files
committed
Make internal types & modify API
1 parent 1189c36 commit e8e302e

14 files changed

+219
-182
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package net.mullvad.mullvadvpn.lib.map
2+
3+
import androidx.compose.animation.core.Animatable
4+
import androidx.compose.animation.core.EaseInOut
5+
import androidx.compose.animation.core.keyframes
6+
import androidx.compose.animation.core.tween
7+
import androidx.compose.runtime.Composable
8+
import androidx.compose.runtime.LaunchedEffect
9+
import androidx.compose.runtime.remember
10+
import kotlinx.coroutines.launch
11+
import net.mullvad.mullvadvpn.lib.map.data.CameraPosition
12+
import net.mullvad.mullvadvpn.lib.map.data.Marker
13+
import net.mullvad.mullvadvpn.lib.map.data.MarkerType
14+
import net.mullvad.mullvadvpn.model.LatLng
15+
import net.mullvad.mullvadvpn.model.Latitude
16+
import net.mullvad.mullvadvpn.model.Longitude
17+
18+
@Composable
19+
fun animatedCameraPosition(
20+
targetCameraLocation: LatLng,
21+
marker: Marker?,
22+
percent: Float,
23+
): CameraPosition {
24+
val tempPreviousLocation =
25+
rememberPrevious(
26+
current = targetCameraLocation,
27+
shouldUpdate = { prev, curr -> prev != curr }
28+
) ?: targetCameraLocation
29+
val previousLocation = remember(targetCameraLocation) { tempPreviousLocation }
30+
31+
val distance =
32+
remember(targetCameraLocation) { targetCameraLocation.distanceTo(previousLocation).toInt() }
33+
val duration = distance.toAnimationDuration()
34+
35+
val longitudeAnimation = remember { Animatable(targetCameraLocation.longitude.value) }
36+
37+
val latitudeAnimation = remember { Animatable(targetCameraLocation.latitude.value) }
38+
val secureZoomAnimation = remember {
39+
Animatable(if (marker?.type == MarkerType.SECURE) SECURE_ZOOM else UNSECURE_ZOOM)
40+
}
41+
val zoomOutMultiplier = remember { Animatable(1f) }
42+
43+
LaunchedEffect(targetCameraLocation) {
44+
launch { latitudeAnimation.animateTo(targetCameraLocation.latitude.value, tween(duration)) }
45+
launch {
46+
// Unwind longitudeAnimation into a Longitude
47+
val currentLongitude = Longitude.fromFloat(longitudeAnimation.value)
48+
49+
// Resolve a vector showing us the shortest path to the target longitude, e.g going
50+
// from 170 to -170 would result in 20 since we can wrap around the globe
51+
val shortestPathVector = currentLongitude.vectorTo(targetCameraLocation.longitude)
52+
53+
// Animate to the new camera location using the shortest path vector
54+
longitudeAnimation.animateTo(
55+
longitudeAnimation.value + shortestPathVector.value,
56+
tween(duration),
57+
)
58+
59+
// Current value animation value might be outside of range of a Longitude, so when the
60+
// animation is done we unwind the animation to the correct value
61+
longitudeAnimation.snapTo(targetCameraLocation.longitude.value)
62+
}
63+
launch {
64+
zoomOutMultiplier.animateTo(
65+
targetValue = 1f,
66+
animationSpec =
67+
keyframes {
68+
if (duration < SHORT_ANIMATION_MILLIS) {
69+
durationMillis = duration
70+
1f at duration using EaseInOut
71+
} else {
72+
durationMillis = duration
73+
1.25f at duration / 3 using EaseInOut
74+
1f at duration using EaseInOut
75+
}
76+
}
77+
)
78+
}
79+
}
80+
81+
LaunchedEffect(marker?.type) {
82+
launch { secureZoomAnimation.animateTo(targetValue = marker?.type.toZoom(), tween(2000)) }
83+
}
84+
85+
return CameraPosition(
86+
zoom = secureZoomAnimation.value * zoomOutMultiplier.value * 0.9f,
87+
latLng =
88+
LatLng(
89+
Latitude(latitudeAnimation.value),
90+
Longitude.fromFloat(longitudeAnimation.value)
91+
),
92+
bias = percent
93+
)
94+
}

android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/Map.kt

+40-80
Original file line numberDiff line numberDiff line change
@@ -6,118 +6,78 @@ import androidx.compose.animation.core.EaseInOut
66
import androidx.compose.animation.core.keyframes
77
import androidx.compose.animation.core.tween
88
import androidx.compose.runtime.Composable
9+
import androidx.compose.runtime.DisposableEffect
910
import androidx.compose.runtime.LaunchedEffect
1011
import androidx.compose.runtime.remember
1112
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.platform.LocalLifecycleOwner
14+
import androidx.compose.ui.viewinterop.AndroidView
15+
import androidx.lifecycle.Lifecycle
16+
import androidx.lifecycle.LifecycleEventObserver
1217
import kotlinx.coroutines.launch
18+
import net.mullvad.mullvadvpn.lib.map.data.CameraPosition
19+
import net.mullvad.mullvadvpn.lib.map.data.MapConfig
1320
import net.mullvad.mullvadvpn.lib.map.data.MapViewState
1421
import net.mullvad.mullvadvpn.lib.map.data.Marker
1522
import net.mullvad.mullvadvpn.lib.map.data.MarkerType
16-
import net.mullvad.mullvadvpn.lib.map.internal.MapGLShader
23+
import net.mullvad.mullvadvpn.lib.map.internal.MapGLSurfaceView
1724
import net.mullvad.mullvadvpn.model.LatLng
1825
import net.mullvad.mullvadvpn.model.Latitude
1926
import net.mullvad.mullvadvpn.model.Longitude
2027

2128
@Composable
2229
fun Map(
2330
modifier: Modifier,
24-
animate: Boolean,
31+
animateCameraMovement: Boolean,
2532
cameraLocation: LatLng,
2633
marker: Marker?,
2734
percent: Float,
2835
) {
2936
val mapViewState =
30-
if (animate) {
31-
animatedMapViewState(cameraLocation, marker, percent)
37+
if (animateCameraMovement) {
38+
MapViewState(marker, animatedCameraPosition(cameraLocation, marker, percent))
3239
} else {
33-
MapViewState(
34-
zoom = marker?.type.toZoom(),
35-
cameraLatLng = cameraLocation,
36-
locationMarker = marker,
37-
percent = percent
38-
)
40+
MapViewState(marker, CameraPosition(cameraLocation, marker?.type.toZoom(), percent))
3941
}
40-
Log.d("MullvadMap", "CameraLocation: ${mapViewState.cameraLatLng}")
41-
MapGLShader(modifier = modifier, mapViewState = mapViewState)
42+
Map(modifier = modifier, mapViewState = mapViewState)
4243
}
4344

44-
@Composable
45-
fun animatedMapViewState(
46-
targetCameraLocation: LatLng,
47-
marker: Marker?,
48-
percent: Float,
49-
): MapViewState {
50-
val tempPreviousLocation =
51-
rememberPrevious(
52-
current = targetCameraLocation,
53-
shouldUpdate = { prev, curr -> prev != curr }
54-
) ?: targetCameraLocation
55-
val previousLocation = remember(targetCameraLocation) { tempPreviousLocation }
56-
57-
val distance =
58-
remember(targetCameraLocation) { targetCameraLocation.distanceTo(previousLocation).toInt() }
59-
val duration = distance.toAnimationDuration()
60-
61-
val longitudeAnimation = remember { Animatable(targetCameraLocation.longitude.value) }
6245

63-
val latitudeAnimation = remember { Animatable(targetCameraLocation.latitude.value) }
64-
val secureZoomAnimation = remember {
65-
Animatable(if (marker?.type == MarkerType.SECURE) SECURE_ZOOM else UNSECURE_ZOOM)
66-
}
67-
val zoomOutMultiplier = remember { Animatable(1f) }
68-
69-
LaunchedEffect(targetCameraLocation) {
70-
launch { latitudeAnimation.animateTo(targetCameraLocation.latitude.value, tween(duration)) }
71-
launch {
72-
// Unwind longitudeAnimation into a Longitude
73-
val currentLongitude = Longitude.fromFloat(longitudeAnimation.value)
46+
@Composable
47+
internal fun Map(modifier: Modifier = Modifier, mapViewState: MapViewState) {
7448

75-
// Resolve a vector showing us the shortest path to the target longitude, e.g going
76-
// from 170 to -170 would result in 20 since we can wrap around the globe
77-
val shortestPathVector = currentLongitude.vectorTo(targetCameraLocation.longitude)
49+
var view: MapGLSurfaceView? = remember { null }
7850

79-
// Animate to the new camera location using the shortest path vector
80-
longitudeAnimation.animateTo(
81-
longitudeAnimation.value + shortestPathVector.value,
82-
tween(duration),
83-
)
51+
val lifeCycleState = LocalLifecycleOwner.current.lifecycle
8452

85-
// Current value animation value might be outside of range of a Longitude, so when the
86-
// animation is done we unwind the animation to the correct value
87-
longitudeAnimation.snapTo(targetCameraLocation.longitude.value)
53+
DisposableEffect(key1 = lifeCycleState) {
54+
val observer = LifecycleEventObserver { _, event ->
55+
when (event) {
56+
Lifecycle.Event.ON_RESUME -> {
57+
view?.onResume()
58+
}
59+
Lifecycle.Event.ON_PAUSE -> {
60+
view?.onPause()
61+
}
62+
else -> {}
63+
}
8864
}
89-
launch {
90-
zoomOutMultiplier.animateTo(
91-
targetValue = 1f,
92-
animationSpec =
93-
keyframes {
94-
if (duration < SHORT_ANIMATION_MILLIS) {
95-
durationMillis = duration
96-
1f at duration using EaseInOut
97-
} else {
98-
durationMillis = duration
99-
1.25f at duration / 3 using EaseInOut
100-
1f at duration using EaseInOut
101-
}
102-
}
103-
)
65+
lifeCycleState.addObserver(observer)
66+
67+
onDispose {
68+
Log.d("mullvad", "AAA View Disposed ${view.hashCode()}")
69+
lifeCycleState.removeObserver(observer)
70+
view?.onPause()
71+
view = null
10472
}
10573
}
10674

107-
LaunchedEffect(marker?.type) {
108-
launch { secureZoomAnimation.animateTo(targetValue = marker?.type.toZoom(), tween(2000)) }
75+
// TODO how to handle mapConfig changes? Can we recreate the view? make them recomposable?
76+
AndroidView(modifier = modifier, factory = { MapGLSurfaceView(it, mapConfig = MapConfig()) }) {
77+
glSurfaceView ->
78+
view = glSurfaceView
79+
glSurfaceView.setData(mapViewState)
10980
}
110-
111-
return MapViewState(
112-
zoom = secureZoomAnimation.value * zoomOutMultiplier.value * 0.9f,
113-
cameraLatLng =
114-
LatLng(
115-
Latitude(latitudeAnimation.value),
116-
Longitude.fromFloat(longitudeAnimation.value)
117-
),
118-
locationMarker = marker,
119-
percent = percent
120-
)
12181
}
12282

12383
fun MarkerType?.toZoom() =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package net.mullvad.mullvadvpn.lib.map.data
2+
3+
import androidx.compose.ui.graphics.Color
4+
import net.mullvad.mullvadvpn.lib.map.internal.toFloatArray
5+
6+
data class GlobeColors(
7+
val landColor: Color,
8+
val oceanColor: Color,
9+
val contourColor: Color,
10+
) {
11+
val landColorArray = landColor.toFloatArray()
12+
val oceanColorArray = oceanColor.toFloatArray()
13+
val contourColorArray = contourColor.toFloatArray()
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package net.mullvad.mullvadvpn.lib.map.data
2+
3+
import androidx.compose.ui.graphics.Color
4+
5+
data class LocationMarkerColors(
6+
val centerColor: Color,
7+
val ringBorderColor: Color = Color.White,
8+
val shadowColor: Color = Color.Black.copy(alpha = 0.55f),
9+
val perimeterColors: Color = centerColor.copy(alpha = 0.4f)
10+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package net.mullvad.mullvadvpn.lib.map.data
2+
3+
import androidx.compose.ui.graphics.Color
4+
5+
data class MapConfig(
6+
val globeColors: GlobeColors =
7+
GlobeColors(
8+
landColor = Color(0.16f, 0.302f, 0.45f),
9+
oceanColor = Color(0.098f, 0.18f, 0.271f),
10+
contourColor = Color(0.098f, 0.18f, 0.271f)
11+
),
12+
val secureMarkerColor: LocationMarkerColors =
13+
LocationMarkerColors(centerColor = Color(0.267f, 0.678f, 0.302f)),
14+
val unsecureMarkerColor: LocationMarkerColors =
15+
LocationMarkerColors(centerColor = Color(0.89f, 0.251f, 0.224f))
16+
)

android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/data/MapViewState.kt

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ package net.mullvad.mullvadvpn.lib.map.data
33
import net.mullvad.mullvadvpn.model.LatLng
44

55
class MapViewState(
6-
val zoom: Float,
7-
val cameraLatLng: LatLng,
86
val locationMarker: Marker?,
9-
val percent: Float
7+
val cameraPosition: CameraPosition
108
)
9+
10+
data class CameraPosition(
11+
val latLng: LatLng,
12+
val zoom: Float,
13+
val bias: Float
14+
)
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package net.mullvad.mullvadvpn.lib.map.internal
22

3-
const val VERTEX_COMPONENT_SIZE = 3
4-
const val COLOR_COMPONENT_SIZE = 4
3+
internal const val VERTEX_COMPONENT_SIZE = 3
4+
internal const val COLOR_COMPONENT_SIZE = 4
55

6-
const val MATRIX_SIZE = 16
6+
internal const val MATRIX_SIZE = 16

android/lib/map/src/main/kotlin/net/mullvad/mullvadvpn/lib/map/internal/GLHelper.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import java.nio.Buffer
88
import java.nio.ByteBuffer
99
import java.nio.FloatBuffer
1010

11-
fun initShaderProgram(vsSource: String, fsSource: String): Int {
11+
internal fun initShaderProgram(vsSource: String, fsSource: String): Int {
1212
val vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vsSource)
1313
require(vertexShader != -1) { "Failed to load vertexShader, result: -1" }
1414

@@ -63,9 +63,9 @@ private fun loadShader(type: Int, shaderCode: String): Int {
6363
return shader
6464
}
6565

66-
fun initArrayBuffer(buffer: ByteBuffer) = initArrayBuffer(buffer, Byte.SIZE_BYTES)
66+
internal fun initArrayBuffer(buffer: ByteBuffer) = initArrayBuffer(buffer, Byte.SIZE_BYTES)
6767

68-
fun initArrayBuffer(buffer: FloatBuffer) = initArrayBuffer(buffer, Float.SIZE_BYTES)
68+
internal fun initArrayBuffer(buffer: FloatBuffer) = initArrayBuffer(buffer, Float.SIZE_BYTES)
6969

7070
private fun initArrayBuffer(dataBuffer: Buffer, unitSizeInBytes: Int = 1): Int {
7171
val buffer = IntArray(1)
@@ -81,7 +81,7 @@ private fun initArrayBuffer(dataBuffer: Buffer, unitSizeInBytes: Int = 1): Int {
8181
return buffer[0]
8282
}
8383

84-
fun initIndexBuffer(dataBuffer: Buffer): IndexBufferWithLength {
84+
internal fun initIndexBuffer(dataBuffer: Buffer): IndexBufferWithLength {
8585
val buffer = IntArray(1)
8686
GLES20.glGenBuffers(1, buffer, 0)
8787

@@ -95,12 +95,12 @@ fun initIndexBuffer(dataBuffer: Buffer): IndexBufferWithLength {
9595
return IndexBufferWithLength(indexBuffer = buffer[0], length = dataBuffer.capacity() / Float.SIZE_BYTES)
9696
}
9797

98-
fun newIdentityMatrix(): FloatArray = FloatArray(MATRIX_SIZE).apply { Matrix.setIdentityM(this, 0) }
98+
internal fun newIdentityMatrix(): FloatArray = FloatArray(MATRIX_SIZE).apply { Matrix.setIdentityM(this, 0) }
9999

100-
fun Color.toFloatArray(): FloatArray {
100+
internal fun Color.toFloatArray(): FloatArray {
101101
return floatArrayOf(red, green, blue, alpha)
102102
}
103103

104-
fun Color.toFloatArrayWithoutAlpha(): FloatArray {
104+
internal fun Color.toFloatArrayWithoutAlpha(): FloatArray {
105105
return floatArrayOf(red, green, blue)
106106
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package net.mullvad.mullvadvpn.lib.map.internal
22

3-
data class IndexBufferWithLength(val indexBuffer: Int, val length: Int)
3+
internal class IndexBufferWithLength(val indexBuffer: Int, val length: Int)

0 commit comments

Comments
 (0)