Both Retire and Reset are now FlatButton's footprint in danger colours — a full-width bordered rectangle, 56dp tall, tinted with the error colour — as the old Reset button was. A bare TextButton next to a paragraph of body text had no edge to read as a control at all, which matters more here than elsewhere: these two are the destructive pair. Stacked rather than in a row, unlike the desktop's, because "Retire this node…" beside "Reset this node…" is ~170dp each on a phone and both wrap. Same width-driven split the label/value lists already make. The spinner keeps the Reset button's place while a wipe runs and both buttons fade, so nothing moves.
This commit is contained in:
+61
-31
@@ -2,6 +2,7 @@ package com.gregshuflin.synchronicity.ensemble
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
@@ -18,6 +19,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -488,36 +490,26 @@ private fun ThisNodeSection(nativeLib: NativeLib, onRetire: () -> Unit, onLeaveN
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
// The two read as one pair of alternatives, so they are the same control in the same
|
||||
// weight — the desktop draws them the same way, for the same reason. Retire is first:
|
||||
// it is the one that leaves the ensemble in a coherent state, and the sentence above
|
||||
// The two read as one pair of alternatives, so they are the same control at the same
|
||||
// size — the desktop draws them the same way, for the same reason. Retire is first: it
|
||||
// is the one that leaves the ensemble in a coherent state, and the sentence above
|
||||
// introduces them in that order.
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
//
|
||||
// Stacked rather than side by side, unlike the desktop's row, for the same reason the
|
||||
// label/value lists differ: "Retire this node…" beside "Reset this node…" is ~170dp
|
||||
// each on a phone and both labels wrap. It is a width decision, not a taste one.
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
EndingButton(
|
||||
label = copy.retireAction,
|
||||
enabled = !resetting,
|
||||
onClick = { confirming = Ending.RETIRE },
|
||||
)
|
||||
if (resetting) {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
modifier = Modifier.size(20.dp),
|
||||
)
|
||||
Text(
|
||||
text = copy.retireWiping,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.error,
|
||||
)
|
||||
} else {
|
||||
EndingButton(
|
||||
label = copy.resetAction,
|
||||
enabled = true,
|
||||
onClick = { confirming = Ending.RESET },
|
||||
)
|
||||
}
|
||||
EndingButton(
|
||||
label = if (resetting) copy.retireWiping else copy.resetAction,
|
||||
enabled = !resetting,
|
||||
busy = resetting,
|
||||
onClick = { confirming = Ending.RESET },
|
||||
)
|
||||
}
|
||||
resetError?.let { error ->
|
||||
Text(
|
||||
@@ -609,18 +601,56 @@ private enum class Ending {
|
||||
RESET,
|
||||
}
|
||||
|
||||
/** Retire and Reset, in the one weight they share. */
|
||||
/**
|
||||
* Retire and Reset, in the one shape they share.
|
||||
*
|
||||
* [com.gregshuflin.synchronicity.ui.FlatButton]'s footprint — a full-width bordered rectangle, 56dp
|
||||
* tall — in the error colour ADR 0032 §4 reserves for destructive acts. Not FlatButton itself,
|
||||
* which is neutral by design. It stays local until something else wants a danger button, at which
|
||||
* point it belongs beside FlatButton.
|
||||
*/
|
||||
@Composable
|
||||
private fun EndingButton(label: String, enabled: Boolean, onClick: () -> Unit) {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.error),
|
||||
private fun EndingButton(
|
||||
label: String,
|
||||
enabled: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
busy: Boolean = false,
|
||||
) {
|
||||
val error = MaterialTheme.colorScheme.error
|
||||
val tint = if (enabled) error else error.copy(alpha = DISABLED_ALPHA)
|
||||
Surface(
|
||||
onClick = { if (enabled) onClick() },
|
||||
modifier =
|
||||
modifier.fillMaxWidth().height(ENDING_BUTTON_HEIGHT).border(1.dp, tint, RectangleShape),
|
||||
shape = RectangleShape,
|
||||
color = error.copy(alpha = ENDING_BUTTON_FILL_ALPHA),
|
||||
contentColor = tint,
|
||||
) {
|
||||
Text(label)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (busy) {
|
||||
CircularProgressIndicator(color = tint, modifier = Modifier.size(SPINNER_SIZE))
|
||||
}
|
||||
Text(text = label, style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Matches [com.gregshuflin.synchronicity.ui.FlatButton], which these are a danger variant of. */
|
||||
private val ENDING_BUTTON_HEIGHT = 56.dp
|
||||
|
||||
/** Enough tint to read as filled without competing with the border. */
|
||||
private const val ENDING_BUTTON_FILL_ALPHA = 0.1f
|
||||
|
||||
/** How far a disabled [EndingButton]'s border and label are faded. */
|
||||
private const val DISABLED_ALPHA = 0.5f
|
||||
|
||||
private val SPINNER_SIZE = 20.dp
|
||||
|
||||
/** Said at the top of the roster when this node's own credential has been revoked. */
|
||||
@Composable
|
||||
private fun SelfRevokedNotice() {
|
||||
|
||||
Reference in New Issue
Block a user