mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-09-19 04:59:59 +00:00
Add geohash globe render quality selector
This commit is contained in:
parent
5bde4e0b4a
commit
8602db88de
@ -15,6 +15,9 @@ import androidx.compose.material3.Button
|
|||||||
import androidx.compose.material3.ButtonDefaults
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SegmentedButton
|
||||||
|
import androidx.compose.material3.SegmentedButtonDefaults
|
||||||
|
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
@ -34,6 +37,8 @@ import com.bitchat.android.geohash.Geohash
|
|||||||
import com.bitchat.android.geohash.GeohashChannelLevel
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
import com.bitchat.android.geohash.LocationChannelManager
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
import com.bitchat.android.ui.globe.GlobeColors
|
import com.bitchat.android.ui.globe.GlobeColors
|
||||||
|
import com.bitchat.android.ui.globe.GlobeRenderQuality
|
||||||
|
import com.bitchat.android.ui.globe.GlobeRenderQualityPreference
|
||||||
import com.bitchat.android.ui.globe.GlobeState
|
import com.bitchat.android.ui.globe.GlobeState
|
||||||
import com.bitchat.android.ui.globe.GlobeView
|
import com.bitchat.android.ui.globe.GlobeView
|
||||||
import com.bitchat.android.ui.globe.LandData
|
import com.bitchat.android.ui.globe.LandData
|
||||||
@ -152,6 +157,9 @@ class GeohashPickerActivity : OrientationAwareActivity() {
|
|||||||
|
|
||||||
val labelTypeface = remember { ResourcesCompat.getFont(context, R.font.geist_mono_medium) }
|
val labelTypeface = remember { ResourcesCompat.getFont(context, R.font.geist_mono_medium) }
|
||||||
val labelTypefaceBold = remember { ResourcesCompat.getFont(context, R.font.geist_mono_semibold) }
|
val labelTypefaceBold = remember { ResourcesCompat.getFont(context, R.font.geist_mono_semibold) }
|
||||||
|
var renderQuality by remember {
|
||||||
|
mutableStateOf(GlobeRenderQualityPreference.load(context))
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
@ -165,6 +173,7 @@ class GeohashPickerActivity : OrientationAwareActivity() {
|
|||||||
land = rings,
|
land = rings,
|
||||||
borders = borders,
|
borders = borders,
|
||||||
cities = cities,
|
cities = cities,
|
||||||
|
renderQuality = renderQuality,
|
||||||
labelTypeface = labelTypeface,
|
labelTypeface = labelTypeface,
|
||||||
labelTypefaceBold = labelTypefaceBold,
|
labelTypefaceBold = labelTypefaceBold,
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
@ -183,15 +192,45 @@ class GeohashPickerActivity : OrientationAwareActivity() {
|
|||||||
tonalElevation = 3.dp,
|
tonalElevation = 3.dp,
|
||||||
shadowElevation = 6.dp
|
shadowElevation = 6.dp
|
||||||
) {
|
) {
|
||||||
Text(
|
Column(
|
||||||
text = stringResource(R.string.pan_zoom_instruction),
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
fontSize = 12.sp,
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
fontFamily = BitchatFontFamily,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp)
|
.padding(horizontal = 14.dp, vertical = 10.dp)
|
||||||
)
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.pan_zoom_instruction),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
fontFamily = BitchatFontFamily,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
Spacer(Modifier.height(8.dp))
|
||||||
|
val qualities = GlobeRenderQuality.entries
|
||||||
|
SingleChoiceSegmentedButtonRow(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
qualities.forEachIndexed { index, quality ->
|
||||||
|
SegmentedButton(
|
||||||
|
selected = renderQuality == quality,
|
||||||
|
onClick = {
|
||||||
|
renderQuality = quality
|
||||||
|
GlobeRenderQualityPreference.save(context, quality)
|
||||||
|
},
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(
|
||||||
|
index = index,
|
||||||
|
count = qualities.size
|
||||||
|
),
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(quality.labelResource),
|
||||||
|
fontSize = 11.sp,
|
||||||
|
fontFamily = BitchatFontFamily
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Floating bottom controls
|
// Floating bottom controls
|
||||||
@ -290,6 +329,13 @@ class GeohashPickerActivity : OrientationAwareActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val GlobeRenderQuality.labelResource: Int
|
||||||
|
get() = when (this) {
|
||||||
|
GlobeRenderQuality.FAST -> R.string.globe_render_quality_fast
|
||||||
|
GlobeRenderQuality.MEDIUM -> R.string.globe_render_quality_medium
|
||||||
|
GlobeRenderQuality.HIGH -> R.string.globe_render_quality_high
|
||||||
|
}
|
||||||
|
|
||||||
private fun levelForLength(length: Int): GeohashChannelLevel {
|
private fun levelForLength(length: Int): GeohashChannelLevel {
|
||||||
return when (length) {
|
return when (length) {
|
||||||
in 0..2 -> GeohashChannelLevel.REGION
|
in 0..2 -> GeohashChannelLevel.REGION
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
package com.bitchat.android.ui.globe
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
|
||||||
|
enum class GlobeRenderQuality {
|
||||||
|
FAST,
|
||||||
|
MEDIUM,
|
||||||
|
HIGH;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromStoredValue(value: String?): GlobeRenderQuality =
|
||||||
|
entries.firstOrNull { it.name == value } ?: MEDIUM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object GlobeRenderQualityPreference {
|
||||||
|
private const val PREFERENCES_NAME = "bitchat_settings"
|
||||||
|
private const val KEY_RENDER_QUALITY = "geohash_globe_render_quality"
|
||||||
|
|
||||||
|
fun load(context: Context): GlobeRenderQuality {
|
||||||
|
val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||||
|
return GlobeRenderQuality.fromStoredValue(
|
||||||
|
preferences.getString(KEY_RENDER_QUALITY, GlobeRenderQuality.MEDIUM.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save(context: Context, quality: GlobeRenderQuality) {
|
||||||
|
context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE)
|
||||||
|
.edit()
|
||||||
|
.putString(KEY_RENDER_QUALITY, quality.name)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -64,6 +64,54 @@ data class GlobeColors(
|
|||||||
|
|
||||||
private class Star(val x: Float, val y: Float, val radius: Float, val alpha: Float)
|
private class Star(val x: Float, val y: Float, val radius: Float, val alpha: Float)
|
||||||
|
|
||||||
|
internal data class GlobeFrameDetail(
|
||||||
|
val graticuleStepDegrees: Double,
|
||||||
|
val landPointStride: Int,
|
||||||
|
val showBorders: Boolean,
|
||||||
|
val cityMaxRank: Int?,
|
||||||
|
val showCityLabels: Boolean,
|
||||||
|
val showGeohashGrid: Boolean,
|
||||||
|
val showNeighborCells: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
internal fun globeFrameDetail(
|
||||||
|
quality: GlobeRenderQuality,
|
||||||
|
isMoving: Boolean
|
||||||
|
): GlobeFrameDetail {
|
||||||
|
if (!isMoving || quality == GlobeRenderQuality.HIGH) {
|
||||||
|
return GlobeFrameDetail(
|
||||||
|
graticuleStepDegrees = 4.0,
|
||||||
|
landPointStride = 1,
|
||||||
|
showBorders = true,
|
||||||
|
cityMaxRank = null,
|
||||||
|
showCityLabels = true,
|
||||||
|
showGeohashGrid = true,
|
||||||
|
showNeighborCells = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return when (quality) {
|
||||||
|
GlobeRenderQuality.FAST -> GlobeFrameDetail(
|
||||||
|
graticuleStepDegrees = 10.0,
|
||||||
|
landPointStride = 2,
|
||||||
|
showBorders = false,
|
||||||
|
cityMaxRank = -1,
|
||||||
|
showCityLabels = false,
|
||||||
|
showGeohashGrid = false,
|
||||||
|
showNeighborCells = false
|
||||||
|
)
|
||||||
|
GlobeRenderQuality.MEDIUM -> GlobeFrameDetail(
|
||||||
|
graticuleStepDegrees = 8.0,
|
||||||
|
landPointStride = 2,
|
||||||
|
showBorders = true,
|
||||||
|
cityMaxRank = 1,
|
||||||
|
showCityLabels = false,
|
||||||
|
showGeohashGrid = true,
|
||||||
|
showNeighborCells = false
|
||||||
|
)
|
||||||
|
GlobeRenderQuality.HIGH -> error("Handled above")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GlobeView(
|
fun GlobeView(
|
||||||
state: GlobeState,
|
state: GlobeState,
|
||||||
@ -71,6 +119,7 @@ fun GlobeView(
|
|||||||
land: List<LandData.Ring>,
|
land: List<LandData.Ring>,
|
||||||
borders: List<LandData.Ring>,
|
borders: List<LandData.Ring>,
|
||||||
cities: List<LandData.City>,
|
cities: List<LandData.City>,
|
||||||
|
renderQuality: GlobeRenderQuality,
|
||||||
labelTypeface: Typeface?,
|
labelTypeface: Typeface?,
|
||||||
labelTypefaceBold: Typeface?,
|
labelTypefaceBold: Typeface?,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
@ -275,12 +324,12 @@ fun GlobeView(
|
|||||||
|
|
||||||
val clip = ClipRect(-size.width, -size.height, size.width * 2f, size.height * 2f)
|
val clip = ClipRect(-size.width, -size.height, size.width * 2f, size.height * 2f)
|
||||||
|
|
||||||
val lowDetail = state.isInMotion
|
val frameDetail = globeFrameDetail(renderQuality, state.isInMotion)
|
||||||
|
|
||||||
// Graticule
|
// Graticule
|
||||||
drawGraticule(
|
drawGraticule(
|
||||||
cx, cy, r, cLat, cLon, colors.graticule, clip,
|
cx, cy, r, cLat, cLon, colors.graticule, clip,
|
||||||
step = if (lowDetail) 10.0 else 4.0
|
step = frameDetail.graticuleStepDegrees
|
||||||
)
|
)
|
||||||
|
|
||||||
// Landmasses
|
// Landmasses
|
||||||
@ -294,12 +343,12 @@ fun GlobeView(
|
|||||||
r = r,
|
r = r,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
clip = clip,
|
clip = clip,
|
||||||
pointStride = if (lowDetail && ring.size >= 64) 2 else 1
|
pointStride = if (ring.size >= 64) frameDetail.landPointStride else 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Country borders are restored when interaction settles.
|
// Country borders are restored when interaction settles.
|
||||||
if (!lowDetail) {
|
if (frameDetail.showBorders) {
|
||||||
for (line in borders) {
|
for (line in borders) {
|
||||||
drawBorderLine(line, borderScratch, preparedProjector, cx, cy, r, colors, clip)
|
drawBorderLine(line, borderScratch, preparedProjector, cx, cy, r, colors, clip)
|
||||||
}
|
}
|
||||||
@ -331,20 +380,27 @@ fun GlobeView(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Cities are detail-only; omitting them while moving keeps touch latency predictable.
|
// Cities are detail-only; omitting them while moving keeps touch latency predictable.
|
||||||
if (!lowDetail) {
|
val cityMaxRank = frameDetail.cityMaxRank
|
||||||
|
if (cityMaxRank == null || cityMaxRank >= 0) {
|
||||||
drawCities(
|
drawCities(
|
||||||
cities, state, preparedProjector, cx, cy, r, colors,
|
cities, state, preparedProjector, cx, cy, r, colors,
|
||||||
labelPaint, haloPaint, labelTypeface, labelTextSizeSmall, density.density
|
labelPaint, haloPaint, labelTypeface, labelTextSizeSmall, density.density,
|
||||||
|
maxRankOverride = cityMaxRank,
|
||||||
|
showLabels = frameDetail.showCityLabels
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detailed cells and labels settle into place after the gesture ends.
|
// Detailed cells and labels settle into place after the gesture ends.
|
||||||
if (!lowDetail && state.selectedGeohash.isNotEmpty()) {
|
if (frameDetail.showGeohashGrid && state.selectedGeohash.isNotEmpty()) {
|
||||||
drawGeohashGrid(state, cx, cy, r, cLat, cLon, colors, clip)
|
drawGeohashGrid(
|
||||||
|
state, cx, cy, r, cLat, cLon, colors, clip,
|
||||||
|
includeNeighbors = frameDetail.showNeighborCells
|
||||||
|
)
|
||||||
drawGeohashLabels(
|
drawGeohashLabels(
|
||||||
state, cx, cy, r, cLat, cLon, colors,
|
state, cx, cy, r, cLat, cLon, colors,
|
||||||
labelPaint, haloPaint, labelTypeface, labelTypefaceBold,
|
labelPaint, haloPaint, labelTypeface, labelTypefaceBold,
|
||||||
labelTextSize, labelTextSizeSmall
|
labelTextSize, labelTextSizeSmall,
|
||||||
|
includeNeighbors = frameDetail.showNeighborCells
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -697,12 +753,14 @@ private fun DrawScope.drawCities(
|
|||||||
haloPaint: Paint,
|
haloPaint: Paint,
|
||||||
typeface: Typeface?,
|
typeface: Typeface?,
|
||||||
textSize: Float,
|
textSize: Float,
|
||||||
density: Float
|
density: Float,
|
||||||
|
maxRankOverride: Int?,
|
||||||
|
showLabels: Boolean
|
||||||
) {
|
) {
|
||||||
if (cities.isEmpty()) return
|
if (cities.isEmpty()) return
|
||||||
val projection = FloatArray(3)
|
val projection = FloatArray(3)
|
||||||
val zoom = state.zoom
|
val zoom = state.zoom
|
||||||
val maxRank = when {
|
val maxRank = maxRankOverride ?: when {
|
||||||
zoom < 2f -> 1
|
zoom < 2f -> 1
|
||||||
zoom < 8f -> 3
|
zoom < 8f -> 3
|
||||||
zoom < 40f -> 4
|
zoom < 40f -> 4
|
||||||
@ -725,7 +783,7 @@ private fun DrawScope.drawCities(
|
|||||||
else colors.label.copy(alpha = alpha * 0.85f)
|
else colors.label.copy(alpha = alpha * 0.85f)
|
||||||
drawCircle(dotColor, radius = dotRadius, center = Offset(sx, sy))
|
drawCircle(dotColor, radius = dotRadius, center = Offset(sx, sy))
|
||||||
|
|
||||||
if (zoom >= 6f || (important && zoom >= 2.5f)) {
|
if (showLabels && (zoom >= 6f || (important && zoom >= 2.5f))) {
|
||||||
labelPaint.textSize = textSize
|
labelPaint.textSize = textSize
|
||||||
labelPaint.typeface = typeface
|
labelPaint.typeface = typeface
|
||||||
labelPaint.textAlign = Paint.Align.LEFT
|
labelPaint.textAlign = Paint.Align.LEFT
|
||||||
@ -789,11 +847,12 @@ private fun DrawScope.drawGeohashGrid(
|
|||||||
cx: Float, cy: Float, r: Float,
|
cx: Float, cy: Float, r: Float,
|
||||||
cLat: Double, cLon: Double,
|
cLat: Double, cLon: Double,
|
||||||
colors: GlobeColors,
|
colors: GlobeColors,
|
||||||
clip: ClipRect
|
clip: ClipRect,
|
||||||
|
includeNeighbors: Boolean
|
||||||
) {
|
) {
|
||||||
val selected = state.selectedGeohash
|
val selected = state.selectedGeohash
|
||||||
val cells = linkedSetOf(selected)
|
val cells = linkedSetOf(selected)
|
||||||
cells.addAll(Geohash.neighborsSamePrecision(selected))
|
if (includeNeighbors) cells.addAll(Geohash.neighborsSamePrecision(selected))
|
||||||
|
|
||||||
for (cell in cells) {
|
for (cell in cells) {
|
||||||
val isSelected = cell == selected
|
val isSelected = cell == selected
|
||||||
@ -856,11 +915,12 @@ private fun DrawScope.drawGeohashLabels(
|
|||||||
labelTypeface: Typeface?,
|
labelTypeface: Typeface?,
|
||||||
labelTypefaceBold: Typeface?,
|
labelTypefaceBold: Typeface?,
|
||||||
selectedSize: Float,
|
selectedSize: Float,
|
||||||
neighborSize: Float
|
neighborSize: Float,
|
||||||
|
includeNeighbors: Boolean
|
||||||
) {
|
) {
|
||||||
val selected = state.selectedGeohash
|
val selected = state.selectedGeohash
|
||||||
val cells = linkedSetOf(selected)
|
val cells = linkedSetOf(selected)
|
||||||
cells.addAll(Geohash.neighborsSamePrecision(selected))
|
if (includeNeighbors) cells.addAll(Geohash.neighborsSamePrecision(selected))
|
||||||
val canvas = drawContext.canvas.nativeCanvas
|
val canvas = drawContext.canvas.nativeCanvas
|
||||||
|
|
||||||
for (cell in cells) {
|
for (cell in cells) {
|
||||||
|
|||||||
@ -540,6 +540,9 @@
|
|||||||
<string name="nobody_around">Nobody around…</string>
|
<string name="nobody_around">Nobody around…</string>
|
||||||
<string name="you_suffix"> (you)</string>
|
<string name="you_suffix"> (you)</string>
|
||||||
<string name="pan_zoom_instruction">Drag to spin · Pinch to zoom · Tap to focus</string>
|
<string name="pan_zoom_instruction">Drag to spin · Pinch to zoom · Tap to focus</string>
|
||||||
|
<string name="globe_render_quality_fast">Fast</string>
|
||||||
|
<string name="globe_render_quality_medium">Medium</string>
|
||||||
|
<string name="globe_render_quality_high">High</string>
|
||||||
<string name="select">Select</string>
|
<string name="select">Select</string>
|
||||||
<string name="type_a_message_placeholder">Type a message…</string>
|
<string name="type_a_message_placeholder">Type a message…</string>
|
||||||
<string name="mention_suggestion_at">@%1$s</string>
|
<string name="mention_suggestion_at">@%1$s</string>
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
package com.bitchat.android.ui.globe
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class GlobeRenderQualityTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun invalidStoredValue_defaultsToMedium() {
|
||||||
|
assertEquals(GlobeRenderQuality.MEDIUM, GlobeRenderQuality.fromStoredValue(null))
|
||||||
|
assertEquals(GlobeRenderQuality.MEDIUM, GlobeRenderQuality.fromStoredValue("UNKNOWN"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stationaryFrame_alwaysUsesFullDetail() {
|
||||||
|
GlobeRenderQuality.entries.forEach { quality ->
|
||||||
|
val detail = globeFrameDetail(quality, isMoving = false)
|
||||||
|
|
||||||
|
assertEquals(1, detail.landPointStride)
|
||||||
|
assertTrue(detail.showBorders)
|
||||||
|
assertNull(detail.cityMaxRank)
|
||||||
|
assertTrue(detail.showCityLabels)
|
||||||
|
assertTrue(detail.showGeohashGrid)
|
||||||
|
assertTrue(detail.showNeighborCells)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingFastFrame_usesMinimumDetail() {
|
||||||
|
val detail = globeFrameDetail(GlobeRenderQuality.FAST, isMoving = true)
|
||||||
|
|
||||||
|
assertEquals(2, detail.landPointStride)
|
||||||
|
assertFalse(detail.showBorders)
|
||||||
|
assertEquals(-1, detail.cityMaxRank)
|
||||||
|
assertFalse(detail.showGeohashGrid)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingMediumFrame_preservesOrientationAndSelection() {
|
||||||
|
val detail = globeFrameDetail(GlobeRenderQuality.MEDIUM, isMoving = true)
|
||||||
|
|
||||||
|
assertEquals(2, detail.landPointStride)
|
||||||
|
assertTrue(detail.showBorders)
|
||||||
|
assertEquals(1, detail.cityMaxRank)
|
||||||
|
assertFalse(detail.showCityLabels)
|
||||||
|
assertTrue(detail.showGeohashGrid)
|
||||||
|
assertFalse(detail.showNeighborCells)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingHighFrame_usesFullDetail() {
|
||||||
|
val detail = globeFrameDetail(GlobeRenderQuality.HIGH, isMoving = true)
|
||||||
|
|
||||||
|
assertEquals(1, detail.landPointStride)
|
||||||
|
assertTrue(detail.showBorders)
|
||||||
|
assertNull(detail.cityMaxRank)
|
||||||
|
assertTrue(detail.showCityLabels)
|
||||||
|
assertTrue(detail.showNeighborCells)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user