Skip to content

Commit e320dee

Browse files
fix: replace IntrinsicSize.Min with custom UniformHeightRow to avoid SubcomposeLayout crash on tablets
1 parent 007a67e commit e320dee

5 files changed

Lines changed: 99 additions & 30 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package me.bmax.apatch.ui.component
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.layout.Layout
5+
import androidx.compose.ui.Modifier
6+
import androidx.compose.ui.layout.Measurable
7+
import androidx.compose.ui.layout.MeasurePolicy
8+
import androidx.compose.ui.layout.MeasureResult
9+
import androidx.compose.ui.layout.MeasureScope
10+
import androidx.compose.ui.unit.Constraints
11+
import androidx.compose.ui.unit.Dp
12+
import androidx.compose.ui.unit.dp
13+
14+
/**
15+
* A row-like layout that distributes children equally in width and ensures
16+
* all children have the same height (matching the tallest child).
17+
*
18+
* Unlike [Row][androidx.compose.foundation.layout.Row] with
19+
* [IntrinsicSize][androidx.compose.foundation.layout.IntrinsicSize],
20+
* this does not trigger intrinsic measurement, so it is safe to use with
21+
* SubcomposeLayout-based children.
22+
*/
23+
@Composable
24+
fun UniformHeightRow(
25+
modifier: Modifier = Modifier,
26+
spacing: Dp = 0.dp,
27+
content: @Composable () -> Unit
28+
) {
29+
Layout(
30+
content = content,
31+
modifier = modifier,
32+
measurePolicy = UniformHeightRowMeasurePolicy(spacing)
33+
)
34+
}
35+
36+
private class UniformHeightRowMeasurePolicy(
37+
private val spacing: Dp
38+
) : MeasurePolicy {
39+
40+
override fun MeasureScope.measure(
41+
measurables: List<Measurable>,
42+
constraints: Constraints
43+
): MeasureResult {
44+
if (measurables.isEmpty()) {
45+
return layout(constraints.minWidth, constraints.minHeight) {}
46+
}
47+
48+
val count = measurables.size
49+
val spacingPx = spacing.roundToPx()
50+
val totalSpacing = spacingPx * (count - 1).coerceAtLeast(0)
51+
val availableForChildren = (constraints.maxWidth - totalSpacing).coerceAtLeast(0)
52+
val childWidth = availableForChildren / count
53+
54+
// Use intrinsic height instead of double measurement to avoid
55+
// "measure() may not be called multiple times" crash with SubcomposeLayout children
56+
val maxChildHeight = measurables.maxOf {
57+
it.minIntrinsicHeight(childWidth)
58+
}.coerceAtLeast(0)
59+
60+
// Single measurement pass with uniform height
61+
val placeables = measurables.map { measurable ->
62+
measurable.measure(
63+
Constraints(
64+
minWidth = childWidth,
65+
maxWidth = childWidth,
66+
minHeight = maxChildHeight,
67+
maxHeight = maxChildHeight
68+
)
69+
)
70+
}
71+
72+
return layout(constraints.maxWidth, maxChildHeight) {
73+
placeables.forEachIndexed { index, placeable ->
74+
val x = index * (childWidth + spacingPx)
75+
placeable.placeRelative(x, 0)
76+
}
77+
}
78+
}
79+
}

