Compare commits

..
Author SHA1 Message Date
atsunatsu 1871c05002 build: release version 4.4.7-ba7opf.17.8 (531) 2026-09-27 21:26:16 +08:00
atsunatsu e9f58f76f6 feat(radar): keep the full-screen circle width share in small windows
The radar circle is a square, so it is limited by the shorter side of
the card it is drawn in: the small-window layout gave the radar card a
height of ~156dp in a 411x450 window, which shrank the circle to ~36% of
the page width while the full-screen layout shows ~84%.

- measure the real window with BoxWithConstraints instead of trusting the
  window size class, which can still report the full screen while the app
  runs in a split/floating window
- when the classic 1:1 split would give the radar less than 75% of the
  page width, use a fill layout: the radar takes the largest square that
  fits, a usable pager block is reserved under it while that keeps the
  circle at its full-screen share, otherwise the pager collapses into a
  48dp translucent tab strip with a tappable panel
- keep the normal two-row top area in the vertical layout: TimerRow and
  NextPassRow both carry weight(1f) internally, so putting them in a
  single row splits that row into two 50% blocks
- extract the geometry into pure Dp functions (useFillRadarLayout,
  radarFillSizes) covered by unit tests
2026-09-27 21:26:12 +08:00
atsunatsu 800494b978 feat(radar): fill radar in split-screen via measured window size
The previous compact-mode trigger used currentWindowAdaptiveInfo(),
which reports the full-screen size class in split-screen / multi-window,
so the radar never enlarged there. Measure the actual window bounds with
BoxWithConstraints: compact (radar weight 1f fills all space, pager fixed
132dp strip) when maxWidth < 600dp && maxHeight < 480dp.

build: release version 4.4.7-ba7opf.17.6 (529)
2026-09-27 20:13:13 +08:00
4 changed files with 420 additions and 57 deletions

No files matched your search

