Public Access
The connecting card set its status string once, when the tab was created, and never touched it again. Every connection therefore looked identical from the outside: one three seconds into a key exchange, one waiting out a fifteen-second timeout against a machine that is asleep, and one that had hung all drew the same "connecting…". The card now draws the five steps of getting there, each lit at the moment the handshake reports reaching it, over an amber track that fills as they finish. ◆ NOTHING ON THE LIST IS INVENTED. Every row changes state because a layer below it said so, at the instant the thing it names actually began. That is the whole reason it is worth showing, and it is why most of this commit is plumbing rather than XAML: there was no progress reporting anywhere in the stack to hook a step list onto, and a card animating plausible progress would have been indistinguishable from one that had stopped receiving any. SshConnectionPhase names four phases and deliberately not more. SSH.NET runs the entire handshake inside one ConnectAsync and raises exactly one event from the middle of it — HostKeyReceived, once the key exchange has produced a key to show — so that event is the only interior moment there is to report. Everything before it is Reaching and everything after it is Authenticating. A fifth phase in that assembly would have to be a timer, so there is not one. OpeningShell is reported by TerminalWorkspace instead, because that is where it happens: the factory's work ends with an authenticated connection, and asking for a pseudo-terminal on one is a separate round trip. The SFTP path passes null — a second connection opened behind an already-open shell has nobody watching a step list for it. The card's fifth step, "Starting the terminal", is the renderer wait and lives in the shell rather than in the SSH assembly, which has never heard of a renderer. On the first connection after a cold start it is a real wait with a real failure mode of its own — a missing WebView2 runtime — so a list that began at "reaching the host" would leave the one wait most likely to hang unnamed. Amber for the step in flight, and that follows the palette's rule rather than bending it. Green is what is true and purple is what you can press; a step still happening is neither, and it is exactly the caveat-worth-reading that amber exists for. Steps behind it go green as they become true. Nothing animates, which is the argument TransfersScreen.axaml already makes for its own track, reaching a screen with far more reason to want a spinner: a spinner is furniture invented to fill a state nobody measured, and these states are measured, so the track fills to what has finished and then waits there. A refusal keeps the step it stopped on, in red, with the ones behind it still green. That is the half a progress bar could not do, and it is the difference between "that host is not there" and "that host is there and would not have me" — a question the reason sentence alone frequently does not settle. The strip's dot goes amber while a tab is connecting, on both heads. It was grey, and so is a tab whose shell has exited: the two states in that strip with the least in common, one worth waiting for and one over. PhoneShell's own comment already recorded half of this — the dot stopped being green before anything had answered — and this is the other half. Progress is raised inline rather than through System.Progress<T>, which captures whatever synchronisation context it was constructed on and posts to it. That reads like a convenience and is really a second place the marshalling decision gets made: silently, differently under a test with no context, and out of order with respect to the failure that follows a phase. The shell marshals once, in one handler, through a new optional post parameter on MainWindowViewModel — the same seam TransfersViewModel already uses, and for the reason its own remark gives. The three Dispatcher.UIThread.Post calls that predate it are the ones this suite's comments record as out of reach; they are left alone rather than swept in here. Both heads draw the list. They differ in one place: Phone.axaml's mono class sets a colour and a size along with the family, so the caption rule names its own family instead of composing the two and asking two rules for one Foreground. The desktop's mono sets the family alone, which is why ConnectingCard does compose them. Each head also gains SHOW LOGS beside the button that gives up — the step list is this attempt and the log is every other one, which is what a connection taking too long actually raises. Seven tests, and the two that matter most run against the container rather than a fake: a real handshake reports its phases in order, and a host-key refusal never claims to have authenticated. A fake asserting what it was written to assert would have established nothing about either. The rest cover the tab advancing while the connection is gated, the step a refusal stops on, and a phase reported after the user has given up on the tab. 1,861 tests, none failing. The Android head's layout is not verified by anything. It compiles, and compiled bindings mean every new binding path resolves, but that project is not in DodoSSH.slnx, there is no test project for it and no device here — so unlike the desktop card, whose shapes the layout harness measures, these rows have not been drawn. Vertical fit is reasoned, not observed.
418 lines
26 KiB
XML
418 lines
26 KiB
XML
<UserControl xmlns="https://github.com/avaloniaui"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
|
xmlns:views="using:DodoSSH.Client.Android.Views"
|
|
x:Class="DodoSSH.Client.Android.Views.PhoneShell"
|
|
x:DataType="vm:MainWindowViewModel"
|
|
Background="{StaticResource Canvas}">
|
|
|
|
<!--
|
|
The phone's single view, and the counterpart of the desktop head's MainWindow — except that this one has
|
|
no window, no nav rail, no titlebar and no status bar.
|
|
|
|
It switches on ShellState, which is the same six states the desktop has because both heads share one
|
|
state machine. What differs is only what each one draws.
|
|
|
|
── v2 ────────────────────────────────────────────────────────────────────────────────────────────────
|
|
The desktop's eight rail destinations become four in a bottom bar, and the rest live one tap deeper
|
|
behind MORE: snippets, SFTP, S3, logs, preferences — and teams, which v2 did not draw and which is
|
|
argued for on the screen itself. That is the v2 design's own arrangement, and it replaces the first
|
|
design's four, which had nothing behind them at all. Five characters was a desktop constraint and the
|
|
phone uses words.
|
|
|
|
The order is the design's rather than the rail's. Terminal sits second, beside Hosts, because those two
|
|
are the pair a session moves between; on the desktop the terminal is not a rail entry at all.
|
|
|
|
── a terminal gets the screen ─────────────────────────────────────────────────────────────────────────
|
|
Three of the four rows below stand down while a shell is showing: the header, the shells strip and the
|
|
bottom bar itself. The strip asks IsShowingPages directly; the other two go through flags the control
|
|
computes, and the bar's is the one that differs — it stays up on Connections with nothing running, which
|
|
is the terminal surface drawing a page rather than a shell. See PhoneShell.RefreshChrome.
|
|
|
|
The arithmetic is why. Header 56, strip 46, bar 64, and the terminal's own two rows on top of that: at
|
|
360dp the shell was framed by about a third of the display, all of it about somewhere the user was not.
|
|
What takes their place is one 35-pixel bar drawn by the surface itself, carrying back on the left and
|
|
the sessions and a + across from it. See TerminalScreen.axaml.
|
|
-->
|
|
|
|
<!--
|
|
Named, and the name is load-bearing: everything the phone draws is inside this one element, so its
|
|
bottom margin is the single place the software keyboard can be kept off the box being typed into,
|
|
whichever of the eleven screens is showing. See PhoneShell.axaml.cs.
|
|
-->
|
|
<Panel x:Name="Body">
|
|
|
|
<!-- ============ getting in ============ -->
|
|
<views:PendingScreen IsVisible="{Binding IsStarting}"
|
|
Heading="OPENING THE KEYCHAIN"
|
|
Detail="Reading this phone's local cache to find out whether it is enrolled." />
|
|
|
|
<views:ServerScreen IsVisible="{Binding IsNeedingServer}" />
|
|
<views:EnrollScreen IsVisible="{Binding IsNeedingEnrollment}" />
|
|
<views:RecoveryCodeScreen IsVisible="{Binding IsShowingRecoveryCode}" />
|
|
<views:LockedScreen IsVisible="{Binding IsLocked}" />
|
|
|
|
<!-- ============ unlocked ============ -->
|
|
<!--
|
|
◆ TWO LAYOUTS, AND THE WIDTH DECIDES WHICH.
|
|
|
|
Below 600dp this is the phone it always was: a header, the screen, the shells strip and a three-entry
|
|
bar across the bottom, with everything else one tap deeper behind SETTINGS. That is right at 360dp,
|
|
where a fourth bar entry costs the width of the three that are there.
|
|
|
|
At or above it — a tablet, an unfolded foldable, a landscape phone, a freeform window — the bar stands
|
|
down and PhoneRail takes the left edge with every destination on it at once, which is the desktop
|
|
head's arrangement. The hub is not one of them: it is a menu of the rail.
|
|
|
|
The three flags are computed in code rather than assembled here, because none of them is a single
|
|
question any more and Avalonia's bindings have no "and" — and the header's is an "or", which not even
|
|
a wrapper can express. See PhoneShell.RefreshChrome, which is also where the one behavioural
|
|
consequence lives: unfolding while sitting on the hub moves to Hosts, rather than leaving somebody on
|
|
a list of things they can now see beside it.
|
|
|
|
An outer Grid of two columns rather than a DockPanel, so the rail's width is the rail's own business
|
|
and the content takes what is left.
|
|
-->
|
|
<Grid IsVisible="{Binding IsUnlocked}" ColumnDefinitions="Auto,*">
|
|
|
|
<views:PhoneRail Grid.Column="0"
|
|
IsVisible="{Binding $parent[views:PhoneShell].ShowsRail}" />
|
|
|
|
<Grid Grid.Column="1" RowDefinitions="Auto,*,Auto,Auto">
|
|
|
|
<!--
|
|
The header: which vault, and whether it is synced.
|
|
|
|
Hidden behind SETTINGS on a narrow surface, and that is the design's arrangement rather than a
|
|
saving. v2 gives every screen one header carrying that screen's own name and its own actions — a
|
|
back arrow, an add, a refresh — so the hub's screens draw their own and this one stands down rather
|
|
than stacking a second row of chrome above theirs. What is left is HOSTS, which is the screen the
|
|
application opens on and the one where the vault's name and the sync light are the most useful thing
|
|
a header could say.
|
|
|
|
◆ One flag where there were two nested conditions, and it gained a third meaning with the rail. It
|
|
still stands down over a terminal, and still stands down behind SETTINGS on a narrow surface — but
|
|
on a wide one it stays up everywhere, because there is no hub to be behind and the screens on the
|
|
rail draw no header of their own. Losing it on the keychain would be losing the only LOCK button on
|
|
the surface. See PhoneShell.ShowsVaultHeader.
|
|
-->
|
|
<Panel Grid.Row="0" IsVisible="{Binding $parent[views:PhoneShell].ShowsVaultHeader}">
|
|
<!--
|
|
◆ DeepChrome rather than Chrome, since v5. The desktop's own titlebar and nav rail made the same
|
|
move in v5b — one step darker than Chrome, #0B0B14 against #10111E, so the frame reads as what
|
|
holds the glass rather than as another pane of it — and this header is the phone's equivalent
|
|
furniture: it is what the desktop's titlebar is, on the surface that has no window to carry one.
|
|
HostActionBar, which takes this header's own place while hosts are selected, and the editor's
|
|
header in HostsScreen, which the header stands down for, both move with it for the same reason;
|
|
see the remark on each.
|
|
-->
|
|
<Border Background="{StaticResource DeepChrome}" BorderBrush="{StaticResource Border}"
|
|
BorderThickness="0,0,0,1" Padding="14,0" Height="56">
|
|
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
|
|
|
|
<!--
|
|
Filled rather than outlined since v2. The mark is the one thing on this header that is not a
|
|
fact about the vault, and the design gives it the accent as a solid tile — which is also what
|
|
the launcher icon draws, so the two agree.
|
|
-->
|
|
<Border Grid.Column="0" Width="26" Height="26" CornerRadius="8"
|
|
Background="{StaticResource Accent}" VerticalAlignment="Center">
|
|
<TextBlock Text=">_" Foreground="{StaticResource AccentInk}"
|
|
FontFamily="{StaticResource MonoFont}" FontSize="10" FontWeight="Bold"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
|
</Border>
|
|
|
|
<TextBlock Grid.Column="1" Classes="heading" Margin="10,0,8,0" FontSize="16"
|
|
Text="{Binding Vault.VaultName}" TextTrimming="CharacterEllipsis" />
|
|
|
|
<!--
|
|
The sync light, and it is green only when it has earned it — see SyncLabel. The design draws
|
|
a permanently green "Synced" here, which is the one claim on that mock-up this application
|
|
will not make.
|
|
-->
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
|
<Ellipse Classes="dot" Classes.live="{Binding IsFullySynced}" Width="6" Height="6"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Classes="label" FontSize="9" Text="{Binding SyncLabel}" />
|
|
</StackPanel>
|
|
|
|
<!--
|
|
◆ THE PENCIL WAS HERE, AND IT MOVED INTO THE ACTION BAR.
|
|
|
|
It was in the header because a long press raised a connect card carrying EDIT, and a card over
|
|
the bottom of the list was the wrong place for the one action people reach for most. Both
|
|
halves of that changed at once: a long press now chooses hosts rather than raising a card, and
|
|
the bar it raises takes this header's place — so the pencil is in that bar, beside the count of
|
|
what it would edit. See HostActionBar.axaml, which is drawn in the Panel below this one.
|
|
|
|
What is left in this row is the vault's name, the sync light and LOCK: three facts about the
|
|
keychain, and none of them about a row.
|
|
-->
|
|
<Button Grid.Column="3" Classes="icon" Margin="4,0,0,0" Command="{Binding LockCommand}"
|
|
ToolTip.Tip="Lock the keychain">
|
|
<TextBlock Text="LOCK" Classes="label" FontSize="8.5"
|
|
Foreground="{StaticResource TextDim}" />
|
|
</Button>
|
|
</Grid>
|
|
</Border>
|
|
</Panel>
|
|
|
|
<!-- ============ ◆ the contextual action bar ============ -->
|
|
<!--
|
|
In the header's row and in its place, never beside it. A bar that stacked under the vault's name
|
|
would be two rows of chrome above a list at 360dp, and worse, it would leave the keychain's name and
|
|
LOCK on screen over a list that is no longer about the keychain — see PhoneShell.ShowsHostSelectionBar,
|
|
which is where the two are made exclusive.
|
|
|
|
Wrapped so the data context can be the vault, as every other screen in this file is: what the bar
|
|
counts and what its four controls do are one vault's business, and the shell's own binding would
|
|
resolve none of them.
|
|
-->
|
|
<Panel Grid.Row="0" IsVisible="{Binding $parent[views:PhoneShell].ShowsHostSelectionBar}">
|
|
<views:HostActionBar DataContext="{Binding Vault}" />
|
|
</Panel>
|
|
|
|
<!-- The screens. Only one draws; which one is the shell's business. -->
|
|
<Panel Grid.Row="1">
|
|
<!--
|
|
Wrapped rather than bound directly, and the desktop head's MainWindow carries the same note for
|
|
the same reason: this element's visibility is the shell's business and its data context is the
|
|
vault, and putting both on one element resolves IsVisible against the vault — where
|
|
IsHostsShowing does not exist. It is a compile error here rather than a silent blank screen only
|
|
because compiled bindings are on.
|
|
-->
|
|
<Panel IsVisible="{Binding IsHostsShowing}">
|
|
<views:HostsScreen DataContext="{Binding Vault}" />
|
|
</Panel>
|
|
<Panel IsVisible="{Binding IsKeychainShowing}">
|
|
<views:KeychainScreen DataContext="{Binding Vault}" />
|
|
</Panel>
|
|
<!--
|
|
IsTerminalSurface rather than IsTerminalShowing: a tab exists from the moment connecting starts and
|
|
its session does not, so this screen has to be on show while there is nothing to render — it is
|
|
what says which machine is being connected to. Which of the two things inside it draws is decided
|
|
in the screen itself. See TerminalTabViewModel.
|
|
-->
|
|
<views:TerminalScreen IsVisible="{Binding IsTerminalSurface}" />
|
|
|
|
<!--
|
|
============ under MORE ============
|
|
|
|
The hub itself takes the shell as its data context, because every row on it is a navigation
|
|
command; the destinations behind it each take the view model they are about, so each one is
|
|
wrapped. SnippetsScreen and LogsScreen are nullable on the shell — they are rebuilt on every
|
|
unlock and nulled on lock — and it is the collapsed wrapper that keeps a template from binding
|
|
against nothing.
|
|
-->
|
|
<views:MoreScreen IsVisible="{Binding IsMoreShowing}" />
|
|
|
|
<Panel IsVisible="{Binding IsSnippetsShowing}">
|
|
<views:SnippetsScreen DataContext="{Binding SnippetsScreen}" />
|
|
</Panel>
|
|
|
|
<Panel IsVisible="{Binding IsLogsShowing}">
|
|
<views:LogsScreen DataContext="{Binding LogsScreen}" />
|
|
</Panel>
|
|
|
|
<!--
|
|
The sixth destination behind MORE, and the one v2 never drew — see the comment on the screen
|
|
itself. Wrapped like its neighbours even though Vaults is not nullable: the reason for the
|
|
wrapper is the data context, not the null. IsVaultsShowing is the shell's and Vaults is not the
|
|
shell.
|
|
|
|
Vaults, not Vault: this one is the vaults themselves and the people in them, where the other is
|
|
one vault's contents and is what the hosts and keychain screens draw.
|
|
-->
|
|
<Panel IsVisible="{Binding IsVaultsShowing}">
|
|
<views:VaultsScreen DataContext="{Binding Vaults}" />
|
|
</Panel>
|
|
|
|
<!--
|
|
One screen for both file destinations. SFTP and S3 differ in which picker they offer and in
|
|
nothing else below it — the panes, the queue and the transfers are the same IRemoteFileStore
|
|
either way — so drawing them twice would be two copies of a screen that has to stay identical.
|
|
Which picker shows is TransfersViewModel.Remote, set on arrival by the shell.
|
|
-->
|
|
<Panel IsVisible="{Binding IsTransfersShowing}">
|
|
<views:FilesScreen DataContext="{Binding Transfers}" />
|
|
</Panel>
|
|
<Panel IsVisible="{Binding IsBucketsShowing}">
|
|
<views:FilesScreen DataContext="{Binding Transfers}" />
|
|
</Panel>
|
|
|
|
<!--
|
|
Preferences carries the header its siblings under MORE have, written here rather than inside the
|
|
screen: the screen is one of two that hang off this hub with a back arrow, and the arrow belongs
|
|
to the shell that knows where back is.
|
|
|
|
It held a PendingScreen until it had a setting on it. The one it gained is this phone's device
|
|
key — the fingerprint the lock screen offers and that nothing here could previously enrol.
|
|
-->
|
|
<Grid IsVisible="{Binding IsPreferencesShowing}" RowDefinitions="Auto,*">
|
|
<Grid Grid.Row="0" ColumnDefinitions="Auto,*" Height="56" Margin="8,0">
|
|
<!-- Gone on a wide surface, with the five on the screens under the hub and for the reason
|
|
written there. -->
|
|
<Button Grid.Column="0" Classes="icon" Content="←" Command="{Binding ShowScreenCommand}"
|
|
IsVisible="{Binding !$parent[views:PhoneShell].IsWide}"
|
|
CommandParameter="{x:Static vm:ShellScreen.More}" />
|
|
<TextBlock Grid.Column="1" Classes="heading" Text="Preferences" Margin="4,0" />
|
|
</Grid>
|
|
<views:PreferencesScreen Grid.Row="1" />
|
|
</Grid>
|
|
</Panel>
|
|
|
|
<!--
|
|
◆ The two host-key decisions, over everything. Outside the screen Panel deliberately: file transfer
|
|
makes its own trust decision on the desktop, and the phone now has that screen — so a sheet nested
|
|
inside the hosts screen would be one the files screen could not raise.
|
|
|
|
◆ And that arrangement is what the shell now relies on rather than merely permits. It used to move to
|
|
HOSTS before letting a handshake ask the question, because the desktop head drew it as a banner on that
|
|
screen; it does not any more, so this sheet is what is over the Connections surface when a machine
|
|
typed into the connect box by hand turns out to be a first contact. Which is the case the navigation
|
|
was worst for — it took the box away. See MainWindowViewModel.OnVaultConnectionFailed.
|
|
-->
|
|
<Panel Grid.Row="1">
|
|
<views:HostKeySheet DataContext="{Binding Vault}" />
|
|
</Panel>
|
|
|
|
<!--
|
|
The shells strip. The design puts it above the nav on every screen, and that is the single most
|
|
desktop-shaped idea in the product — it is what makes a terminal a surface you switch to rather than
|
|
a screen you navigate away from. Kept, because losing it would make an open shell invisible from the
|
|
host list, which is where you are when you open the next one.
|
|
|
|
v2 draws the sessions as pills rather than as a labelled row, and drops the word SHELLS: with a
|
|
rounded chip carrying a live dot and a name, the label was spending nine characters of a 360dp row
|
|
saying what the row already looks like.
|
|
|
|
On every screen except the one it names. The terminal draws these same sessions in its own bar, and
|
|
two rows of the same pills — one of them 46 pixels of it — is the arrangement this surface exists to
|
|
stop. Wrapped rather than given a second condition, because the strip's own visibility is about
|
|
whether there are any tabs and this one is about which surface is up.
|
|
|
|
◆ And it stands down under the host editor, which is a page rather than a card since the pencil moved
|
|
into the action bar. A strip of open shells above a form is the same two-rows-of-chrome problem the
|
|
terminal has, on a screen where the form is the whole point of being there. See
|
|
PhoneShell.ShowsShellStrip, which is where that "and" is made, Avalonia's bindings having none.
|
|
-->
|
|
<Panel Grid.Row="2" IsVisible="{Binding $parent[views:PhoneShell].ShowsShellStrip}">
|
|
<!-- DeepChrome rather than Sidebar, since v5, joining the header and the bar above and below it:
|
|
the strip is chrome the same way they are — a frame around the screen rather than a pane of
|
|
it — and Sidebar is what a card sits on, which this row is not. -->
|
|
<Border IsVisible="{Binding HasTabs}" Background="{StaticResource DeepChrome}"
|
|
BorderBrush="{StaticResource Border}" BorderThickness="0,1,0,0" Height="46">
|
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
|
|
<ItemsControl ItemsSource="{Binding Tabs}" Margin="12,0" VerticalAlignment="Center">
|
|
<ItemsControl.ItemsPanel>
|
|
<ItemsPanelTemplate><StackPanel Orientation="Horizontal" Spacing="6" /></ItemsPanelTemplate>
|
|
</ItemsControl.ItemsPanel>
|
|
<ItemsControl.ItemTemplate>
|
|
<DataTemplate x:DataType="vm:TerminalTabViewModel">
|
|
<Button Classes="row" MinHeight="34" Padding="13,0" CornerRadius="10"
|
|
Background="{StaticResource Panel}" BorderBrush="{StaticResource BorderMid}"
|
|
BorderThickness="1"
|
|
Command="{Binding $parent[views:PhoneShell].((vm:MainWindowViewModel)DataContext).SelectTabCommand}"
|
|
CommandParameter="{Binding}">
|
|
<StackPanel Orientation="Horizontal" Spacing="7" VerticalAlignment="Center">
|
|
<!--
|
|
Green only while there is a shell behind the tab. It used to be lit unconditionally,
|
|
which was true when a tab could not exist without a session; one can now —
|
|
connecting opens the tab first — and a dot that was green before anything had
|
|
answered would be the one thing on this strip claiming something untrue.
|
|
|
|
Amber while it is being made, which is the other half of that correction. Not being
|
|
green stopped the dot lying, but it left a tab still dialling drawn exactly like a
|
|
tab whose shell has exited — the two states on this strip with the least in common,
|
|
one worth waiting for and one over. See Phone.axaml.
|
|
-->
|
|
<Ellipse Classes="dot" Classes.live="{Binding IsLive}"
|
|
Classes.connecting="{Binding IsConnecting}" Width="6" Height="6"
|
|
VerticalAlignment="Center" />
|
|
<TextBlock Classes="mono" FontSize="11" Text="{Binding Label}" />
|
|
</StackPanel>
|
|
</Button>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</ScrollViewer>
|
|
</Border>
|
|
</Panel>
|
|
|
|
<!-- ============ navigation ============ -->
|
|
<!--
|
|
Gone while a shell is showing, which is the whole of that surface's arrangement: the bar's three
|
|
destinations are replaced by a back arrow and a + that leads to all of them, both in the terminal's
|
|
own bar. See TerminalScreen.axaml.
|
|
|
|
◆ A shell rather than the surface, which are not the same question — Connections with nothing running
|
|
is that surface drawing a page, and the bar stays under it. The screen it stands down for is the one
|
|
that pays for the room; a box asking which machine to connect to does not, and taking the nav away
|
|
from somebody who has just closed their last tab left them with only the back gesture.
|
|
|
|
◆ It is no longer a single question — a wide surface takes the rail instead — so it reads one flag
|
|
the control computes rather than a condition here. See PhoneShell.ShowsBottomBar.
|
|
|
|
── three rather than four ─────────────────────────────────────────────────────────────────────────
|
|
The keychain moved behind SETTINGS. A bottom bar is for the places a session moves between, and keys,
|
|
credentials and tags are not that — they are managed occasionally and then left alone, which is the
|
|
shape of everything else already behind the hub. What is left is the two halves of using this
|
|
application, and the drawer holding the rest.
|
|
-->
|
|
<!-- DeepChrome rather than Chrome, since v5 — see the remark on the vault header, above, which this
|
|
bar is the foot of the same frame the header is the top of. -->
|
|
<Border Grid.Row="3" IsVisible="{Binding $parent[views:PhoneShell].ShowsBottomBar}"
|
|
Background="{StaticResource DeepChrome}" BorderBrush="{StaticResource Border}"
|
|
BorderThickness="0,1,0,0" Height="64">
|
|
<Grid ColumnDefinitions="*,*,*">
|
|
|
|
<views:NavButton Grid.Column="0" Label="Hosts" Glyph="▤" IsCurrent="{Binding IsHostsShowing}"
|
|
Command="{Binding ShowScreenCommand}"
|
|
CommandParameter="{x:Static vm:ShellScreen.Hosts}" />
|
|
|
|
<!--
|
|
The terminal is a surface rather than a page — see ShellSurface — so this one does not go
|
|
through ShowScreen. Its own command is on the shell.
|
|
|
|
"Connections" rather than "Terminal", and the word does more work than a rename usually does:
|
|
with nothing open this surface is where a connection is *made* — a machine typed in by hand, and
|
|
the ones connected to before — so a tab labelled Terminal would name the thing behind the door
|
|
rather than the door. The enum member stays ShellSurface.Terminal, for the reason the keychain's
|
|
tab is not called Vault: the surface is a terminal, and the word a user reads is the product's.
|
|
|
|
◆ It lights again, and IsTerminalSurface is the right question rather than a near miss. This bar
|
|
is drawn in exactly two situations — a page, and Connections with nothing running — and that flag
|
|
separates them: false for every page, true for the one screen where this is where you are. With a
|
|
shell up the bar is gone and nothing here is read at all. It was left unbound while the bar was
|
|
collapsed for the whole of the terminal surface, which made a lit state unreachable; a screen the
|
|
bar can now sit under and not point at is the entry looking broken instead.
|
|
-->
|
|
<views:NavButton Grid.Column="1" Label="Connections" Glyph="⌗"
|
|
IsCurrent="{Binding IsTerminalSurface}"
|
|
Command="{Binding ShowTerminalCommand}" />
|
|
|
|
<!--
|
|
IsMoreSurface rather than IsMoreShowing: this tab stands for the hub and everything behind it,
|
|
and a bar that went dark the moment you opened one of them would only ever light two of its
|
|
three entries.
|
|
|
|
"Settings" and a gear, where it was "More" and a hamburger. The hub held a handful of things
|
|
that did not fit and could not be called anything better than More; with the keychain, teams and
|
|
preferences all on it, it is the place everything that is not connecting to something lives —
|
|
which is what a gear has meant on a phone since before this application existed.
|
|
-->
|
|
<views:NavButton Grid.Column="2" Label="Settings" Glyph="⚙" IsCurrent="{Binding IsMoreSurface}"
|
|
Command="{Binding ShowScreenCommand}"
|
|
CommandParameter="{x:Static vm:ShellScreen.More}" />
|
|
|
|
</Grid>
|
|
</Border>
|
|
|
|
</Grid>
|
|
|
|
</Grid>
|
|
|
|
</Panel>
|
|
|
|
</UserControl>
|