app/src/main/java/me/bmax/apatch/ui/screen/APM.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import androidx.compose.foundation.layout.Spacer
3232
import androidx.compose.foundation.layout.fillMaxSize
3333
import androidx.compose.foundation.layout.fillMaxWidth
3434
import androidx.compose.foundation.layout.height
35-
import androidx.compose.foundation.layout.IntrinsicSize
35+
import me.bmax.apatch.ui.component.UniformHeightRow
3636
import androidx.compose.foundation.layout.padding
3737
import androidx.compose.foundation.layout.size
3838
import androidx.compose.foundation.layout.width
@@ -827,12 +827,12 @@ private fun ModuleList(
827827
else -> {
828828
if (isWideScreen) {
829829
items(chunkedModules!!, key = { chunk -> chunk.joinToString("|") { it.id } }) { chunk ->
830-
Row(
831-
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
832-
horizontalArrangement = Arrangement.spacedBy(16.dp)
830+
UniformHeightRow(
831+
modifier = Modifier.fillMaxWidth(),
832+
spacing = 16.dp
833833
) {
834834
chunk.forEach { module ->
835-
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
835+
Column(modifier = Modifier.fillMaxHeight()) {
836836
var isChecked by rememberSaveable(module) { mutableStateOf(module.enabled) }
837837
val scope = rememberCoroutineScope()
838838
val updatedModule = viewModel.getCachedUpdate(module.id)
@@ -897,9 +897,6 @@ private fun ModuleList(
897897
})
898898
}
899899
}
900-
if (chunk.size == 1) {
901-
Spacer(modifier = Modifier.weight(1f))
902-
}
903900
}
904901
}
905902
} else {

app/src/main/java/me/bmax/apatch/ui/screen/KPM.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import androidx.compose.foundation.layout.fillMaxHeight
2626
import androidx.compose.foundation.layout.fillMaxSize
2727
import androidx.compose.foundation.layout.fillMaxWidth
2828
import androidx.compose.foundation.layout.height
29-
import androidx.compose.foundation.layout.IntrinsicSize
29+
import me.bmax.apatch.ui.component.UniformHeightRow
3030
import androidx.compose.foundation.layout.padding
3131
import androidx.compose.foundation.shape.CircleShape
3232
import androidx.compose.foundation.layout.size
@@ -747,12 +747,12 @@ private fun KPModuleList(
747747
else -> {
748748
if (isWideScreen) {
749749
items(chunkedModules!!, key = { chunk -> chunk.joinToString("|") { it.name } }) { chunk ->
750-
Row(
751-
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
752-
horizontalArrangement = Arrangement.spacedBy(16.dp)
750+
UniformHeightRow(
751+
modifier = Modifier.fillMaxWidth(),
752+
spacing = 16.dp
753753
) {
754754
chunk.forEach { module ->
755-
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
755+
Column(modifier = Modifier.fillMaxHeight()) {
756756
val scope = rememberCoroutineScope()
757757
KPModuleItem(
758758
module,
@@ -777,9 +777,6 @@ private fun KPModuleList(
777777
)
778778
}
779779
}
780-
if (chunk.size == 1) {
781-
Spacer(modifier = Modifier.weight(1f))
782-
}
783780
}
784781
}
785782
} else {

app/src/main/java/me/bmax/apatch/ui/screen/OnlineKPMScreen.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package me.bmax.apatch.ui.screen
33
import android.content.Context
44
import android.widget.Toast
55
import androidx.compose.foundation.clickable
6+
import me.bmax.apatch.ui.component.UniformHeightRow
67
import androidx.compose.foundation.layout.*
78
import androidx.compose.foundation.lazy.LazyColumn
89
import androidx.compose.foundation.lazy.items
@@ -133,18 +134,15 @@ fun OnlineKPMScreen(navigator: DestinationsNavigator) {
133134
verticalArrangement = Arrangement.spacedBy(8.dp)
134135
) {
135136
items(chunkedModules, key = { chunk -> chunk.joinToString("|") { it.name } }) { chunk ->
136-
Row(
137-
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
138-
horizontalArrangement = Arrangement.spacedBy(8.dp)
137+
UniformHeightRow(
138+
modifier = Modifier.fillMaxWidth(),
139+
spacing = 8.dp
139140
) {
140141
chunk.forEach { module ->
141-
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
142+
Column(modifier = Modifier.fillMaxHeight()) {
142143
OnlineKPMItem(module, context)
143144
}
144145
}
145-
if (chunk.size == 1) {
146-
Spacer(modifier = Modifier.weight(1f))
147-
}
148146
}
149147
}
150148
}

app/src/main/java/me/bmax/apatch/ui/screen/OnlineModuleScreen.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package me.bmax.apatch.ui.screen
22

33
import android.content.Context
44
import android.widget.Toast
5+
import me.bmax.apatch.ui.component.UniformHeightRow
56
import androidx.compose.foundation.layout.*
67
import androidx.compose.foundation.lazy.LazyColumn
78
import androidx.compose.foundation.lazy.items
@@ -134,18 +135,15 @@ fun OnlineModuleScreen(navigator: DestinationsNavigator) {
134135
verticalArrangement = Arrangement.spacedBy(8.dp)
135136
) {
136137
items(chunkedModules, key = { chunk -> chunk.joinToString("|") { it.name } }) { chunk ->
137-
Row(
138-
modifier = Modifier.fillMaxWidth().height(IntrinsicSize.Min),
139-
horizontalArrangement = Arrangement.spacedBy(8.dp)
138+
UniformHeightRow(
139+
modifier = Modifier.fillMaxWidth(),
140+
spacing = 8.dp
140141
) {
141142
chunk.forEach { module ->
142-
Column(modifier = Modifier.weight(1f).fillMaxHeight()) {
143+
Column(modifier = Modifier.fillMaxHeight()) {
143144
OnlineModuleItem(module, context)
144145
}
145146
}
146-
if (chunk.size == 1) {
147-
Spacer(modifier = Modifier.weight(1f))
148-
}
149147
}
150148
}
151149
}

0 commit comments

Comments
 (0)