Public Access
Let a tap on the phone's host list mean connect
Choosing a machine raised the connect bar over the bottom of the list: a password box, CONNECT, EDIT, MOVE and DELETE. Five controls in the way of the one thing a tap on a machine's name obviously means. So the gestures split. A tap connects. A long press raises the bar, with all five. The pencil in the phone's header — its only persistent chrome — edits whichever host is chosen, which is the one of the five common enough to be worth a control that is always in the same place. The flag doing it is the desktop's own IsHostPaneOpen rather than a second one. That head made exactly this move when a selection stopped opening its drawer, and the question both are asking is "has somebody asked about this host" — answering it twice is how two heads come to disagree about what a selection means. One tap cannot finish: a host that authenticates with a typed password has nowhere on a list to be given one. That tap raises the bar with the box in it and says so, and a second tap with the box filled in connects. The branch is in the view model rather than in the head, because "can this machine be reached without asking for anything" is the same question the bar's own password box answers, and a copy of it in a view would be a second reading of a binding chain that has one. Two mechanics worth knowing. Avalonia raises Tapped on release whatever the press lasted, so a long press would open the bar and then connect — one touch firing both gestures — which is why HostsScreen tracks the hold and swallows the tap it precedes. And Holding only fires once IsHoldingEnabled is set, so that and the handler are attached together rather than one in markup and one in code. ConnectToRecent now opens the pane rather than selecting the row. On the phone it has to: a selection alone raises nothing now, so going back to a recent machine would land on a screen with nothing to press.
This commit is contained in:
@@ -1872,6 +1872,7 @@ internal sealed partial class VaultViewModel(
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(AnEditorIsOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsConnectBar))]
|
||||
[NotifyPropertyChangedFor(nameof(CanEditSelectedHost))]
|
||||
[NotifyPropertyChangedFor(nameof(IsDrawerOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(IsShowingHostDetail))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsHostPaneActions))]
|
||||
@@ -1904,6 +1905,7 @@ internal sealed partial class VaultViewModel(
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(AnEditorIsOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsConnectBar))]
|
||||
[NotifyPropertyChangedFor(nameof(CanEditSelectedHost))]
|
||||
[NotifyPropertyChangedFor(nameof(IsDrawerOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(IsShowingHostDetail))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsHostPaneActions))]
|
||||
@@ -1965,6 +1967,7 @@ internal sealed partial class VaultViewModel(
|
||||
[NotifyPropertyChangedFor(nameof(IsDrawerOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(IsShowingHostDetail))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsHostPaneActions))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsConnectBar))]
|
||||
private bool isHostPaneOpen;
|
||||
|
||||
/// <summary>
|
||||
@@ -2128,6 +2131,7 @@ internal sealed partial class VaultViewModel(
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(AnEditorIsOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsConnectBar))]
|
||||
[NotifyPropertyChangedFor(nameof(CanEditSelectedHost))]
|
||||
private bool isAddSheetOpen;
|
||||
|
||||
/// <summary>
|
||||
@@ -2154,6 +2158,7 @@ internal sealed partial class VaultViewModel(
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(AnEditorIsOpen))]
|
||||
[NotifyPropertyChangedFor(nameof(ShowsConnectBar))]
|
||||
[NotifyPropertyChangedFor(nameof(CanEditSelectedHost))]
|
||||
[NotifyPropertyChangedFor(nameof(GroupSheetLabel))]
|
||||
private SidebarGroupHeader? groupSheet;
|
||||
|
||||
@@ -2176,12 +2181,42 @@ internal sealed partial class VaultViewModel(
|
||||
/// Whether the phone's connect bar has anything to be about.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A host is chosen and nothing is covering the list. Both halves are needed and the second is the one
|
||||
/// worth stating: the editor cards replace the list rather than floating over it, so a bar left showing
|
||||
/// underneath would carry CONNECT and EDIT for a host that is no longer on screen — and under the host
|
||||
/// editor, for the very record being typed into.
|
||||
/// <para>
|
||||
/// ◆ <b>Selecting a host no longer raises this bar, and <see cref="IsHostPaneOpen"/> is the difference.</b>
|
||||
/// It used to read <c>SelectedHost is not null</c>, so a tap on any row put a card over the bottom of the
|
||||
/// list carrying a password box, CONNECT, EDIT, MOVE and DELETE — five controls in the way of the one
|
||||
/// thing a tap on a machine's name means, which is connect to it. A tap connects now; a long press asks
|
||||
/// for this. See <see cref="ConnectToRowAsync"/> and <see cref="OpenHostPane"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It is the same flag and the same reasoning the desktop's drawer already used, which is why it is that
|
||||
/// flag rather than a second one: the question both heads are asking is "has somebody asked about this
|
||||
/// host", and answering it twice is how two heads come to disagree about what a selection means.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The second half stays and is still worth stating: the editor cards replace the list rather than
|
||||
/// floating over it, so a bar left showing underneath would carry CONNECT and EDIT for a host that is no
|
||||
/// longer on screen — and under the host editor, for the very record being typed into.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal bool ShowsConnectBar => SelectedHost is not null && !AnEditorIsOpen;
|
||||
internal bool ShowsConnectBar => SelectedHost is not null && IsHostPaneOpen && !AnEditorIsOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the phone's header should be offering the pencil that edits the selected host.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The other half of the gesture change above. With the bar no longer raised by choosing a machine, EDIT
|
||||
/// went behind a long press — so the header carries a pencil for the row that <em>is</em> chosen, which
|
||||
/// is the one control a thumb can reach without opening anything. It is hidden rather than disabled while
|
||||
/// an editor is up, because a pencil that opens the form already on screen is a control with nothing to
|
||||
/// do.
|
||||
/// <para>
|
||||
/// It deliberately does not ask which screen is showing. That is the shell's question and this is the
|
||||
/// vault's; the header wraps this in the shell's own <c>IsHostsShowing</c>, which is the arrangement
|
||||
/// PhoneShell already uses everywhere Avalonia's bindings need an "and".
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
internal bool CanEditSelectedHost => SelectedHost is not null && !AnEditorIsOpen;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the phone's connect bar is showing its own controls rather than one of the two panels that
|
||||
@@ -7376,6 +7411,59 @@ internal sealed partial class VaultViewModel(
|
||||
[RelayCommand(AllowConcurrentExecutions = true)]
|
||||
private Task ConnectAsync() => ConnectToSelectedHostAsync(CancellationToken.None);
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the host a tap landed on, or raises the phone's bar when it needs a password first.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <b>The phone's tap, and it lives here rather than in the head because the branch is a product rule
|
||||
/// rather than a gesture.</b> Which gesture means "open this" is the head's business — that is why the
|
||||
/// files screen maps its own tap in code-behind — but <em>whether this machine can be reached without
|
||||
/// asking for anything</em> is the same question the connect bar's own password box answers, and a copy
|
||||
/// of it in a view would be a second reading of a binding chain that already has one.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The two outcomes are both "connect": one of them arrives, and the other needs a secret first and so
|
||||
/// puts the bar up with the box in it and says so. What it must never do is quietly connect with no
|
||||
/// password, or open a bar for a host that did not need one — that bar is five controls over the bottom
|
||||
/// of the list, and it is what a tap used to raise for every machine.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A successful tap closes the bar. Tapping a second machine while the first one's bar is up would
|
||||
/// otherwise leave the panel behind on the new selection, which is a bar nobody asked for, opened by the
|
||||
/// gesture that exists to avoid opening one.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// It takes no cancellation token and allows concurrent executions, for the two reasons
|
||||
/// <see cref="ConnectAsync"/> carries at length.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[RelayCommand(AllowConcurrentExecutions = true)]
|
||||
private Task ConnectToRowAsync(HostRowViewModel? row)
|
||||
{
|
||||
if (row is null)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
SelectedHost = row;
|
||||
|
||||
// Read after the assignment, because both are about the row that was just chosen. ConnectPassword
|
||||
// is checked as well as the binding: a bar already up with a password typed into it is exactly the
|
||||
// second tap this should honour rather than answer with the same instruction again.
|
||||
if (SelectedHostAsksForAPassword && ConnectPassword.Length == 0)
|
||||
{
|
||||
IsHostPaneOpen = true;
|
||||
Status = $"{row.Label} asks for a password. Type it below, then CONNECT.";
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
IsHostPaneOpen = false;
|
||||
|
||||
return ConnectToSelectedHostAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ConnectAsync" />
|
||||
/// <param name="cancellationToken">
|
||||
/// Whatever the caller's own lifetime is. The command passes none; the host-key retry passes its own,
|
||||
@@ -8862,6 +8950,7 @@ internal sealed partial class VaultViewModel(
|
||||
OnPropertyChanged(nameof(SelectedHostAsksForAPassword));
|
||||
OnPropertyChanged(nameof(SelectedHostAuthenticationNote));
|
||||
OnPropertyChanged(nameof(ShowsConnectBar));
|
||||
OnPropertyChanged(nameof(CanEditSelectedHost));
|
||||
|
||||
// Every field the drawer's detail pane draws. They are properties of the vault rather than of the
|
||||
// row because two of them need the group chain read and one needs the keychain searched, and none of
|
||||
|
||||
Reference in New Issue
Block a user