mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-11-08 08:01:09 +00:00
Bring back a gentle zoom-in to the globe's opening rotation
The opening rotation had lost the zoom-in it used to have. It now starts at 72% of the whole-globe size and grows into it while turning to the location, as one 1.4 s eased move, so the selection stays at level 1 and the entire globe stays in view. Pinching during an interrupted intro continues from the current size instead of snapping up to the minimum, and never pinches further out.
This commit is contained in:
parent
a3d7c14276
commit
e5d232004e
@ -37,7 +37,8 @@ class GlobeState(
|
||||
private set
|
||||
var centerLon by mutableDoubleStateOf(if (startZoomedOut) GlobeMath.normalizeLon(targetLon - 70.0) else targetLon)
|
||||
private set
|
||||
var zoom by mutableFloatStateOf(if (startZoomedOut) GlobeMath.MIN_ZOOM else 1f)
|
||||
// The opening rotation starts a little smaller than the whole-globe view and grows into it.
|
||||
var zoom by mutableFloatStateOf(if (startZoomedOut) INTRO_START_ZOOM else 1f)
|
||||
private set
|
||||
var precision by mutableIntStateOf(initialPrecision.coerceIn(1, GlobeMath.MAX_PRECISION))
|
||||
private set
|
||||
@ -93,7 +94,9 @@ class GlobeState(
|
||||
/** Continuous zoom from pinch gestures; precision follows automatically. */
|
||||
fun zoomBy(factor: Float) {
|
||||
if (factor == 1f) return
|
||||
zoom = (zoom * factor).coerceIn(GlobeMath.MIN_ZOOM, GlobeMath.MAX_ZOOM)
|
||||
// An interrupted intro can leave the globe below the minimum; continue from there rather
|
||||
// than snapping up, while never pinching further out than the current size.
|
||||
zoom = (zoom * factor).coerceIn(minOf(GlobeMath.MIN_ZOOM, zoom), GlobeMath.MAX_ZOOM)
|
||||
syncPrecisionFromZoom()
|
||||
syncSelection()
|
||||
}
|
||||
@ -192,7 +195,10 @@ class GlobeState(
|
||||
animateTo(centerLat, centerLon, targetZoom, p, durationMs = 450)
|
||||
}
|
||||
|
||||
/** Cinematic intro: spin and zoom from a far view into the target location. */
|
||||
/**
|
||||
* Cinematic intro: turn to the target while gently zooming from [INTRO_START_ZOOM] into the
|
||||
* frame for [targetPrecision] (the whole globe at level 1).
|
||||
*/
|
||||
private fun playIntro(targetLat: Double, targetLon: Double, targetPrecision: Int) {
|
||||
if (baseRadiusPx <= 0f) {
|
||||
// viewport not ready yet; retry once attached to layout via caller
|
||||
@ -255,6 +261,11 @@ class GlobeState(
|
||||
// clamping tighter would make syncSelection() encode the wrong cell.
|
||||
const val MIN_LAT = -89.0
|
||||
const val MAX_LAT = 89.0
|
||||
/**
|
||||
* Where the opening rotation's gentle zoom-in starts. Within [FLIGHT_ZOOM_RATIO] of the
|
||||
* whole-globe view, so the intro rotates and zooms together on the glide path.
|
||||
*/
|
||||
const val INTRO_START_ZOOM = 0.72f
|
||||
/** Zoom changes beyond this factor fly along [GlobeFlight]; smaller ones glide. */
|
||||
const val FLIGHT_ZOOM_RATIO = 1.5f
|
||||
const val FLIGHT_MS_PER_UNIT = 230.0
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
package com.bitchat.android.ui.globe
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class GlobeStateTest {
|
||||
|
||||
private fun introState() = GlobeState(
|
||||
targetLat = 57.0,
|
||||
targetLon = 10.0,
|
||||
initialPrecision = 1,
|
||||
startZoomedOut = true
|
||||
).apply { setViewport(baseRadiusPx = 475f, screenMinPx = 1080f) }
|
||||
|
||||
@Test
|
||||
fun intro_startsSlightlySmallerThanTheWholeGlobeAtLevelOne() {
|
||||
val state = introState()
|
||||
|
||||
assertTrue(state.zoom < GlobeMath.MIN_ZOOM)
|
||||
assertTrue(state.zoom > 0.5f)
|
||||
assertEquals(1, state.precision)
|
||||
assertEquals(1, state.selectedGeohash.length)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun wholeGlobeFrame_isLevelOne() {
|
||||
// The intro lands on the level-1 frame, which is the whole-globe view.
|
||||
assertEquals(
|
||||
GlobeMath.MIN_ZOOM,
|
||||
GlobeMath.zoomForPrecision(1, baseRadiusPx = 475f, screenMinPx = 1080f)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pinchingAnInterruptedIntro_continuesWithoutSnapping() {
|
||||
val state = introState()
|
||||
val start = state.zoom
|
||||
|
||||
state.zoomBy(1.1f)
|
||||
assertEquals(start * 1.1f, state.zoom, 1e-4f)
|
||||
|
||||
state.zoomBy(0.5f)
|
||||
assertEquals(start * 1.1f, state.zoom, 1e-4f)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun pinchingOut_stopsAtTheWholeGlobe() {
|
||||
val state = GlobeState(57.0, 10.0, initialPrecision = 1, startZoomedOut = false)
|
||||
.apply { setViewport(475f, 1080f) }
|
||||
|
||||
state.zoomBy(0.5f)
|
||||
|
||||
assertEquals(GlobeMath.MIN_ZOOM, state.zoom)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user