From 2d0fe6f5fc529aa473b756fd7205e695948c2537 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sat, 5 Sep 2026 20:38:03 +0200 Subject: [PATCH] fix: disable Add Artifact until a dialect is picked Submitting without a dialect was rejected in the view model, which called onFailure, which navigated to ImplementationPendingRoute("Failed Artifact") -- a whole screen away from the form, saying nothing about which field was wrong, and leaving the way back to the only sensible fix as the back button. That is a bad way to report any missing field, but the dialect is the one where it is unrecoverable in place. A blank name or url is answered by typing; a dialect has to already exist, and since dialects moved to the group screen there is nothing on this form that can conjure one. So an unpicked dialect is not a mistake to report after the fact, it is a state the button should not be pressable in. Material 3 gives ExtendedFloatingActionButton no `enabled` parameter, so this paints the disabled colours from ButtonDefaults.buttonColors() -- the same ones every other disabled button in the app resolves from the theme, rather than an alpha invented here -- and returns early from onClick. Also marks it disabled to accessibility services. Colours alone leave a screen reader announcing a button it is happy to press, and pressing it does nothing, which is worse than a button that says it is unavailable. A group with no dialects at all is covered by the same condition, since there is then nothing to select. Co-Authored-By: Claude Opus 5 --- .../ui/composable/AddArtifactScreen.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt index 34e7932f..5d451f8d 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/AddArtifactScreen.kt @@ -20,10 +20,12 @@ import androidx.compose.material.icons.filled.LocalOffer import androidx.compose.material.icons.filled.MoreVert import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBarDefaults +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FilterChip +import androidx.compose.material3.FloatingActionButtonDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme @@ -33,6 +35,7 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar +import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -42,6 +45,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.disabled +import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -110,6 +115,16 @@ fun AddArtifactScreen( // one is picked; dialects are defined from the group detail screen. var selectedDialectId: String? by remember { mutableStateOf(null) } + // Of the required fields this is the only one the screen cannot ask + // for again: a dialect has to already exist, and nothing here can + // create one. So an unpicked dialect is a dead end rather than + // something to submit and be told about, and the button says so. + val canAddArtifact = selectedDialectId != null + + // M3 gives a FAB no `enabled`, so borrow the disabled colours every + // other button in the app uses rather than inventing a shade here. + val buttonColors = ButtonDefaults.buttonColors() + Scaffold( topBar = { TopAppBar( @@ -126,7 +141,27 @@ fun AddArtifactScreen( actions = {}, floatingActionButton = { ExtendedFloatingActionButton( + modifier = if (canAddArtifact) { + Modifier + } else { + // Looking unavailable is not being unavailable: + // without this a screen reader still announces + // a button it is happy to press. + Modifier.semantics { disabled() } + }, + containerColor = if (canAddArtifact) { + FloatingActionButtonDefaults.containerColor + } else { + buttonColors.disabledContainerColor + }, + contentColor = if (canAddArtifact) { + contentColorFor(FloatingActionButtonDefaults.containerColor) + } else { + buttonColors.disabledContentColor + }, onClick = { + if (!canAddArtifact) return@ExtendedFloatingActionButton + addArtifactViewModel.addArtifact( localChatRoom = addArtifactUIState.localChatRoom, nameField = nameFieldState,