fix(android): keep body metrics two-column on normal phones
This commit is contained in:
parent
60b08f225a
commit
0e13c3947a
2 changed files with 87 additions and 3 deletions
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
|
|
@ -378,7 +379,7 @@ private fun BodyMetricField(
|
|||
)
|
||||
}
|
||||
|
||||
private data class BodyMetricItem(
|
||||
internal data class BodyMetricItem(
|
||||
val label: String,
|
||||
val unit: String,
|
||||
val value: String,
|
||||
|
|
@ -386,10 +387,19 @@ private data class BodyMetricItem(
|
|||
)
|
||||
|
||||
/** Two columns retain scanability; very narrow accessibility layouts stack. */
|
||||
internal fun bodyMetricColumnCount(availableWidthDp: Float, fontScale: Float): Int {
|
||||
val twoCompactFieldsDp = 2 * 140f + 10f
|
||||
return if (availableWidthDp >= twoCompactFieldsDp && fontScale < 1.5f) 2 else 1
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.BodyMetricGrid(items: List<BodyMetricItem>) {
|
||||
internal fun ColumnScope.BodyMetricGrid(items: List<BodyMetricItem>) {
|
||||
BoxWithConstraints(Modifier.fillMaxWidth()) {
|
||||
val columns = if (maxWidth < 330.dp) 1 else 2
|
||||
/* WHY: this 360 dp device has 312 dp after screen padding; the old
|
||||
* 330 dp threshold therefore stacked fields that need only 140 dp.
|
||||
* CONTRACT: two compact fields plus their 10 dp gutter are preferred
|
||||
* unless width or accessibility font scaling makes them unreadable. */
|
||||
val columns = bodyMetricColumnCount(maxWidth.value, LocalDensity.current.fontScale)
|
||||
Column(verticalArrangement = Arrangement.spacedBy(0.dp)) {
|
||||
items.chunked(columns).forEach { rowItems ->
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
package com.labfytools.trainlog.ui
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.junit4.v2.createComposeRule
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.labfytools.trainlog.ui.theme.TrainlogTheme
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [35])
|
||||
class BodyMetricGridTest {
|
||||
@get:Rule val compose = createComposeRule()
|
||||
|
||||
private val items = listOf(
|
||||
BodyMetricItem("POIDS", "kg", "", {}),
|
||||
BodyMetricItem("COU", "cm", "", {}),
|
||||
BodyMetricItem("TOUR DE TAILLE", "cm", "", {}),
|
||||
BodyMetricItem("HANCHES", "cm", "", {}),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun deployedPhoneContentWidthUsesTwoColumns() {
|
||||
// 1080 px / 480 dpi = 360 dp; the screen frame leaves about 312 dp.
|
||||
assertEquals(2, bodyMetricColumnCount(312f, 0.9f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun genuinelyNarrowWidthStacksFields() {
|
||||
assertEquals(1, bodyMetricColumnCount(280f, 1f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun largeAccessibilityTextStacksFields() {
|
||||
assertEquals(1, bodyMetricColumnCount(312f, 1.5f))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deployedPhoneWidthRendersPairedFieldsWithoutClippingUnits() {
|
||||
compose.setContent {
|
||||
TrainlogTheme {
|
||||
Column(Modifier.width(312.dp)) { BodyMetricGrid(items) }
|
||||
}
|
||||
}
|
||||
val weight = compose.onNodeWithText("POIDS").fetchSemanticsNode().boundsInRoot
|
||||
val neck = compose.onNodeWithText("COU").fetchSemanticsNode().boundsInRoot
|
||||
assertTrue(neck.left > weight.left)
|
||||
assertTrue(kotlin.math.abs(neck.top - weight.top) < 2f)
|
||||
compose.onNodeWithText("kg").fetchSemanticsNode()
|
||||
assertTrue(compose.onAllNodesWithText("cm", useUnmergedTree = true).fetchSemanticsNodes().isNotEmpty())
|
||||
compose.onNodeWithText("TOUR DE TAILLE").fetchSemanticsNode()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun trulyNarrowLayoutStacksPairedFields() {
|
||||
compose.setContent {
|
||||
TrainlogTheme {
|
||||
Column(Modifier.width(280.dp)) { BodyMetricGrid(items.take(2)) }
|
||||
}
|
||||
}
|
||||
val weight = compose.onNodeWithText("POIDS").fetchSemanticsNode().boundsInRoot
|
||||
val neck = compose.onNodeWithText("COU").fetchSemanticsNode().boundsInRoot
|
||||
assertTrue(neck.top > weight.top)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue