refactor: pick an artifact's dialect, do not invent one while adding it

Adding an artifact offered a "New dialect" chip that swapped in three
more fields -- name, country, language -- and minted a dialect on the
way to creating the artifact. Now that a group defines its dialects on
its own screen, that path is a second, worse way to do the same thing:
it creates a dialect as a side effect of an unrelated action, in a form
where the fields belong to neither entity clearly, and with no sight of
what the group has already defined beyond a row of chips.

Drop it. The chip row is now exactly the dialects that exist, and
selectedDialectId changes meaning from "null = create a new one" to
"null = nothing picked yet" -- which the FAB rejects alongside the other
required fields, rather than falling through to creating something.

A group with no dialects yet gets a line saying so and pointing at the
group screen, instead of a lone chip that opens a form.

addArtifact loses the three TextFieldStates and existingDialectId for a
single dialectId, and with them the branch that called addDialect and
threaded its id back in. Validation is now one condition rather than one
per path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kgothatso NgakoandClaude Opus 5 committed 2026-09-05 20:20:35 +02:00
1 parent 6ff9fd2d38
commit e14be2d187
2 files changed
+24 -147

No files matched your search

@@ -18,9 +18,6 @@ import androidx.compose.material.icons.filled.Label
import androidx.compose.material.icons.filled.Link
import androidx.compose.material.icons.filled.LocalOffer
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.Public
import androidx.compose.material.icons.filled.Title
import androidx.compose.material.icons.filled.Translate
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.BottomAppBarDefaults
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -108,12 +105,9 @@ fun AddArtifactScreen(
val nameFieldState = rememberTextFieldState()
val urlFieldState = rememberTextFieldState()
val versionLabelFieldState = rememberTextFieldState("1.0")
val dialectNameFieldState = rememberTextFieldState()
val dialectCountryFieldState = rememberTextFieldState()
val dialectLanguageFieldState = rememberTextFieldState()
// null = "New dialect" (show the create fields); otherwise the id of
// an existing dialect to reuse.
// The id of the group dialect this artifact is written in. Null until
// one is picked; dialects are defined from the group detail screen.
var selectedDialectId: String? by remember { mutableStateOf(null) }
Scaffold(
@@ -138,10 +132,7 @@ fun AddArtifactScreen(
nameField = nameFieldState,
urlField = urlFieldState,
versionLabelField = versionLabelFieldState,
existingDialectId = selectedDialectId,
dialectNameField = dialectNameFieldState,
dialectCountryField = dialectCountryFieldState,
dialectLanguageField = dialectLanguageFieldState,
dialectId = selectedDialectId,
onSuccess = { artifactId ->
// Open the newly created artifact, removing this
// add screen from the back stack.
@@ -266,112 +257,24 @@ fun AddArtifactScreen(
Text("Source dialect")
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
addArtifactUIState.dialects.forEach { dialect ->
FilterChip(
selected = selectedDialectId == dialect.id,
onClick = { selectedDialectId = dialect.id },
label = { Text(dialect.name) }
)
if (addArtifactUIState.dialects.isEmpty()) {
Text(
text = "No dialects have been defined in this group yet. Add one from the group's detail screen first.",
style = MaterialTheme.typography.bodySmall
)
} else {
FlowRow(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
addArtifactUIState.dialects.forEach { dialect ->
FilterChip(
selected = selectedDialectId == dialect.id,
onClick = { selectedDialectId = dialect.id },
label = { Text(dialect.name) }
)
}
}
FilterChip(
selected = selectedDialectId == null,
onClick = { selectedDialectId = null },
leadingIcon = {
Icon(
Icons.Default.Add,
contentDescription = "Create a new dialect"
)
},
label = { Text("New dialect") }
)
}
// Only collect new-dialect details when not reusing an existing one.
if (selectedDialectId == null) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth()
.background(BottomAppBarDefaults.containerColor),
state = dialectNameFieldState,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent
),
leadingIcon = {
Icon(
Icons.Default.Title,
contentDescription = "Name of the dialect"
)
},
label = {
Text(
text = "Dialect Name"
)
},
placeholder = {
Text(
text = "eg. Sesotho"
)
},
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth()
.background(BottomAppBarDefaults.containerColor),
state = dialectCountryFieldState,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent
),
leadingIcon = {
Icon(
Icons.Default.Public,
contentDescription = "Country of the dialect"
)
},
label = {
Text(
text = "Country"
)
},
placeholder = {
Text(
text = "eg. Lesotho"
)
},
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth()
.background(BottomAppBarDefaults.containerColor),
state = dialectLanguageFieldState,
colors = OutlinedTextFieldDefaults.colors(
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
disabledBorderColor = Color.Transparent
),
leadingIcon = {
Icon(
Icons.Default.Translate,
contentDescription = "Language of the dialect"
)
},
label = {
Text(
text = "Language"
)
},
placeholder = {
Text(
text = "eg. st"
)
},
)
}
// TODO: Add Visibility
@@ -62,10 +62,7 @@ class AddArtifactViewModel(
nameField: TextFieldState,
urlField: TextFieldState,
versionLabelField: TextFieldState,
existingDialectId: HexKey?,
dialectNameField: TextFieldState,
dialectCountryField: TextFieldState,
dialectLanguageField: TextFieldState,
dialectId: HexKey?,
visibility: String = MantraRepository.DEFAULT_VISIBILITY,
license: String = MantraRepository.DEFAULT_LICENSE,
onSuccess: (artifactId: String) -> Unit,
@@ -75,16 +72,10 @@ class AddArtifactViewModel(
val name = nameField.text.toString()
val url = urlField.text.toString()
val versionLabel = versionLabelField.text.toString()
val dialectName = dialectNameField.text.toString()
val dialectCountry = dialectCountryField.text.toString()
val dialectLanguage = dialectLanguageField.text.toString()
// When no existing dialect is selected, the new-dialect fields are required.
val creatingNewDialect = existingDialectId.isNullOrBlank()
val newDialectIncomplete = dialectName.isBlank() || dialectCountry.isBlank() || dialectLanguage.isBlank()
if (name.isBlank() || url.isBlank() || versionLabel.isBlank() ||
(creatingNewDialect && newDialectIncomplete)
) {
// addArtifact requires an existing source dialect; they are defined
// from the group detail screen, not here.
if (name.isBlank() || url.isBlank() || versionLabel.isBlank() || dialectId.isNullOrBlank()) {
onFailure.invoke()
return
}
@@ -95,20 +86,6 @@ class AddArtifactViewModel(
viewModelScope.launch(Dispatchers.IO) {
val artifactInnerEvent = runCatching {
// Reuse the selected dialect, or create a new source dialect and
// reference it by id. addArtifact requires a valid dialectId.
val dialectId = if (creatingNewDialect) {
mantraRepository.addDialect(
localChatRoom = localChatRoom,
name = dialectName,
country = dialectCountry,
language = dialectLanguage,
userPublicKey = activeUserPublicKey,
)?.id ?: return@runCatching null
} else {
existingDialectId
}
mantraRepository.addArtifact(
localChatRoom = localChatRoom,
name = name,
@@ -127,9 +104,6 @@ class AddArtifactViewModel(
nameField.clearText()
urlField.clearText()
versionLabelField.clearText()
dialectNameField.clearText()
dialectCountryField.clearText()
dialectLanguageField.clearText()
viewModelScope.launch(Dispatchers.Main) {
onSuccess.invoke(artifactInnerEvent.id)