mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
fix(concord): show the 64-byte name cap instead of silently dropping the edit
Community, channel and access-role names are capped at 64 UTF-8 bytes (CORD-02 §6). The actions refused a longer name, but the forms took any length, so a long rename or channel create simply did nothing. The name fields now show a "used / 64 bytes" counter that turns red past the cap, and Create / Save / Rename stay disabled until the name fits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1a821544f5
commit
88149f66f3
@@ -591,6 +591,7 @@
|
||||
<string name="concord_invite_revoke_explainer">Anyone still holding this link will no longer be able to join. People who already joined with it stay in the community. This can't be undone.</string>
|
||||
<string name="concord_invite_revoke_confirm">Revoke</string>
|
||||
<string name="concord_invite_revoking">Revoking…</string>
|
||||
<string name="concord_name_budget">%1$d / %2$d bytes</string>
|
||||
<string name="concord_invite_revoke_privatize_warning">This is the community's last live invite link. Revoking it makes the community Private and rotates its keys, so only current members keep access.</string>
|
||||
<string name="concord_invite_revoked_privatized">Link revoked. The community is now Private and its keys were rotated.</string>
|
||||
<string name="concord_invite_revoked_privatize_pending">Link revoked. The community is now Private, but its keys were not rotated: an admin who can ban members needs to rotate them.</string>
|
||||
|
||||
+7
-2
@@ -851,6 +851,8 @@ private fun ConcordChannelEditDialog(
|
||||
onValueChange = { name = it },
|
||||
singleLine = true,
|
||||
label = { Text(stringRes(Res.string.concord_channel_name_label)) },
|
||||
isError = !concordNameFits(name),
|
||||
supportingText = { ConcordNameBudget(name) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
// A new channel may be Private (CORD-03): its own key, and an access Role — the
|
||||
@@ -872,6 +874,8 @@ private fun ConcordChannelEditDialog(
|
||||
singleLine = true,
|
||||
placeholder = { Text(name.trim()) },
|
||||
label = { Text(stringRes(Res.string.concord_channel_access_role_label)) },
|
||||
isError = !concordNameFits(roleName),
|
||||
supportingText = { if (roleName.isNotBlank()) ConcordNameBudget(roleName) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
@@ -880,8 +884,9 @@ private fun ConcordChannelEditDialog(
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
enabled = name.isNotBlank(),
|
||||
onClick = { if (name.isNotBlank()) onConfirm(name.trim(), makePrivate, roleName.trim().ifBlank { null }) },
|
||||
// Past the 64-byte cap every reader drops the edition, so the action would silently no-op.
|
||||
enabled = name.isNotBlank() && concordNameFits(name) && concordNameFits(roleName),
|
||||
onClick = { if (name.isNotBlank() && concordNameFits(name) && concordNameFits(roleName)) onConfirm(name.trim(), makePrivate, roleName.trim().ifBlank { null }) },
|
||||
) {
|
||||
Text(
|
||||
stringRes(
|
||||
|
||||
+1
-1
@@ -147,7 +147,7 @@ fun ConcordCreateScreen(
|
||||
}
|
||||
}
|
||||
},
|
||||
enabled = name.value.isNotBlank() && !working,
|
||||
enabled = name.value.isNotBlank() && concordNameFits(name.value) && !working,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
) {
|
||||
Text(stringRes(Res.string.concord_create_action))
|
||||
|
||||
+1
-1
@@ -202,7 +202,7 @@ fun ConcordEditScreen(
|
||||
if (ok) nav.popBack()
|
||||
}
|
||||
},
|
||||
enabled = name.value.isNotBlank() && !working,
|
||||
enabled = name.value.isNotBlank() && concordNameFits(name.value) && !working,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
|
||||
) {
|
||||
Text(stringRes(Res.string.concord_edit_save))
|
||||
|
||||
+19
@@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.resources.concord_create_icon_hint
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_create_name
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_create_title
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_edit_banner_hint
|
||||
import com.vitorpamplona.amethyst.commons.resources.concord_name_budget
|
||||
import com.vitorpamplona.amethyst.commons.resources.failed_to_upload_media_no_details
|
||||
import com.vitorpamplona.amethyst.commons.resources.remove
|
||||
import com.vitorpamplona.amethyst.commons.ui.actions.uploads.GallerySelectSingle
|
||||
@@ -85,6 +86,7 @@ import com.vitorpamplona.amethyst.commons.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.commons.ui.theme.MediumRelayIconModifier
|
||||
import com.vitorpamplona.amethyst.commons.viewmodels.AccountViewModel
|
||||
import com.vitorpamplona.quartz.concord.cord02Community.ImagePointer
|
||||
import com.vitorpamplona.quartz.concord.cord04Roles.ConcordLimits
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.displayUrl
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
@@ -130,6 +132,8 @@ fun ConcordMetadataFields(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
singleLine = true,
|
||||
label = { Text(stringRes(Res.string.concord_create_name)) },
|
||||
isError = !concordNameFits(name.value),
|
||||
supportingText = { ConcordNameBudget(name.value) },
|
||||
)
|
||||
OutlinedTextField(
|
||||
value = about.value,
|
||||
@@ -142,6 +146,21 @@ fun ConcordMetadataFields(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether [name] fits the protocol-wide name cap (CORD-02 §6: 64 UTF-8 bytes, for the Community,
|
||||
* Channels and Roles). Every reader drops an edition past it, so a form must not submit one.
|
||||
*/
|
||||
fun concordNameFits(name: String): Boolean = ConcordLimits.nameFits(name.trim())
|
||||
|
||||
/** A text field's `used / 64` byte counter for a Concord name, in the error color once over the cap. */
|
||||
@Composable
|
||||
fun ConcordNameBudget(name: String) {
|
||||
Text(
|
||||
stringRes(Res.string.concord_name_budget, ConcordLimits.utf8Size(name.trim()), ConcordLimits.NAME_MAX_BYTES),
|
||||
color = if (concordNameFits(name)) MaterialTheme.colorScheme.onSurfaceVariant else MaterialTheme.colorScheme.error,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The circular community-icon hero: shows the current (decrypted) icon over a stable robohash
|
||||
* placeholder, and on tap opens the photo picker → encrypts + uploads the chosen image and updates
|
||||
|
||||
Reference in New Issue
Block a user