From d46d898f4d7266d9843f4c52fb8395a30a5a8d5e Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 24 Sep 2026 04:02:20 +0000 Subject: [PATCH] style(cordn): group the five form screens into SettingsSections The hub was converted earlier; these five kept the shared geometry but were still flat columns divided by horizontal rules. A rule says "something else follows" without saying what, which on two of these screens is the whole problem: - Migrate is two devices' jobs and only one of them is yours today. Doing the wrong one hands your account to another phone. "Move off this phone" and "Take over from another phone" are now boxes with names rather than halves of a page. - Coordinators reads as three answers: what you use, what is on offer, and the manual escape hatch. Pasting a 64-character key belongs last, as the fallback it became once discovery went in above it. - Backup's passphrase governs both export and restore. It sat above two unrelated-looking blocks and never said so; it is its own section now. - The link screen separates asking from answering. - Key packages gets one section rather than one per coordinator, because SettingsSection titles come from a StringResource and a coordinator's label is runtime text. Its dividers move inside and are drawn between peers rather than after the last one, which used to rule off to nothing. SettingsFormBlock is the one new piece: the section card has no padding of its own, by design, because it is built for rows like SettingsItem that carry theirs so a divider can run edge to edge. Form content needs that inset back, and one helper keeps five screens identical to each other as they change. Two strings retired: the in-body "Add a coordinator" and "Coordinators announcing themselves" titles are section headers now. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n --- .../settings/cordn/CordnBackupScreen.kt | 106 ++++++++++-------- .../settings/cordn/CordnCoordinatorsScreen.kt | 52 +++++---- .../settings/cordn/CordnKeyPackagesScreen.kt | 34 ++++-- .../settings/cordn/CordnLinkScreen.kt | 89 ++++++++------- .../settings/cordn/CordnMigrateScreen.kt | 22 +++- .../settings/cordn/SettingsFormBlock.kt | 51 +++++++++ amethyst/src/main/res/values/strings.xml | 2 - .../composeResources/values/strings.xml | 10 ++ 8 files changed, 242 insertions(+), 124 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/SettingsFormBlock.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt index 9d57679de4..b3ce768968 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnBackupScreen.kt @@ -32,9 +32,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold @@ -56,11 +53,15 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_export +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_passphrase +import com.vitorpamplona.amethyst.commons.resources.cordn_backup_section_restore import com.vitorpamplona.amethyst.commons.resources.cordn_backup_title import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -191,57 +192,66 @@ fun CordnBackupScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, ) - OutlinedTextField( - value = passphrase, - onValueChange = { - passphrase = it - error = null - }, - label = { Text(stringRes(R.string.cordn_backup_passphrase)) }, - visualTransformation = PasswordVisualTransformation(), - singleLine = true, - modifier = Modifier.fillMaxWidth(), - ) - - Card( - modifier = Modifier.fillMaxWidth(), - colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), - ) { - Column(Modifier.padding(12.dp)) { - Text(stringRes(R.string.cordn_backup_contents_title), style = MaterialTheme.typography.titleSmall) - Text(stringRes(R.string.cordn_backup_contents_body), style = MaterialTheme.typography.bodySmall) + // The passphrase is its own section because it governs both of the + // two below: the same word exports and restores, and a field + // floating above two unrelated-looking cards did not say so. + SettingsSection(Res.string.cordn_backup_section_passphrase) { + SettingsFormBlock { + OutlinedTextField( + value = passphrase, + onValueChange = { + passphrase = it + error = null + }, + label = { Text(stringRes(R.string.cordn_backup_passphrase)) }, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + modifier = Modifier.fillMaxWidth(), + ) } } - Button( - onClick = { saver.launch("cordn-backup.bin") }, - // No passphrase, no export. The file carries ratchet trees and - // private key material; there is no version of it that is safe - // to write unprotected. - enabled = !busy && passphrase.isNotBlank(), - modifier = Modifier.fillMaxWidth(), - ) { - // Key derivation here is scrypt, which is slow on purpose, so - // a greyed-out button was the only sign anything was happening - // for several seconds. - BusyLabel(busy, stringRes(R.string.cordn_backup_export)) + SettingsSection(Res.string.cordn_backup_section_export) { + SettingsFormBlock { + Text(stringRes(R.string.cordn_backup_contents_title), style = MaterialTheme.typography.titleSmall) + Text( + text = stringRes(R.string.cordn_backup_contents_body), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + Button( + onClick = { saver.launch("cordn-backup.bin") }, + // No passphrase, no export. The file carries ratchet + // trees and private key material; there is no version + // of it that is safe to write unprotected. + enabled = !busy && passphrase.isNotBlank(), + modifier = Modifier.fillMaxWidth(), + ) { + // Key derivation here is scrypt, which is slow on + // purpose, so a greyed-out button was the only sign + // anything was happening for several seconds. + BusyLabel(busy, stringRes(R.string.cordn_backup_export)) + } + } } - HorizontalDivider(Modifier.padding(vertical = 8.dp)) + SettingsSection(Res.string.cordn_backup_section_restore) { + SettingsFormBlock { + Text( + text = stringRes(R.string.cordn_backup_restore_body), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) - Text(stringRes(R.string.cordn_backup_restore_title), style = MaterialTheme.typography.titleSmall) - Text( - text = stringRes(R.string.cordn_backup_restore_body), - style = MaterialTheme.typography.bodySmall, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - - Button( - onClick = { picker.launch("*/*") }, - enabled = !busy && passphrase.isNotBlank(), - modifier = Modifier.fillMaxWidth(), - ) { - BusyLabel(busy, stringRes(R.string.cordn_backup_restore)) + Button( + onClick = { picker.launch("*/*") }, + enabled = !busy && passphrase.isNotBlank(), + modifier = Modifier.fillMaxWidth(), + ) { + BusyLabel(busy, stringRes(R.string.cordn_backup_restore)) + } + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt index e58176fc4c..0c9852fe31 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnCoordinatorsScreen.kt @@ -36,7 +36,6 @@ import androidx.compose.material3.AlertDialog import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField @@ -60,6 +59,9 @@ import com.vitorpamplona.amethyst.commons.cordn.CordnCoordinatorDiscovery import com.vitorpamplona.amethyst.commons.cordn.DiscoveredCoordinator import com.vitorpamplona.amethyst.commons.resources.Res import com.vitorpamplona.amethyst.commons.resources.cancel +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_discover +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_manual +import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_section_yours import com.vitorpamplona.amethyst.commons.resources.cordn_coordinators_title import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -68,6 +70,7 @@ import com.vitorpamplona.amethyst.model.cordn.CordnRuntime import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.note.timeAgoNoDot import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.cordn.spec00Coordinator.CoordinatorServerInfo import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer @@ -136,25 +139,39 @@ fun CordnCoordinatorsScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, ) - coordinators.forEach { config -> - CoordinatorCard(config, runtime) + // Three sections rather than one column split by rules: what you + // already use, what is on offer, and the manual escape hatch. The + // dividers said "something else follows" without saying what. + SettingsSection(Res.string.cordn_coordinators_section_yours) { + SettingsFormBlock { + coordinators.forEach { config -> + CoordinatorCard(config, runtime) + } + + if (coordinators.isEmpty()) { + Text( + text = stringRes(R.string.cordn_coordinators_none), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } } - if (coordinators.isEmpty()) { - Text( - text = stringRes(R.string.cordn_coordinators_none), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) + SettingsSection(Res.string.cordn_coordinators_section_discover) { + SettingsFormBlock { + DiscoverCoordinators(runtime, accountViewModel, coordinators.map { it.pubKey }.toSet()) + } } - HorizontalDivider(Modifier.padding(vertical = 8.dp)) - - DiscoverCoordinators(runtime, accountViewModel, coordinators.map { it.pubKey }.toSet()) - - HorizontalDivider(Modifier.padding(vertical = 8.dp)) - - AddCoordinator(runtime) + // Last, and it reads as the fallback it is now that discovery is + // above it: pasting a 64-character key is what you do when nobody + // announced the one you were told to use. + SettingsSection(Res.string.cordn_coordinators_section_manual) { + SettingsFormBlock { + AddCoordinator(runtime) + } + } } } } @@ -308,7 +325,6 @@ private fun DiscoverCoordinators( var error by remember { mutableStateOf(null) } val failed = stringRes(R.string.cordn_coordinators_discover_failed) - Text(stringRes(R.string.cordn_coordinators_discover), style = MaterialTheme.typography.titleSmall) Text( text = stringRes(R.string.cordn_coordinators_discover_explainer), style = MaterialTheme.typography.bodySmall, @@ -446,8 +462,6 @@ private fun AddCoordinator(runtime: CordnRuntime) { } } - Text(stringRes(R.string.cordn_coordinators_add), style = MaterialTheme.typography.titleSmall) - OutlinedTextField( value = pubKeyInput, onValueChange = { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt index 9658791bcf..046b96885d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnKeyPackagesScreen.kt @@ -53,6 +53,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.cordn.CoordinatorConfig import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_section import com.vitorpamplona.amethyst.commons.resources.cordn_keypackages_title import com.vitorpamplona.amethyst.commons.ui.components.EmptyState import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav @@ -61,6 +62,7 @@ import com.vitorpamplona.amethyst.model.cordn.CordnKeyPackageRow import com.vitorpamplona.amethyst.model.cordn.CordnRuntime import com.vitorpamplona.amethyst.ui.pluralStringRes import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.coroutines.launch @@ -128,17 +130,29 @@ fun CordnKeyPackagesScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, ) - if (coordinators.isEmpty()) { - Text( - text = stringRes(R.string.cordn_coordinators_none), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - ) - } + // One section holding every coordinator rather than one each: + // SettingsSection titles come from a StringResource and a + // coordinator's label is runtime text, so a section per + // coordinator is not something this primitive can say. The + // dividers stay, inside, where they now separate peers within a + // box instead of floating in open page. + SettingsSection(Res.string.cordn_keypackages_section) { + SettingsFormBlock { + if (coordinators.isEmpty()) { + Text( + text = stringRes(R.string.cordn_coordinators_none), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } - coordinators.forEach { config -> - CoordinatorKeyPackages(config, runtime) - HorizontalDivider() + coordinators.forEachIndexed { index, config -> + // Between, not after: a rule under the last one drew a + // line to nothing. + if (index > 0) HorizontalDivider() + CoordinatorKeyPackages(config, runtime) + } + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt index 1d77752590..097b84adfb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnLinkScreen.kt @@ -60,11 +60,13 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_link_no_coordinator import com.vitorpamplona.amethyst.commons.resources.cordn_link_not_joinable import com.vitorpamplona.amethyst.commons.resources.cordn_link_paste import com.vitorpamplona.amethyst.commons.resources.cordn_link_relays +import com.vitorpamplona.amethyst.commons.resources.cordn_link_section_input import com.vitorpamplona.amethyst.commons.resources.cordn_link_title import com.vitorpamplona.amethyst.commons.ui.navigation.navs.INav import com.vitorpamplona.amethyst.commons.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner +import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.ui.stringRes import kotlinx.coroutines.launch import org.jetbrains.compose.resources.stringResource @@ -122,49 +124,56 @@ fun CordnLinkScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, ) - OutlinedTextField( - value = input, - onValueChange = { - input = it - // Clearing on edit rather than re-parsing per keystroke: a - // half-typed ref is always invalid, and showing that while - // someone is still pasting is noise, not feedback. - inspection = null - requestState = null - }, - label = { Text(stringResource(Res.string.cordn_link_field)) }, - singleLine = false, - modifier = Modifier.fillMaxWidth(), - ) - - Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { - Button( - onClick = { inspection = CordnLinkInspection.of(input) }, - enabled = input.isNotBlank(), - ) { - Text(stringResource(Res.string.cordn_link_inspect)) - } - OutlinedButton( - onClick = { - clipboard.getText()?.text?.let { + // The field, its buttons and the scanner are one thing to do; the + // verdict below is another. Boxing the input says where the screen + // asks something of you and where it answers. + SettingsSection(Res.string.cordn_link_section_input) { + SettingsFormBlock { + OutlinedTextField( + value = input, + onValueChange = { input = it - inspection = CordnLinkInspection.of(it) - } - }, - ) { - Text(stringResource(Res.string.cordn_link_paste)) - } - OutlinedButton(onClick = { scanning = true }) { - Text(stringRes(R.string.cordn_link_scan)) - } - if (input.isNotEmpty()) { - OutlinedButton( - onClick = { - input = "" + // Clearing on edit rather than re-parsing per keystroke: a + // half-typed ref is always invalid, and showing that while + // someone is still pasting is noise, not feedback. inspection = null + requestState = null }, - ) { - Text(stringResource(Res.string.cordn_link_clear)) + label = { Text(stringResource(Res.string.cordn_link_field)) }, + singleLine = false, + modifier = Modifier.fillMaxWidth(), + ) + + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button( + onClick = { inspection = CordnLinkInspection.of(input) }, + enabled = input.isNotBlank(), + ) { + Text(stringResource(Res.string.cordn_link_inspect)) + } + OutlinedButton( + onClick = { + clipboard.getText()?.text?.let { + input = it + inspection = CordnLinkInspection.of(it) + } + }, + ) { + Text(stringResource(Res.string.cordn_link_paste)) + } + OutlinedButton(onClick = { scanning = true }) { + Text(stringRes(R.string.cordn_link_scan)) + } + if (input.isNotEmpty()) { + OutlinedButton( + onClick = { + input = "" + inspection = null + }, + ) { + Text(stringResource(Res.string.cordn_link_clear)) + } + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt index dfe6af4c49..db9b3d4d4e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/CordnMigrateScreen.kt @@ -31,7 +31,6 @@ import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.Card -import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedTextField @@ -71,6 +70,8 @@ import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_receive import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_replaces_warning import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_scan import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_scan_this +import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_section_receive +import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_section_send import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_sign_in_first import com.vitorpamplona.amethyst.commons.resources.cordn_migrate_title import com.vitorpamplona.amethyst.commons.ui.components.EmptyState @@ -81,6 +82,7 @@ import com.vitorpamplona.amethyst.model.cordn.CordnRuntime import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.QrCodeDrawer import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.SimpleQrCodeScanner +import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsSection import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.quartz.cordn.appMultiDevice.CordnHandoffCode import kotlinx.coroutines.launch @@ -146,11 +148,21 @@ fun CordnMigrateScreen( color = MaterialTheme.colorScheme.onSurfaceVariant, ) - SendSide(runtime, accountViewModel) + // Two boxes, because they are two different devices' jobs and + // only one of them is yours today. A rule between them said they + // were separate without saying which was which — and doing the + // wrong one here hands your account to another phone. + SettingsSection(Res.string.cordn_migrate_section_send) { + SettingsFormBlock { + SendSide(runtime, accountViewModel) + } + } - HorizontalDivider(Modifier.padding(vertical = 8.dp)) - - ReceiveSide(runtime, accountViewModel) + SettingsSection(Res.string.cordn_migrate_section_receive) { + SettingsFormBlock { + ReceiveSide(runtime, accountViewModel) + } + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/SettingsFormBlock.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/SettingsFormBlock.kt new file mode 100644 index 0000000000..ca8825fec0 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/cordn/SettingsFormBlock.kt @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.cordn + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +/** + * Form content inside a `SettingsSection`. + * + * The section's card deliberately has no padding of its own: it is built for + * rows like `SettingsItem`, which carry theirs so a divider can run edge to + * edge between them. cordn's pages are forms rather than row lists — text + * fields, explainers and buttons — and those need the inset the rows would + * otherwise have supplied. + * + * One helper rather than a `Modifier.padding(16.dp)` repeated down five + * screens, so the pages stay identical to each other as they change. + */ +@Composable +fun SettingsFormBlock(content: @Composable ColumnScope.() -> Unit) { + Column( + modifier = Modifier.fillMaxWidth().padding(16.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + content = content, + ) +} diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index d826420159..b2ce11a2a5 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -367,7 +367,6 @@ Declining is permanent. Getting back in means being invited again. Yesterday Add a coordinator in Settings to start using cordn groups. - Coordinators announcing themselves Reads announcements your relays already carry. No coordinator is contacted, so none of them learns you looked. Look for coordinators Add @@ -402,7 +401,6 @@ A coordinator orders one group\'s messages and is the only thing that can. Losing it loses the ordering, and a second one does not mirror the first — so these are named things you choose, not interchangeable relays. No coordinators yet. Unnamed coordinator - Add a coordinator Add Could not open that coordinator. A name for it (yours, optional) diff --git a/commonsUI/src/commonMain/composeResources/values/strings.xml b/commonsUI/src/commonMain/composeResources/values/strings.xml index c32b527459..11895c0511 100644 --- a/commonsUI/src/commonMain/composeResources/values/strings.xml +++ b/commonsUI/src/commonMain/composeResources/values/strings.xml @@ -5401,6 +5401,16 @@ Groups delivered by a coordinator you choose, instead of by relays. Inspect a group link See what a cordn1… link points at before you act on it + Move off this phone + Take over from another phone + Published key packages + The link + Coordinators you use + Announcing themselves + Add one by hand + Passphrase + Make a backup + Restore from a backup The coordinator This device Coordinators