diff --git a/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt b/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt index 3d1d2736..6c20e39b 100644 --- a/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt +++ b/app/src/main/java/com/bitchat/android/ui/GeohashPickerActivity.kt @@ -95,13 +95,18 @@ class GeohashPickerActivity : OrientationAwareActivity() { BitchatTheme { val context = LocalContext.current val scope = rememberCoroutineScope() + val initialRenderQuality = remember(context) { + GlobeRenderQualityPreference.load(context) + } + var renderQuality by remember { mutableStateOf(initialRenderQuality) } val globeState = remember { GlobeState( targetLat = targetLat, targetLon = targetLon, initialPrecision = initialPrecision, - startZoomedOut = true + startZoomedOut = true, + initialRenderQuality = initialRenderQuality ).apply { introTarget = Triple(targetLat, targetLon, initialPrecision) } @@ -157,9 +162,6 @@ class GeohashPickerActivity : OrientationAwareActivity() { val labelTypeface = remember { ResourcesCompat.getFont(context, R.font.geist_mono_medium) } val labelTypefaceBold = remember { ResourcesCompat.getFont(context, R.font.geist_mono_semibold) } - var renderQuality by remember { - mutableStateOf(GlobeRenderQualityPreference.load(context)) - } Box( Modifier @@ -173,7 +175,6 @@ class GeohashPickerActivity : OrientationAwareActivity() { land = rings, borders = borders, cities = cities, - renderQuality = renderQuality, labelTypeface = labelTypeface, labelTypefaceBold = labelTypefaceBold, modifier = Modifier.fillMaxSize() @@ -210,9 +211,11 @@ class GeohashPickerActivity : OrientationAwareActivity() { modifier = Modifier.fillMaxWidth() ) { qualities.forEachIndexed { index, quality -> + val selected = renderQuality == quality SegmentedButton( - selected = renderQuality == quality, + selected = selected, onClick = { + globeState.setRenderQuality(quality) renderQuality = quality GlobeRenderQualityPreference.save(context, quality) }, @@ -220,12 +223,27 @@ class GeohashPickerActivity : OrientationAwareActivity() { index = index, count = qualities.size ), + icon = {}, label = { - Text( - text = stringResource(quality.labelResource), - fontSize = 11.sp, - fontFamily = BitchatFontFamily - ) + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + if (selected) { + Icon( + imageVector = Icons.Filled.Check, + contentDescription = null, + modifier = Modifier + .align(Alignment.CenterStart) + .size(18.dp) + ) + } + Text( + text = stringResource(quality.labelResource), + fontSize = 11.sp, + fontFamily = BitchatFontFamily + ) + } } ) } diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt index 6c62d0ef..302434d8 100644 --- a/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeState.kt @@ -28,7 +28,8 @@ class GlobeState( targetLat: Double, targetLon: Double, initialPrecision: Int, - startZoomedOut: Boolean + startZoomedOut: Boolean, + initialRenderQuality: GlobeRenderQuality = GlobeRenderQuality.MEDIUM ) { var centerLat by mutableFloatStateOf(if (startZoomedOut) (targetLat * 0.4).toFloat() else targetLat.toFloat()) private set @@ -47,6 +48,8 @@ class GlobeState( internal var baseRadiusPx by mutableFloatStateOf(0f) internal var screenMinPx by mutableFloatStateOf(0f) + internal var renderQuality = initialRenderQuality + private set private var scope: CoroutineScope? = null private var animJob: Job? = null @@ -68,6 +71,14 @@ class GlobeState( this.scope = scope } + /** + * Rendering quality only affects moving frames. This is deliberately not snapshot state: + * changing it must not invalidate the expensive stationary globe beneath the selector. + */ + fun setRenderQuality(quality: GlobeRenderQuality) { + renderQuality = quality + } + fun setViewport(baseRadiusPx: Float, screenMinPx: Float) { if (baseRadiusPx <= 0f || screenMinPx <= 0f) return this.baseRadiusPx = baseRadiusPx diff --git a/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt b/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt index e11e163a..25dd376a 100644 --- a/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt +++ b/app/src/main/java/com/bitchat/android/ui/globe/GlobeView.kt @@ -119,7 +119,6 @@ fun GlobeView( land: List, borders: List, cities: List, - renderQuality: GlobeRenderQuality, labelTypeface: Typeface?, labelTypefaceBold: Typeface?, modifier: Modifier = Modifier @@ -324,7 +323,7 @@ fun GlobeView( val clip = ClipRect(-size.width, -size.height, size.width * 2f, size.height * 2f) - val frameDetail = globeFrameDetail(renderQuality, state.isInMotion) + val frameDetail = globeFrameDetail(state.renderQuality, state.isInMotion) // Graticule drawGraticule( diff --git a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeRenderQualityTest.kt b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeRenderQualityTest.kt index 3712970f..1581e38f 100644 --- a/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeRenderQualityTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/ui/globe/GlobeRenderQualityTest.kt @@ -14,6 +14,21 @@ class GlobeRenderQualityTest { assertEquals(GlobeRenderQuality.MEDIUM, GlobeRenderQuality.fromStoredValue("UNKNOWN")) } + @Test + fun globeState_usesAndUpdatesRenderQualityWithoutReplacingState() { + val state = GlobeState( + targetLat = 0.0, + targetLon = 0.0, + initialPrecision = 2, + startZoomedOut = false, + initialRenderQuality = GlobeRenderQuality.FAST + ) + + assertEquals(GlobeRenderQuality.FAST, state.renderQuality) + state.setRenderQuality(GlobeRenderQuality.HIGH) + assertEquals(GlobeRenderQuality.HIGH, state.renderQuality) + } + @Test fun stationaryFrame_alwaysUsesFullDetail() { GlobeRenderQuality.entries.forEach { quality ->