+3 -1
View File
@@ -10,4 +10,6 @@ dependencies {
implementation(project(":feature:mutual"))
implementation(project(":feature:cw"))
implementation("androidx.constraintlayout:constraintlayout:2.2.1")
}
testImplementation(libs.test.junit4)
testImplementation(libs.test.coroutines)
}
@@ -28,14 +28,18 @@ import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.pager.HorizontalPager
@@ -43,19 +47,24 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.keepScreenOn
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
@@ -74,8 +83,6 @@ import com.rtbishop.look4sat.core.presentation.TimerRow
import com.rtbishop.look4sat.core.presentation.TopBar
import com.rtbishop.look4sat.core.presentation.formatFrequency
import com.rtbishop.look4sat.core.presentation.getDefaultPass
import com.rtbishop.look4sat.core.presentation.hasEnoughHeight
import com.rtbishop.look4sat.core.presentation.isVerticalLayout
import com.rtbishop.look4sat.core.presentation.layoutPadding
import kotlinx.coroutines.launch
import kotlinx.coroutines.delay
@@ -88,6 +95,85 @@ private enum class RadarPage(val title: String) {
Sstv("SSTV")
}
/**
* Always-visible translucent pager tab strip drawn over the radar in small windows.
*/
private val COMPACT_TAB_STRIP_HEIGHT = 48.dp
/**
* Smallest pager block worth reserving below the radar before falling back to the
* overlaid tab strip + panel. Below this the pager would be unusable anyway.
*/
private val COMPACT_MIN_PAGER_HEIGHT = 96.dp
/**
* Vertical chrome the classic layout spends outside the radar: two 48dp top bar rows
* plus three 6dp row gaps (bar/bar/radar/pager).
*/
private val CLASSIC_VERTICAL_CHROME = 114.dp
/**
* The radar circle is 0.95 x min(cardWidth, cardHeight), so a card that is wider than it
* is tall always shows a small circle with empty bands on the sides. Full screen the radar
* card ends up ~0.88 x cardWidth tall (circle ~84% of the page width). Once the classic
* 1:1 split on a vertical window would give the radar less than this share of its own
* width, the fill layout takes over so the circle keeps the full-screen width proportion
* instead of shrinking with the window height.
*/
private const val FILL_RADAR_SPLIT_RATIO = 0.75f
/**
* Smallest radar side (as a share of the page width) worth keeping a reserved pager block for.
* 0.88 x 0.95 = ~0.84 = the circle's share of the page width full screen, so the pager is only
* preserved while reserving it costs the circle nothing against the full-screen proportion.
*/
private const val MIN_RADAR_SHARE_WITH_PAGER = 0.88f
/** Vertical gap kept between the stacked cards. */
private val ROW_GAP = 6.dp
/**
* True when a vertical window is too short for the classic 1:1 split to keep the radar circle
* at its full-screen share of the page width. The split gives the radar
* `(height - chrome) / 2`, so a short window caps the circle by that height instead of by the
* page width.
*/
internal fun useFillRadarLayout(isVertical: Boolean, maxWidth: Dp, maxHeight: Dp): Boolean =
isVertical && (maxHeight - CLASSIC_VERTICAL_CHROME) / 2 < maxWidth * FILL_RADAR_SPLIT_RATIO
/**
* Geometry of the fill layout: the square the radar keeps for itself (largest square that fits,
* so the circle spans the page width like it does full screen) and the space left for the pager.
* When that space is too small to be usable the pager is overlaid on the plot instead.
*/
internal data class RadarFillSizes(
val radarSide: Dp,
val pagerSpace: Dp,
val pagerOverlaid: Boolean
)
internal fun radarFillSizes(maxWidth: Dp, maxHeight: Dp): RadarFillSizes {
// Preferred: keep a usable pager block under the radar square. That is only worth it while
// the circle still reaches its full-screen share of the page width.
val radarWithPager = minOf(maxWidth, maxHeight - COMPACT_MIN_PAGER_HEIGHT - ROW_GAP)
if (radarWithPager >= maxWidth * MIN_RADAR_SHARE_WITH_PAGER) {
return RadarFillSizes(
radarSide = radarWithPager,
pagerSpace = maxHeight - radarWithPager - ROW_GAP,
pagerOverlaid = false
)
}
// Window too short for both: the radar takes the largest square it can and the pager is
// folded into the overlaid tab strip.
val radarSide = minOf(maxWidth, maxHeight)
val pagerSpace = maxHeight - radarSide - ROW_GAP
return RadarFillSizes(
radarSide = radarSide,
pagerSpace = pagerSpace,
pagerOverlaid = pagerSpace < COMPACT_MIN_PAGER_HEIGHT
)
}
@Composable
fun RadarDestination(navigateUp: () -> Unit) {
val context = LocalContext.current
@@ -173,43 +259,188 @@ private fun RadarScreen(
time = it.time
)
}
Column(
BoxWithConstraints(
modifier = Modifier
.layoutPadding()
.padding(bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding())
.keepScreenOn(),
verticalArrangement = Arrangement.spacedBy(6.dp)
.keepScreenOn()
) {
val isVertical = isVerticalLayout()
// In split-screen / small windows the vertical space is scarce: give the
// radar card the priority share so the plot stays large (its Canvas is a
// square limited by the card's smaller dimension), and shrink the pager
// card — its pages are all scrollable, so nothing gets clipped.
val radarWeight = if (isVertical && !hasEnoughHeight()) 1.6f else 1f
val pagerWeight = if (isVertical && !hasEnoughHeight()) 0.6f else 1f
if (isVertical) {
TopBar {
IconCard(action = navigateUp, resId = R.drawable.ic_back)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
// Measure the ACTUAL window constraints instead of the window size
// class: currentWindowAdaptiveInfo() often reports the full-screen
// size in split-screen / multi-window, so the compact branch never
// triggered there. maxHeight/maxWidth are the real window bounds.
val isVertical = maxWidth < 600.dp
// The classic vertical layout gives the radar a 1:1 share of the height while also
// paying for two top bar rows. In split-screen / small windows that leaves a card that
// is wider than it is tall, so the circle is capped by the height and stops filling the
// page width. Below the full-screen proportion the fill layout takes over: one top bar
// row, the radar keeps the largest square it can, the pager yields (reserved block when
// the window is tall enough, overlaid tab strip + panel when it is not).
val fillRadar = useFillRadarLayout(isVertical, maxWidth, maxHeight)
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(6.dp)
) {
if (isVertical) {
// Normal vertical top area: the timer card keeps its own row and the pass card
// gets a full-width row of its own. Never merge them into one row — both cards
// carry weight(1f), so side by side they split the width into two halves.
TopBar {
IconCard(action = navigateUp, resId = R.drawable.ic_back)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
}
TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) }
} else {
TopBar {
IconCard(action = navigateUp, resId = R.drawable.ic_back)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc)
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
}
}
TopBar { NextPassRow(pass = upcomingPass, isUtc = uiState.isUtc) }
} else {
TopBar {
IconCard(action = navigateUp, resId = R.drawable.ic_back)
TimerRow(timeString = uiState.currentTime, isTimeAos = uiState.isTimeAos)
NextPassRow(pass = upcomingPass, modifier = Modifier.weight(1f), isUtc = uiState.isUtc)
IconCard(action = addToCalendar, resId = R.drawable.ic_calendar)
if (isVertical) {
if (fillRadar) {
RadarFillArea(
uiState = uiState,
trackB = trackB,
trackBPosition = trackBPosition,
onAction = onAction,
logViewModel = logViewModel,
requestMicPermission = requestMicPermission,
modifier = Modifier.weight(1f)
)
} else {
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
}
} else {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
}
}
}
if (isVertical) {
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(radarWeight))
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(pagerWeight))
} else {
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
RadarCard(uiState, trackB, trackBPosition, Modifier.weight(1f))
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.weight(1f))
}
}
/**
* Radar area for split-screen / small windows. The radar takes the largest square the area
* can hold so the circle spans the same share of the width it does full screen, and the
* pager yields: a real block below the square when there is room, otherwise a translucent
* tab strip overlaid on the plot that opens the pager panel on top of it.
*/
@Composable
private fun RadarFillArea(
uiState: RadarState,
trackB: List<OrbitalPos>,
trackBPosition: OrbitalPos?,
onAction: (RadarAction) -> Unit,
logViewModel: LogViewModel,
requestMicPermission: () -> Unit,
modifier: Modifier = Modifier
) {
BoxWithConstraints(modifier = modifier.fillMaxSize()) {
val sizes = radarFillSizes(maxWidth, maxHeight)
// Hoisted out of the nested Box scope: the BoxWithConstraints receiver is not
// implicitly reachable inside it.
val pagerPanelHeight = maxHeight * 0.72f
if (!sizes.pagerOverlaid) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(ROW_GAP)
) {
RadarCard(uiState, trackB, trackBPosition, Modifier.height(sizes.radarSide).fillMaxWidth())
PagerCard(uiState, onAction, logViewModel, requestMicPermission, Modifier.height(sizes.pagerSpace))
}
} else {
Box(modifier = Modifier.fillMaxSize()) {
RadarCard(uiState, trackB, trackBPosition, Modifier.fillMaxSize(), overlayStrip = true)
CompactPagerOverlay(
uiState = uiState,
onAction = onAction,
logViewModel = logViewModel,
requestMicPermission = requestMicPermission,
panelHeight = pagerPanelHeight,
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
}
}
/**
* Collapsed pager for short windows: a translucent row of tab names over the bottom of the
* radar plot; picking one opens the full pager panel on top of the plot, collapsible again
* with the chevron in its tab row.
*/
@Composable
private fun CompactPagerOverlay(
uiState: RadarState,
onAction: (RadarAction) -> Unit,
logViewModel: LogViewModel,
requestMicPermission: () -> Unit,
panelHeight: Dp,
modifier: Modifier = Modifier
) {
val pages = rememberRadarPages(uiState)
var openPage by remember { mutableStateOf<RadarPage?>(null) }
val currentPage = openPage
if (currentPage == null) {
Surface(
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.88f),
shape = MaterialTheme.shapes.small,
modifier = modifier
.fillMaxWidth()
.height(COMPACT_TAB_STRIP_HEIGHT)
) {
Row(modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically) {
pages.forEach { page ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.clickable { openPage = page }
) {
Text(
text = page.title,
fontSize = 13.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center
)
}
}
}
}
} else {
PagerCard(
uiState = uiState,
onAction = onAction,
logViewModel = logViewModel,
requestMicPermission = requestMicPermission,
modifier = modifier
.fillMaxWidth()
.height(panelHeight),
startPage = currentPage,
onCollapse = { openPage = null }
)
}
}
/** Page list of the pager card, shared by the inline pager and the compact overlay panel. */
@Composable
private fun rememberRadarPages(uiState: RadarState): List<RadarPage> {
val hasCalculatorPage = remember(uiState.transceivers.transmitters) {
uiState.transceivers.transmitters.any(DopplerFrequencyCalculator::isNamedLinearTransponder)
}
return remember(hasCalculatorPage) {
buildList {
add(RadarPage.Transceivers)
if (hasCalculatorPage) add(RadarPage.Calculator)
add(RadarPage.Log)
add(RadarPage.Sstv)
}
}
}
@@ -220,20 +451,15 @@ private fun PagerCard(
onAction: (RadarAction) -> Unit,
logViewModel: LogViewModel,
requestMicPermission: () -> Unit,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
startPage: RadarPage? = null,
onCollapse: (() -> Unit)? = null
) {
val hasCalculatorPage = remember(uiState.transceivers.transmitters) {
uiState.transceivers.transmitters.any(DopplerFrequencyCalculator::isNamedLinearTransponder)
}
val pages = remember(hasCalculatorPage) {
buildList {
add(RadarPage.Transceivers)
if (hasCalculatorPage) add(RadarPage.Calculator)
add(RadarPage.Log)
add(RadarPage.Sstv)
}
}
val pagerState = rememberPagerState(pageCount = { pages.size })
val pages = rememberRadarPages(uiState)
val pagerState = rememberPagerState(
initialPage = startPage?.let { pages.indexOf(it) }?.coerceAtLeast(0) ?: 0,
pageCount = { pages.size }
)
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(pages.size) {
@@ -244,12 +470,26 @@ private fun PagerCard(
ElevatedCard(modifier = modifier) {
Column(modifier = Modifier.fillMaxSize()) {
val selectedTabIndex = pagerState.currentPage.coerceIn(0, pages.lastIndex)
PrimaryTabRow(selectedTabIndex = selectedTabIndex) {
pages.forEachIndexed { index, page ->
Tab(
selected = selectedTabIndex == index,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
text = { Text(text = page.title, maxLines = 1, overflow = TextOverflow.Ellipsis) }
Row(verticalAlignment = Alignment.CenterVertically) {
PrimaryTabRow(
selectedTabIndex = selectedTabIndex,
modifier = if (onCollapse != null) Modifier.weight(1f) else Modifier
) {
pages.forEachIndexed { index, page ->
Tab(
selected = selectedTabIndex == index,
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
text = { Text(text = page.title, maxLines = 1, overflow = TextOverflow.Ellipsis) }
)
}
}
onCollapse?.let { collapse ->
Text(
text = "▼",
fontSize = 14.sp,
modifier = Modifier
.padding(horizontal = 12.dp)
.clickable { collapse() }
)
}
}
@@ -294,7 +534,8 @@ private fun RadarCard(
uiState: RadarState,
trackB: List<OrbitalPos> = emptyList(),
trackBPosition: OrbitalPos? = null,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
overlayStrip: Boolean = false
) {
val satellitePos = uiState.orbitalPos
val shouldAnimateBorder = satellitePos?.aboveHorizon == true && satellitePos.eclipsed
@@ -337,19 +578,25 @@ private fun RadarCard(
sunPosition = uiState.sunPosition,
moonPosition = uiState.moonPosition,
)
PositionOverlay(position)
PositionOverlay(position, overlayStrip)
}
}
}
}
@Composable
private fun PositionOverlay(position: OrbitalPos) {
private fun PositionOverlay(position: OrbitalPos, overlayStrip: Boolean = false) {
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 6.dp, vertical = 4.dp)
// Keep the bottom labels clear of the overlaid pager tab strip in small windows.
.padding(
start = 6.dp,
end = 6.dp,
top = 4.dp,
bottom = if (overlayStrip) COMPACT_TAB_STRIP_HEIGHT + 4.dp else 4.dp
)
) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
@@ -0,0 +1,114 @@
/*
* Look4Sat. Amateur radio satellite tracker and pass predictor.
* Copyright (C) 2019-2026 Arty Bishop and contributors.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.rtbishop.look4sat.feature.radar
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import kotlin.math.min
/**
* Small-window radar sizing.
*
* The plotted circle is `0.95 x min(cardWidth, cardHeight)`, so a radar card that is wider than
* it is tall caps the circle by its height and shows empty bands on the sides. Full screen the
* classic 1:1 split leaves the radar card ~0.88 x cardWidth tall, i.e. the circle spans ~84% of
* the page width. These tests pin the split-screen / small-window layout to the same width share
* instead of letting the circle shrink with the window height.
*/
class RadarFillLayoutTest {
/** Circle diameter as a share of the page width (radar canvas rule: 0.95 * min(width, height)). */
private fun circleShare(width: Dp, height: Dp, sizes: RadarFillSizes): Float {
val availableHeight = if (sizes.pagerOverlaid) height else sizes.radarSide
return 0.95f * min(width.value, availableHeight.value) / width.value
}
@Test
fun fullScreenVerticalKeepsClassicSplit() {
// 411 x 867dp full screen: the 1:1 split already gives the circle ~87% of the width,
// so the full-screen layout must not change.
assertFalse(useFillRadarLayout(isVertical = true, maxWidth = 411.dp, maxHeight = 867.dp))
assertFalse(useFillRadarLayout(isVertical = true, maxWidth = 393.dp, maxHeight = 804.dp))
}
@Test
fun splitScreenAndSmallWindowsUseFillLayout() {
assertTrue(useFillRadarLayout(true, 411.dp, 426.dp)) // portrait split-screen half
assertTrue(useFillRadarLayout(true, 411.dp, 500.dp)) // floating small window
assertTrue(useFillRadarLayout(true, 411.dp, 676.dp)) // 70/30 split
assertTrue(useFillRadarLayout(true, 393.dp, 560.dp))
}
@Test
fun wideWindowsKeepSideBySideLayout() {
assertFalse(useFillRadarLayout(isVertical = false, maxWidth = 700.dp, maxHeight = 400.dp))
}
@Test
fun fillLayoutKeepsCircleAtFullScreenWidthShare() {
// Previously these rendered the circle at ~36% of the page width.
val cases = listOf(
411.dp to 426.dp,
411.dp to 500.dp,
393.dp to 560.dp,
411.dp to 676.dp
)
cases.forEach { (width, height) ->
val share = circleShare(width, height, radarFillSizes(width, height))
assertTrue("circle is only ${share * 100}% of the page width at ${width.value}x${height.value}",
share >= 0.84f)
}
}
@Test
fun classicSplitWouldHaveSqueezedTheCircle() {
// Regression guard documenting why the fill layout exists: the old compact branch left
// a 411 x 156dp card, i.e. a circle at 36% of the page width, in the same split window.
val classicCompactCardHeight = 426.dp - 96.dp - 132.dp - 18.dp
val classicCompactShare = 0.95f * classicCompactCardHeight.value / 411f
assertTrue(classicCompactShare < 0.45f)
}
@Test
fun pagerIsOverlaidOnlyWhenAReservedBlockWouldBeUseless() {
val shortWindow = radarFillSizes(411.dp, 426.dp)
assertTrue(shortWindow.pagerOverlaid)
assertTrue(shortWindow.radarSide == 411.dp)
val tallWindow = radarFillSizes(411.dp, 676.dp)
assertFalse(tallWindow.pagerOverlaid)
assertEquals(411f, tallWindow.radarSide.value, 0.01f)
assertEquals(259f, tallWindow.pagerSpace.value, 0.01f)
}
@Test
fun pagerBlockIsKeptWhileTheCircleStaysAtTheFullScreenShare() {
// 411 x 468dp: reserving 96dp for the pager still leaves the circle at ~85% of the page
// width, so the pager stays as a real block instead of folding into the overlay strip.
val sizes = radarFillSizes(411.dp, 468.dp)
assertFalse(sizes.pagerOverlaid)
assertEquals(366f, sizes.radarSide.value, 0.01f)
assertEquals(96f, sizes.pagerSpace.value, 0.01f)
val share = circleShare(411.dp, 468.dp, sizes)
assertTrue("circle is only ${share * 100}% of the page width", share >= 0.84f)
}
}
+2 -2
View File
@@ -1,8 +1,8 @@
[versions]
#noinspection UnusedVersionCatalogEntry
appVersionCode = "527"
appVersionCode = "531"
#noinspection UnusedVersionCatalogEntry
appVersionName = "4.4.7-ba7opf.17.4"
appVersionName = "4.4.7-ba7opf.17.8"
#noinspection UnusedVersionCatalogEntry
compileSdk = "37"
#noinspection UnusedVersionCatalogEntry