From 6f25f426ba7a64f7be0f5a3fa08110423db6c3ac Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Mon, 28 Sep 2026 07:17:43 +0000 Subject: [PATCH] fix the overlapping options in the concurrency dialog ChoiceGroup emitted its header and option rows as siblings straight into the AlertDialog text slot, and they drew on top of each other, leaving only one radio visible with garbled labels. Wrap the group in its own Column so the rows stack, and cover it with a test that asserts the presets occupy separate rows. --- .../subtracks/ui/settings/SettingsScreen.kt | 20 ++++--- .../settings/SettingsConcurrencyDialogTest.kt | 56 +++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 app/src/test/kotlin/com/subtracks/ui/settings/SettingsConcurrencyDialogTest.kt diff --git a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt index de817361..4f23e226 100644 --- a/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/subtracks/ui/settings/SettingsScreen.kt @@ -323,15 +323,17 @@ private fun ChoiceGroup( selected: T, onSelect: (T) -> Unit, ) { - Text(header, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary) - options.forEach { (value, label) -> - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth().clickable { onSelect(value) }, - ) { - RadioButton(selected = value == selected, onClick = { onSelect(value) }) - Spacer(Modifier.width(8.dp)) - Text(label) + Column { + Text(header, style = MaterialTheme.typography.titleSmall, color = MaterialTheme.colorScheme.primary) + options.forEach { (value, label) -> + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().clickable { onSelect(value) }, + ) { + RadioButton(selected = value == selected, onClick = { onSelect(value) }) + Spacer(Modifier.width(8.dp)) + Text(label) + } } } } diff --git a/app/src/test/kotlin/com/subtracks/ui/settings/SettingsConcurrencyDialogTest.kt b/app/src/test/kotlin/com/subtracks/ui/settings/SettingsConcurrencyDialogTest.kt new file mode 100644 index 00000000..505ab15e --- /dev/null +++ b/app/src/test/kotlin/com/subtracks/ui/settings/SettingsConcurrencyDialogTest.kt @@ -0,0 +1,56 @@ +package com.subtracks.ui.settings + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.github.takahirom.roborazzi.RobolectricDeviceQualifiers +import com.subtracks.data.prefs.StreamQuality +import com.subtracks.ui.theme.SubtracksTheme +import org.junit.Assert.assertTrue +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.annotation.Config +import org.robolectric.annotation.GraphicsMode + +@RunWith(AndroidJUnit4::class) +@GraphicsMode(GraphicsMode.Mode.NATIVE) +@Config(qualifiers = RobolectricDeviceQualifiers.PixelXL) +class SettingsConcurrencyDialogTest { + @get:Rule + val composeRule = createAndroidComposeRule() + + @Test + fun theConcurrencyDialogListsEachPresetOnItsOwnRow() { + composeRule.setContent { + SubtracksTheme { + SettingsScreen( + sources = emptyList(), + activeSourceId = null, + wifiQuality = StreamQuality(), + mobileQuality = StreamQuality(), + syncConcurrency = 5, + onSelectSource = {}, + onDeleteSource = {}, + onWifiQualityChange = {}, + onMobileQualityChange = {}, + onSyncConcurrencyChange = {}, + onAddServer = {}, + onBack = {}, + ) + } + } + + composeRule.onNodeWithText("Sync concurrency").performClick() + + val rows = + listOf("1 (sequential)", "2", "4", "8", "16").map { + composeRule.onNodeWithText(it).fetchSemanticsNode().boundsInRoot + } + rows.zipWithNext().forEach { (above, below) -> + assertTrue("concurrency options overlap: $above vs $below", below.top >= above.bottom) + } + } +}