Public Access
Lay the phone out like the desktop when the surface is not a phone
Three destinations in a bar and everything else behind SETTINGS is the right shape at 360dp, where a fourth entry costs the width of the three that are there. On a tablet, an unfolded foldable or a landscape phone it is the wrong one: there is room for every destination at once, and the hub becomes an extra tap between somebody and a screen they can already see space for. So at 600dp — Android's own boundary between a compact window and a medium one, in the density-independent units Avalonia lays out in — the bar stands down and PhoneRail takes the left edge with all nine on it. It is the desktop's NavRail arrangement rather than its file: the two heads cannot share a view, and this one draws the phone's destination set with the phone's palette and touch targets. The flags are computed in code rather than assembled in the markup because none of them is a single question any more, and Avalonia's bindings have no "and" — and the header's condition is an "or", which not even a wrapper can express. That header is the one worth reading twice: narrow it stands down behind SETTINGS, so the hub's screens can draw their own; wide there is no hub to be behind, so it stays up everywhere. Losing it on the keychain would be losing the only LOCK button on the surface. Removing the hub means removing the routes into it, and there were four kinds. The rail has no SETTINGS entry, because that screen is a menu of the rail. The five back arrows in the screens under it are hidden, since an arrow to a screen the layout removed is the one control on a header that leads nowhere. The system back gesture goes to Hosts instead. And unfolding while sitting on the hub moves to Hosts, rather than leaving somebody on a list of things now visible beside it. One bug fixed on the way: OnBodyResized returned early unless the keyboard was open, so a foldable would have opened to a phone layout until somebody typed something. The chrome is refreshed first and unconditionally; the early return belongs to the older job below it. What this does not do is use the width *inside* a screen — the host list is one column at any size. Two columns needs the row model to change, because that list is headings and hosts in one sequence and a heading has to span, and that model is shared with the desktop. Check 8.1 walks the rail; nothing here is covered by a test, for the reason 8.0 exists.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:DodoSSH.Client.Shell.ViewModels"
|
||||
x:Class="DodoSSH.Client.Android.Views.PhoneRail"
|
||||
x:DataType="vm:MainWindowViewModel">
|
||||
|
||||
<!--
|
||||
◆ The destinations down the left edge, on a surface too wide to be a phone.
|
||||
|
||||
This is the desktop head's NavRail arrangement rather than a second design: a column of destinations at
|
||||
the left, each a glyph and a word, the current one lit. What it is not is that head's *file* — the two
|
||||
heads cannot share a view, and this one draws the phone's destination set with the phone's palette and
|
||||
the phone's touch targets.
|
||||
|
||||
── WHY IT EXISTS AT ALL ────────────────────────────────────────────────────────────────────────────────
|
||||
The bottom bar carries three entries and everything else lives one tap deeper behind SETTINGS. That is
|
||||
the right shape at 360dp, where a fourth entry costs the width of the three that are there. It is the
|
||||
wrong shape on a tablet or an unfolded foldable, where there is room for every destination at once and
|
||||
the hub becomes an extra tap between the user and a screen they can already see space for.
|
||||
|
||||
So above the breakpoint the bar stands down and this takes its place, with all nine destinations that
|
||||
were split between the bar and the hub. SETTINGS is not among them: it is a list of the things on this
|
||||
rail, and a rail entry leading to a menu of the rail would be furniture.
|
||||
|
||||
── WHAT IT SHOWS THAT THE HUB DID NOT ──────────────────────────────────────────────────────────────────
|
||||
A count beside Hosts and Keychain, which is what the desktop rail's extra width buys there too. Only
|
||||
where a count is real: Logs has no total until it is read and Preferences is not a quantity, so those
|
||||
show nothing rather than a zero — a number the application never computed is worse than a blank.
|
||||
|
||||
── SFTP AND S3 ARE HERE, WHERE THE DESKTOP PUTS THEM IN ITS TAB STRIP ──────────────────────────────────
|
||||
That head has fixed tabs for the two destinations that are not about the keychain; this one has no tab
|
||||
strip, and its hub already listed them beside the rest. Putting them anywhere else would be inventing a
|
||||
third arrangement for one screen's worth of difference.
|
||||
|
||||
── STYLES ARE LOCAL, COLOURS ARE NOT ───────────────────────────────────────────────────────────────────
|
||||
The .railentry styles live in this file because they are this control's own shape and nothing else wears
|
||||
them; every colour in them is resolved from Theme/Palette.axaml by key, which is the rule that matters —
|
||||
a rail that named its own blues is how a fifth surface colour ends up in the application.
|
||||
-->
|
||||
|
||||
<UserControl.Styles>
|
||||
<Style Selector="Button.railentry">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="CornerRadius" Value="10" />
|
||||
<Setter Property="Padding" Value="12,0" />
|
||||
|
||||
<!-- 48, not the bar's 64: a rail entry is a row rather than a target at the screen's edge, and nine
|
||||
of them at 64 would not fit the shortest surface this layout is drawn on. Still above the 44
|
||||
every other touch target here is held to. -->
|
||||
<Setter Property="MinHeight" Value="48" />
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.railentry:pointerover /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Panel}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.railentry.active /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Active}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.railentry TextBlock.railglyph">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
||||
<Setter Property="FontSize" Value="14" />
|
||||
<Setter Property="Width" Value="24" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.railentry TextBlock.raillabel">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextDim}" />
|
||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||
<Setter Property="FontSize" Value="12.5" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
<Setter Property="TextTrimming" Value="CharacterEllipsis" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.railentry TextBlock.railcount">
|
||||
<Setter Property="Foreground" Value="{StaticResource TextFaint}" />
|
||||
<Setter Property="FontFamily" Value="{StaticResource MonoFont}" />
|
||||
<Setter Property="FontSize" Value="10.5" />
|
||||
<Setter Property="VerticalAlignment" Value="Center" />
|
||||
</Style>
|
||||
|
||||
<!-- Three signals for the current entry rather than one, which is the rule NavButton already states for
|
||||
the bar it replaces: colour alone at this size is what a user with a colour deficiency is left
|
||||
with. Here it is the filled row, the accent, and the weight. -->
|
||||
<Style Selector="Button.railentry.active TextBlock.railglyph">
|
||||
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="Button.railentry.active TextBlock.raillabel">
|
||||
<Setter Property="Foreground" Value="{StaticResource AccentText}" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
</Style>
|
||||
</UserControl.Styles>
|
||||
|
||||
<Border Width="188" Background="{StaticResource Sidebar}"
|
||||
BorderBrush="{StaticResource Border}" BorderThickness="0,0,1,0">
|
||||
|
||||
<!--
|
||||
DockPanel with LastChildFill false, exactly as the desktop rail does it: Preferences is pinned to the
|
||||
foot because it is the one entry about the machine rather than about the keychain, and everything else
|
||||
flows from the top. A ScrollViewer over the top group so that a short surface — a foldable's cover
|
||||
display, a small window in Android's freeform mode — scrolls the list rather than clipping it.
|
||||
-->
|
||||
<DockPanel LastChildFill="False">
|
||||
|
||||
<StackPanel DockPanel.Dock="Bottom" Margin="8,0,8,10">
|
||||
<Button Classes="railentry" Classes.active="{Binding IsPreferencesShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Preferences}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="⚙" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Preferences" />
|
||||
</Grid>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer DockPanel.Dock="Top" VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled">
|
||||
<StackPanel Margin="8,10,8,0" Spacing="2">
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsHostsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Hosts}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="▤" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Hosts" />
|
||||
<TextBlock Grid.Column="2" Classes="railcount" Text="{Binding Vault.Hosts.Count}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<!--
|
||||
The terminal is a surface rather than a page, so it goes through its own command — the same
|
||||
reason the bottom bar's middle entry does. It is never lit, and deliberately: this rail is
|
||||
collapsed while a shell is showing, so IsCurrent could only ever read false. What marks that
|
||||
destination as current is the surface filling the screen.
|
||||
|
||||
No count, unlike the two above it. The shells strip sits above the bottom of every page on this
|
||||
surface and is that count, drawn as the sessions themselves — a number beside this word would be
|
||||
the same fact said twice, three inches apart.
|
||||
-->
|
||||
<Button Classes="railentry" Command="{Binding ShowTerminalCommand}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="⌗" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Connections" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Border Height="1" Background="{StaticResource Border}" Margin="4,8" />
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsKeychainShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Keychain}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="⚿" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Keychain" />
|
||||
<TextBlock Grid.Column="2" Classes="railcount" Text="{Binding Vault.Keys.Count}" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsSnippetsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Snippets}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="❯" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Snippets" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsTransfersShowing}"
|
||||
Command="{Binding ShowFilesCommand}"
|
||||
CommandParameter="{x:Static vm:RemoteKind.Host}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="⇅" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="SFTP" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsBucketsShowing}"
|
||||
Command="{Binding ShowFilesCommand}"
|
||||
CommandParameter="{x:Static vm:RemoteKind.Bucket}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="◳" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="S3" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsLogsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Logs}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="≡" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Logs" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
<Button Classes="railentry" Classes.active="{Binding IsVaultsShowing}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.Vaults}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto">
|
||||
<TextBlock Grid.Column="0" Classes="railglyph" Text="◎" />
|
||||
<TextBlock Grid.Column="1" Classes="raillabel" Text="Vaults" />
|
||||
</Grid>
|
||||
</Button>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
</DockPanel>
|
||||
</Border>
|
||||
|
||||
</UserControl>
|
||||
Reference in New Issue
Block a user