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 <noreply@anthropic.com>
This commit is contained in:
Kgothatso Ngako
2026-09-05 20:38:03 +02:00
parent 6c63027912
commit 2d0fe6f5fc

View File

@@ -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,