Files
DodoSSH/src/DodoSSH.Client.Android/Views/MoreScreen.axaml
T
jaap-jan c3dfa42108
ci / build and test (pull_request) Successful in 3m32s
ci / desktop nightly (pull_request) Skipped
ci / android head (pull_request) Successful in 5m17s
ci / api image (pull_request) Failing after 13s
Start port forwards on the phone, listed in a sheet off its terminal
The Android head skipped a host's forwards because it had nowhere to show
what was listening. Its terminal bar now draws a ⇄ for a tab that has
forwards, carrying the listening count in green and turning the danger
colour when one failed, and it raises a sheet over the same rows the
desktop sidebar lists: notation, route, state, failure reason and a tap
to switch each off or on. MainWindowViewModel.StartForwards no longer
returns early on Android.

The sheet is shell state like the connect sheet: it collapses the
renderer while up, is lowered by leaving the terminal, and the back
gesture closes it first.

Forwards ride the shell's keep-alive rather than holding the foreground
service themselves, since a forward cannot outlive its shell. They only
change the notification, which now counts ports forwarded beside shells
connected, fed by a new ForwardsChanged event and ListeningForwardCount.

The phone still has no editor for forwards; the More screen card, README
and design-import-gaps now say they run here and are edited on the
desktop.
2026-09-28 21:06:05 +02:00

