mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-06 03:38:23 +00:00
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012BfD4txdnsaPRXmNXbup9n
This commit is contained in:
+58
-48
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+33
-19
@@ -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<String?>(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 = {
|
||||
|
||||
+24
-10
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+49
-40
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+17
-5
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -367,7 +367,6 @@
|
||||
<string name="cordn_invitations_decline_warning">Declining is permanent. Getting back in means being invited again.</string>
|
||||
<string name="cordn_chat_yesterday">Yesterday</string>
|
||||
<string name="cordn_group_unavailable_detail">Add a coordinator in Settings to start using cordn groups.</string>
|
||||
<string name="cordn_coordinators_discover">Coordinators announcing themselves</string>
|
||||
<string name="cordn_coordinators_discover_explainer">Reads announcements your relays already carry. No coordinator is contacted, so none of them learns you looked.</string>
|
||||
<string name="cordn_coordinators_discover_action">Look for coordinators</string>
|
||||
<string name="cordn_coordinators_discover_add">Add</string>
|
||||
@@ -402,7 +401,6 @@
|
||||
<string name="cordn_coordinators_explainer">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.</string>
|
||||
<string name="cordn_coordinators_none">No coordinators yet.</string>
|
||||
<string name="cordn_coordinators_unnamed">Unnamed coordinator</string>
|
||||
<string name="cordn_coordinators_add">Add a coordinator</string>
|
||||
<string name="cordn_coordinators_add_action">Add</string>
|
||||
<string name="cordn_coordinators_add_failed">Could not open that coordinator.</string>
|
||||
<string name="cordn_coordinators_label">A name for it (yours, optional)</string>
|
||||
|
||||
@@ -5401,6 +5401,16 @@
|
||||
<string name="cordn_hub_explainer">Groups delivered by a coordinator you choose, instead of by relays.</string>
|
||||
<string name="cordn_hub_link">Inspect a group link</string>
|
||||
<string name="cordn_hub_link_desc">See what a cordn1… link points at before you act on it</string>
|
||||
<string name="cordn_migrate_section_send">Move off this phone</string>
|
||||
<string name="cordn_migrate_section_receive">Take over from another phone</string>
|
||||
<string name="cordn_keypackages_section">Published key packages</string>
|
||||
<string name="cordn_link_section_input">The link</string>
|
||||
<string name="cordn_coordinators_section_yours">Coordinators you use</string>
|
||||
<string name="cordn_coordinators_section_discover">Announcing themselves</string>
|
||||
<string name="cordn_coordinators_section_manual">Add one by hand</string>
|
||||
<string name="cordn_backup_section_passphrase">Passphrase</string>
|
||||
<string name="cordn_backup_section_export">Make a backup</string>
|
||||
<string name="cordn_backup_section_restore">Restore from a backup</string>
|
||||
<string name="cordn_hub_section_service">The coordinator</string>
|
||||
<string name="cordn_hub_section_device">This device</string>
|
||||
<string name="cordn_hub_coordinators">Coordinators</string>
|
||||
|
||||
Reference in New Issue
Block a user