Public Access
The Android device key store, the biometric gate and the lock screen's UNLOCK WITH FINGERPRINT button have all shipped since this head was written, and none of them could ever run: that button appears only when a device key exists, and nothing on the phone could create one. `CanUnlockWithDevice` was false on every launch of every phone. This is the missing half. **The offer is on PREFERENCES**, which held a PendingScreen until it had a setting on it. It is there rather than beside the button it turns on because registering needs an unlocked keychain and a reachable server — the vault has to be open to seal the bundle, and the wrap has to reach the account or a phone somebody has lost could never be revoked. Neither is true on the lock screen. One card, and exactly one of its three blocks is ever drawn: the offer, the withdrawal, or the sentence saying this phone has nowhere to keep a key. That is `CanRegisterDevice` / `CanForgetDevice` / `HasNoDeviceKeyOption`, which are two flags and not one and its negation for the reason written where they are set — a phone with no screen lock and a phone already registered are both "cannot register", and only the second has anything to take back. The withdrawal has no confirmation, deliberately, and the sentence above it carries what the desktop puts in a tooltip this head has no room for. `StatusMessage` is on the screen because it is the only feedback this head has once the system's own dialogue has gone. **Two things would have been wrong in the feature the moment it worked.** `Environment.MachineName` answers `localhost` on Android, and registering names the device — so every phone would have arrived in the account's device list as another identical row, on the very screen a lost handset is revoked from. `PhoneEnvironment.DeviceName` was already written and never called; the shell now takes it as an optional constructor argument that the desktop does not pass, and it reaches enrollment, registration and every connection log entry. That was gap §7 of docs/android-port.md, and it is now closed. And the status line said "Waiting for Windows…" over an Android biometric prompt. `GestureWait` picks the sentence from the platform rather than from a head, unlike the device name beside it: a device name is a fact about one handset only the head can read, and which dialogue appears is a fact about the operating system this assembly is running on. Two tests cover the seam — the injected name reaching the account, and the default still being this machine's own name — and `FakeVaultServer` records what each device called itself, because the name is the only part of a registration a person ever reads. The gesture itself is unreachable from any test process, so Phase 13 of docs/manual-checks.md carries five checks, including that enrolling a new fingerprint in Android's own Settings destroys the key. That one is the property that makes this a fast path rather than a weakening of the passphrase.
223 lines
13 KiB
XML
223 lines
13 KiB
XML
<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.Vault}">
|
||
<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>
|
||
|
||
<!--
|
||
Teams, 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 an invitation is
|
||
claimed by signing in, and somebody being invited is at least as likely to be holding a phone.
|
||
|
||
◎ rather than a glyph of its own. The desktop rail already draws teams 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.Team}">
|
||
<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="Teams" />
|
||
<TextBlock Classes="detail" Foreground="{StaticResource TextDim}"
|
||
Text="Who shares a keychain with you, 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. Nothing in this application forwards anything — ISshConnection
|
||
offers OpenShellAsync and nothing else — so every switch on that screen would be a control with no
|
||
effect and every counter a number with no source.
|
||
|
||
It is a paragraph here rather than a missing row for the reason the desktop keeps TEAMS in its rail:
|
||
a destination that is quietly absent reads as a product that never intended it, and this one is
|
||
intended. SyncEntityType.PortForward = 9 is reserved on the wire and unused. 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="NOT BUILT" />
|
||
</Border>
|
||
</StackPanel>
|
||
<TextBlock Classes="body"
|
||
Text="A connection here opens one channel — a shell — and nothing else, so there is no tunnel for a rule to run through. The wire protocol reserves a slot for forwarding rules and no client writes one. Nothing on this screen is hidden by a setting; the feature is absent." />
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
|
||
</Grid>
|
||
|
||
</UserControl>
|