227 lines
13 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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.MoreScreen"
x:DataType="vm:MainWindowViewModel"
Background="{StaticResource Canvas}">
<!--
Design v2 — SETTINGS: the hub for everything the bottom bar has no room for.
Three slots and ten destinations is the arithmetic the design solves by putting seven of them one tap
deeper. This screen is that tap. It takes the shell as its data context rather than the vault, because
every row on it is a navigation command and nothing here reads an item.
Teams is the tenth and the design never drew it — see the row itself. That one is the design's count
plus one rather than a rearrangement of it. The keychain below is a rearrangement, and the only one:
the bottom bar lost a slot to gain this row.
The rows are the design's list rows rather than cards: a card is one thing you act on, and a destination
is not a thing — it is a place. Each carries a sentence saying what is behind it, because a hub whose
entries are one word each is a menu you have to open to read.
── from MORE to SETTINGS ──────────────────────────────────────────────────────────────────────────────
The hub was called More because that was the honest name for a handful of things that did not fit. With
the keychain on it as well it is the place everything that is not connecting to a machine lives, which
has a name of its own and a glyph everyone already knows.
The keychain is first, and above the rule, because it is the only row here that holds the user's own
material rather than a screen about the application. Everything below it is somewhere to look at what
has happened or how this phone behaves.
-->
<Grid RowDefinitions="Auto,*">
<Border Grid.Row="0" Height="56" Padding="16,0">
<TextBlock Classes="heading" Text="Settings" />
</Border>
<ScrollViewer Grid.Row="1">
<StackPanel Margin="10,0,10,20" Spacing="2">
<!--
"Keychain", although the design's tab says Vault and the enum member is Vault. The split is a
standing rule rather than an inconsistency: every sentence a user reads calls the encrypted store a
keychain, and everything under the surface calls it a vault, because renaming the wire route, the
tables and the CLR types would put an AAD resource type inside the blast radius of a
find-and-replace. See docs/design-import-gaps.md. A row reading Vault would be the one place the
product surface leaked the implementation's word.
-->
<Button Classes="row" Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Keychain}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="⚿" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Keychain" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
Text="Keys, passwords, buckets and tags — everything in the vault that is not a host." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<Border Height="1" Background="{StaticResource Border}" Margin="16,8" />
<Button Classes="row" Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Snippets}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="❯" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Snippets" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}" Text="Saved commands, typed into an open shell." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<!--
These two go through ShowFiles rather than ShowScreen: they are one screen over one view model,
and which kind of remote it offers is the thing being chosen. See ShowFilesCommand for why that
is set by the button rather than on arrival.
-->
<Button Classes="row" Command="{Binding ShowFilesCommand}"
CommandParameter="{x:Static vm:RemoteKind.Host}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="⇅" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="SFTP" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
Text="Browse a host's files." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<Button Classes="row" Command="{Binding ShowFilesCommand}"
CommandParameter="{x:Static vm:RemoteKind.Bucket}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="◳" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="S3" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
Text="Objects in an S3-compatible bucket from the keychain." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<Button Classes="row" Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Logs}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="≡" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Logs" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}" Text="What has been connected to, and what has changed." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<!--
Vaults, which the v2 design has no row for — it is a shipped screen the design had no slot for
rather than a drawn one with nothing behind it. It is on the phone because a vault arrives
without being asked for, and somebody it arrives for is at least as likely to be holding a phone.
◎ rather than a glyph of its own. The desktop rail already draws this destination with it, and
two heads giving one destination two marks is how a user learns the wrong one.
-->
<Button Classes="row" Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Vaults}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="◎" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Vaults" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
Text="Your vaults, who is in each one, and who holds its key." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<Button Classes="row" Command="{Binding ShowScreenCommand}"
CommandParameter="{x:Static vm:ShellScreen.Preferences}">
<Grid ColumnDefinitions="Auto,*,Auto">
<TextBlock Grid.Column="0" Text="⚙" Foreground="{StaticResource AccentText}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Preferences" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
Text="Unlocking with a fingerprint, and what this phone cannot do." />
</StackPanel>
<TextBlock Grid.Column="2" Text="›" Foreground="{StaticResource TextGhost}" FontSize="15"
VerticalAlignment="Center" />
</Grid>
</Button>
<!--
Locking, here as well as in the header.
The header carries it on HOSTS and stands down behind SETTINGS, so without this row every screen
under here would be two taps from locking the keychain. That is the wrong direction for the one
action whose whole value is being quick, and it is why this is a row on the hub rather than
something you go back for.
-->
<Button Classes="row" Margin="0,10,0,0" Command="{Binding LockCommand}">
<Grid ColumnDefinitions="Auto,*">
<TextBlock Grid.Column="0" Text="⚿" Foreground="{StaticResource TextDim}" FontSize="14"
Width="22" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Spacing="2" VerticalAlignment="Center">
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="Lock the keychain" />
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
Text="Shells you have open keep running." />
</StackPanel>
</Grid>
</Button>
<!--
◆ Port forwarding, stated rather than drawn.
The v2 design gives this hub a sixth entry and a full screen behind it: local, remote and dynamic
rules with toggles and byte counters. Forwarding is built and runs here as on the desktop — a host's
forwards start with every terminal opened to it — but it is not a destination, so it has no row. A
forward belongs to a host and runs with a session, and on this head the session's forwards are a
sheet off the terminal's bar: the ⇄ button, drawn for a tab that has some, lists them with a switch
each. See MainWindowViewModel.IsForwardsSheetOpen.
What the phone lacks is the editor: its host form has no PORT FORWARDS section yet, so forwards are
added on the desktop and arrive here with the host. The byte counters are not built anywhere;
SSH.NET keeps no count to draw.
It is a paragraph here rather than a missing row for the reason the desktop keeps TEAMS in its rail:
a place somebody looks for a feature should say where it went. See docs/design-import-gaps.md.
-->
<Border Classes="card" Margin="6,16,6,0">
<StackPanel Spacing="7">
<StackPanel Orientation="Horizontal" Spacing="9">
<TextBlock Text="⇄" Foreground="{StaticResource TextFaint}" FontSize="14"
VerticalAlignment="Center" />
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold"
Foreground="{StaticResource TextDim}" Text="Port forwarding" />
<Border Classes="tag outline" VerticalAlignment="Center">
<TextBlock Text="IN THE TERMINAL" />
</Border>
</StackPanel>
<TextBlock Classes="body"
Text="Forwards saved on a host start with every terminal opened to it, here as on the desktop. The ⇄ button in the terminal's bar lists them, with a switch each and the reason one could not start. They are added and edited in the desktop app's host editor; this phone runs them but has no editor for them yet." />
</StackPanel>
</Border>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>