Public Access
Merge branch 'claude/adding-hosts-phone-docs-e472b0'
This commit is contained in:
commit
0d239bcd6c
43 files changed
+6976
-147
No files matched your search
@@ -8,10 +8,13 @@
|
||||
<!--
|
||||
Design v2 — MORE: the hub for everything the bottom bar has no room for.
|
||||
|
||||
Four slots and nine destinations is the arithmetic the design solves by putting five of them one tap
|
||||
Four slots and ten destinations is the arithmetic the design solves by putting six 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. The count is the design's plus
|
||||
one rather than a rearrangement of it: nothing moved out of the bottom bar to make room.
|
||||
|
||||
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.
|
||||
@@ -89,6 +92,29 @@
|
||||
</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">
|
||||
|
||||
@@ -14,10 +14,11 @@
|
||||
state machine. What differs is only what each one draws.
|
||||
|
||||
── v2 ────────────────────────────────────────────────────────────────────────────────────────────────
|
||||
The desktop's eight rail destinations become four in a bottom bar, and five more live one tap deeper
|
||||
behind MORE: snippets, SFTP, S3, logs and preferences. 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 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.
|
||||
@@ -58,7 +59,7 @@
|
||||
|
||||
Hidden behind MORE, 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 five hub screens draw their own and this one stands down rather than stacking a second row of
|
||||
so the hub's screens draw their own and this one stands down rather than stacking a second row of
|
||||
chrome above theirs. It stays on the two destinations that are the product's top level, where the
|
||||
vault's name and the sync light are the most useful thing a header could say.
|
||||
|
||||
@@ -135,7 +136,7 @@
|
||||
============ under MORE ============
|
||||
|
||||
The hub itself takes the shell as its data context, because every row on it is a navigation
|
||||
command; the five destinations behind it each take the view model they are about, so each one is
|
||||
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.
|
||||
@@ -150,6 +151,15 @@
|
||||
<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 Teams is not nullable: the reason for the wrapper
|
||||
is the data context, not the null. IsTeamShowing is the shell's and Teams is not the shell.
|
||||
-->
|
||||
<Panel IsVisible="{Binding IsTeamShowing}">
|
||||
<views:TeamsScreen DataContext="{Binding Teams}" />
|
||||
</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
|
||||
@@ -282,8 +292,9 @@
|
||||
CommandParameter="{x:Static vm:ShellScreen.Vault}" />
|
||||
|
||||
<!--
|
||||
IsMoreSurface rather than IsMoreShowing: this tab stands for six screens, and a bar that went
|
||||
dark the moment you opened one of them would only ever light three of its four entries.
|
||||
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 three of its
|
||||
four entries.
|
||||
-->
|
||||
<views:NavButton Grid.Column="3" Label="More" Glyph="≣" IsCurrent="{Binding IsMoreSurface}"
|
||||
Command="{Binding ShowScreenCommand}"
|
||||
|
||||
@@ -272,7 +272,7 @@ internal sealed partial class PhoneShell : UserControl
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// v2 is the first arrangement here with a second level: five destinations sit behind MORE, each with
|
||||
/// v2 is the first arrangement here with a second level: six destinations sit behind MORE, each with
|
||||
/// its own back arrow. Android's back is the same gesture as that arrow and users reach for it first,
|
||||
/// and left unhandled it does not go up — it finishes the activity. Ending the application from a log
|
||||
/// screen is not a plausible reading of "back".
|
||||
@@ -347,7 +347,7 @@ internal sealed partial class PhoneShell : UserControl
|
||||
switch (current.Screen)
|
||||
{
|
||||
case ShellScreen.Snippets or ShellScreen.Logs or ShellScreen.Transfers
|
||||
or ShellScreen.Buckets or ShellScreen.Preferences:
|
||||
or ShellScreen.Buckets or ShellScreen.Preferences or ShellScreen.Team:
|
||||
current.ShowScreenCommand.Execute(ShellScreen.More);
|
||||
e.Handled = true;
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
<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.TeamsScreen"
|
||||
x:DataType="vm:TeamsViewModel"
|
||||
Background="{StaticResource Canvas}">
|
||||
|
||||
<!--
|
||||
TEAMS, under MORE — and the one screen behind that hub the v2 phone design never drew.
|
||||
|
||||
It is the reverse of every other entry in docs/design-import-gaps.md: a shipped screen the design had
|
||||
no slot for, rather than a drawn screen with nothing behind it. It is on the phone because an
|
||||
invitation is claimed by *signing in*, and the person being invited is at least as likely to be
|
||||
holding a phone as sitting at a desktop — a team the server has just put somebody into, visible only
|
||||
on a head they may never have installed, is a membership they cannot see.
|
||||
|
||||
That argument is also why the invited list is drawn here and not treated as an administrator's detail:
|
||||
the people on it are the ones who cannot yet see the team, and the row says out loud that no mail was
|
||||
sent.
|
||||
|
||||
So there is no mock-up to depart from. What this departs from instead is the desktop screen over the
|
||||
same view model, and every difference below is a phone difference rather than a second opinion.
|
||||
|
||||
**The desktop's two columns are one.** A 268-pixel team list beside a members-and-vaults table does
|
||||
not exist at 360dp, so the three lists stack in one scrolling column with the teams at the top. That
|
||||
is the same thing HOSTS does with the desktop's sidebar and its connect column, and for the same
|
||||
reason.
|
||||
|
||||
**Nothing scrolls inside anything.** The desktop caps its members and vaults lists at 240 and 200
|
||||
pixels so the two can sit above each other in one pane. Here every list is sized to its content and
|
||||
the screen's own ScrollViewer does all of the scrolling: a list that scrolls inside a page is a region
|
||||
a thumb has to find the edges of, and three of them on one screen is three ways to get stuck.
|
||||
|
||||
◆ **SHARE KEY is drawn and nothing that takes something away is.** That is a decision rather than a
|
||||
subset. Wrapping a vault key is the one act on this screen a server cannot perform at all — it needs a
|
||||
machine that already holds the key, and this phone is one — so a teams screen that could only be read
|
||||
would leave the product's central claim undemonstrated on the head most people carry. REMOVE MEMBER,
|
||||
WITHDRAW KEY and REVOKE INVITATION are the other half of that, and each of them acts on the first
|
||||
press: the view model's armed-confirmation state covers archiving a team and handing one over, and
|
||||
those three are not armed by it. The desktop guards them with a tooltip instead, which is a control a
|
||||
touch screen has no way to show. An irreversible revocation under a thumb with its explanation missing
|
||||
is the wrong trade, so all three stay on the desktop — where the sentence beside them is visible.
|
||||
Archiving and hand-over are not drawn either, for a plainer reason: they decide whether a team goes on
|
||||
existing and who controls it, which is not a thing to do while walking.
|
||||
|
||||
**ADD MEMBER is not drawn either**, and it is the operation this screen least needs. It is an address
|
||||
typed into a box, a directory lookup, a role picker, and a paragraph beside it saying what adding
|
||||
somebody did *not* do — and since invitations arrived the ordinary way into a team is one the server
|
||||
claims at sign-in, which is what put this screen on the phone at all. Creating a team is here, because
|
||||
a team is where those invitations are sent from and it is two short fields.
|
||||
|
||||
**The key-holder list under a vault is not drawn.** It is a fourth list, it belongs to the selected
|
||||
vault rather than to the team, and the view model publishes no flag saying whether it has anything in
|
||||
it — so a heading for it would sit over nothing whenever nobody holds a key, which is exactly the
|
||||
empty state this head insists comes from the view model rather than from markup. What the phone can
|
||||
answer about a vault is on the vault's own row: whether *this* machine can open it.
|
||||
|
||||
**↻ and `+` both, because this screen has more reason to re-read than any other.** Nothing here is
|
||||
cached — it is all read from the server on arrival and again at the end of every command — so the one
|
||||
thing a member cannot otherwise see is a change somebody else just made: a vault key wrapped to them
|
||||
from a colleague's desktop, or a team they have this moment been invited into. On the desktop the
|
||||
re-read is leaving the rail and coming back, which is one click. Here it is a trip out to MORE and
|
||||
back, so the button earns its place. It binds to a real command rather than to ShowScreen(Team),
|
||||
which would set Screen to the value it already holds, raise nothing and reload nothing.
|
||||
-->
|
||||
|
||||
<Grid RowDefinitions="Auto,Auto,Auto,*,Auto">
|
||||
|
||||
<!-- ============ header ============ -->
|
||||
<Grid Grid.Row="0" ColumnDefinitions="Auto,*,Auto,Auto" Height="56" Margin="8,0">
|
||||
<Button Grid.Column="0" Classes="icon" Content="←"
|
||||
Command="{Binding $parent[views:PhoneShell].((vm:MainWindowViewModel)DataContext).ShowScreenCommand}"
|
||||
CommandParameter="{x:Static vm:ShellScreen.More}" />
|
||||
<TextBlock Grid.Column="1" Classes="heading" Text="Teams" Margin="4,0" />
|
||||
<Button Grid.Column="2" Classes="icon" Content="↻" Command="{Binding RefreshCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="3" Classes="icon accent" Content="+" Command="{Binding NewTeamCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
</Grid>
|
||||
|
||||
<!-- ============ a new team ============ -->
|
||||
<!--
|
||||
Above the list rather than in place of it, which is the opposite of what the host and snippet
|
||||
editors do — and the difference is what the form is about. Those two edit a row that is on screen,
|
||||
so a card stacked over the list hides the thing being changed. This one is about a team that does
|
||||
not exist yet, and the teams that do are exactly the useful thing to be able to see while naming it:
|
||||
the slug has to be unique on this server, and the near misses are right underneath.
|
||||
-->
|
||||
<Border Grid.Row="1" Classes="card" Margin="12,0,12,8" IsVisible="{Binding IsCreatingTeam}">
|
||||
<StackPanel Spacing="10">
|
||||
<TextBlock Classes="label" Text="NEW TEAM" />
|
||||
|
||||
<TextBox Classes="field" Text="{Binding NewTeamName}" PlaceholderText="name" />
|
||||
<TextBox Classes="field" Text="{Binding NewTeamSlug}" PlaceholderText="slug-for-urls" />
|
||||
|
||||
<TextBlock Classes="body"
|
||||
Text="The slug is lowercase letters, digits and hyphens, and has to be unique across this server. It is fixed once the team exists — a team can be renamed and its slug cannot." />
|
||||
|
||||
<Grid ColumnDefinitions="*,8,*">
|
||||
<Button Grid.Column="0" Classes="primary" Height="44" Content="CREATE"
|
||||
Command="{Binding CreateTeamCommand}" IsEnabled="{Binding !IsBusy}" />
|
||||
<Button Grid.Column="2" Classes="secondary" Height="44" Content="CANCEL"
|
||||
Command="{Binding CancelNewTeamCommand}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!--
|
||||
Status, and it is the empty state as well: the view model writes "you are not in a team yet" into
|
||||
the same property it writes an offline notice and every command's outcome into. A literal here would
|
||||
be a second voice saying the same thing slightly differently.
|
||||
-->
|
||||
<TextBlock Grid.Row="2" Classes="detail" Margin="18,2,18,6" TextWrapping="Wrap"
|
||||
Text="{Binding Status}"
|
||||
IsVisible="{Binding Status, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
|
||||
|
||||
<!-- ============ the column ============ -->
|
||||
<ScrollViewer Grid.Row="3">
|
||||
<StackPanel Margin="0,0,0,18">
|
||||
|
||||
<TextBlock Classes="section" Text="TEAMS" Margin="18,4,18,4" />
|
||||
|
||||
<!--
|
||||
Rows as cards, filled when chosen, which is what HOSTS settled on in v2 and what the radius
|
||||
ladder calls a card: one item, one rule, one thing you act on. The fill is on the item rather
|
||||
than on a Border inside it so the rounding the theme draws for selection is the row's own.
|
||||
-->
|
||||
<ListBox ItemsSource="{Binding Teams}" SelectedItem="{Binding SelectedTeam}"
|
||||
IsVisible="{Binding HasTeams}" Background="Transparent" BorderThickness="0">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Margin" Value="10,1" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Active}" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamRowViewModel">
|
||||
<Grid ColumnDefinitions="*,Auto" MinHeight="54" Margin="14,11">
|
||||
<StackPanel Grid.Column="0" Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="detail" FontSize="10.5" Text="{Binding Detail}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- The caller's own role in this team, which is what says why some of it is read-only. -->
|
||||
<Border Grid.Column="1" Classes="tag outline" Margin="8,0,0,0">
|
||||
<TextBlock Text="{Binding Role}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<!-- ============ the chosen team ============ -->
|
||||
<StackPanel IsVisible="{Binding HasSelection}">
|
||||
|
||||
<TextBlock Classes="section" Text="MEMBERS" Margin="18,18,18,4" />
|
||||
|
||||
<ListBox ItemsSource="{Binding Members}" SelectedItem="{Binding SelectedMember}"
|
||||
Background="Transparent" BorderThickness="0">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Margin" Value="10,1" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Active}" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamMemberRowViewModel">
|
||||
<Grid ColumnDefinitions="*,Auto" MinHeight="54" Margin="14,11">
|
||||
<StackPanel Grid.Column="0" Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="detail" FontSize="10.5" Text="{Binding Email}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<!--
|
||||
◆ The one fact on this row that decides whether the button at the foot of the screen
|
||||
can do anything: an account with no published identity key has nothing for a vault
|
||||
key to be wrapped to. One sentence, from the view model, painted twice rather than
|
||||
written twice — the warning colour is the whole of the difference, and a converter
|
||||
for it would hide that the two are the same string.
|
||||
|
||||
The published case is quiet rather than green. Green on this head means a shell is
|
||||
open right now, and a published key is a durable fact about an account — borrowing
|
||||
the status colour for it would be the second meaning that makes the first
|
||||
unreadable. Only the missing key is coloured, because only it needs answering.
|
||||
-->
|
||||
<TextBlock Classes="detail" FontSize="10" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource TextDim}" Text="{Binding KeyState}"
|
||||
IsVisible="{Binding Member.IsEnrolled}" />
|
||||
<TextBlock Classes="detail" FontSize="10" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource WarnText}" Text="{Binding KeyState}"
|
||||
IsVisible="{Binding !Member.IsEnrolled}" />
|
||||
|
||||
<!--
|
||||
A date to the day, or that they have never been here at all. The view model writes
|
||||
both, and neither is a guess: the server records the account's last authenticated
|
||||
request at most once an hour, which is what makes a day the honest unit.
|
||||
-->
|
||||
<TextBlock Classes="detail" FontSize="9.5" Foreground="{StaticResource TextFaint}"
|
||||
Text="{Binding LastActive}" />
|
||||
</StackPanel>
|
||||
|
||||
<Border Grid.Column="1" Classes="tag outline" Margin="8,0,0,0">
|
||||
<TextBlock Text="{Binding Role}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Classes="body" Margin="18,10,18,0"
|
||||
Text="Being in a team is what lets the server hand somebody this team's vaults. It is not what lets them read one: a vault key can only be wrapped by a machine that already holds it, which is what sharing below does." />
|
||||
|
||||
<!-- ============ ◆ who has been asked and has not arrived ============ -->
|
||||
<!--
|
||||
The section this screen exists for, and the one the desktop had nothing to draw until
|
||||
invitations were built. Read-only here: withdrawing one is a control that acts on the first
|
||||
press, which is the line drawn at the top of this file.
|
||||
|
||||
So these are cards rather than the flat rows above them, and the shape is the difference: a row
|
||||
that fills when you touch it is one of several you are choosing between, and there is nothing
|
||||
to choose here. An ItemsControl rather than a ListBox for the same reason — a list with a
|
||||
selection nothing reads would be a control offering something it cannot do.
|
||||
|
||||
Gated on the view model's own count rather than left to stand over an empty list, because a
|
||||
team with nobody outstanding is the ordinary case and a permanent empty heading would make it
|
||||
look like a section that had failed to load.
|
||||
|
||||
The waiting row carries the whole mechanism in its own sentence — no mail was sent, and they
|
||||
join when they first sign in here. That is the sentence somebody has to read, because every
|
||||
other product's version of this word means an email is on its way.
|
||||
-->
|
||||
<StackPanel IsVisible="{Binding HasInvitations}">
|
||||
<TextBlock Classes="section" Text="INVITED" Margin="18,18,18,4" />
|
||||
|
||||
<ItemsControl ItemsSource="{Binding Invitations}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamInvitationRowViewModel">
|
||||
<Border Classes="card" Margin="12,3">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<StackPanel Grid.Column="0" Spacing="3" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="12.5" Text="{Binding Email}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
<TextBlock Classes="detail" FontSize="10" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource TextDim}" Text="{Binding State}"
|
||||
IsVisible="{Binding !IsPending}" />
|
||||
<TextBlock Classes="detail" FontSize="10" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource WarnText}" Text="{Binding State}"
|
||||
IsVisible="{Binding IsPending}" />
|
||||
</StackPanel>
|
||||
<Border Grid.Column="1" Classes="tag outline" Margin="8,0,0,0">
|
||||
<TextBlock Text="{Binding Role}" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock Classes="section" Text="VAULTS" Margin="18,18,18,4" />
|
||||
|
||||
<ListBox ItemsSource="{Binding Vaults}" SelectedItem="{Binding SelectedVault}"
|
||||
Background="Transparent" BorderThickness="0">
|
||||
<ListBox.Styles>
|
||||
<Style Selector="ListBoxItem">
|
||||
<Setter Property="Padding" Value="0" />
|
||||
<Setter Property="MinHeight" Value="0" />
|
||||
<Setter Property="Margin" Value="10,1" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
<Style Selector="ListBoxItem:selected /template/ ContentPresenter">
|
||||
<Setter Property="Background" Value="{StaticResource Active}" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
</ListBox.Styles>
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate x:DataType="vm:TeamVaultRowViewModel">
|
||||
<StackPanel Spacing="3" MinHeight="54" Margin="14,11" VerticalAlignment="Center">
|
||||
<TextBlock Classes="mono" FontSize="13.5" FontWeight="SemiBold" Text="{Binding Name}"
|
||||
TextTrimming="CharacterEllipsis" />
|
||||
|
||||
<!--
|
||||
Whether *this* phone can open it, which is a property of its keyring rather than
|
||||
anything the server could answer. Painted the same two ways as the member's key
|
||||
state above, because it is the same question asked from the other end.
|
||||
-->
|
||||
<TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource TextDim}" Text="{Binding State}"
|
||||
IsVisible="{Binding IsReadable}" />
|
||||
<TextBlock Classes="detail" FontSize="10.5" TextWrapping="Wrap"
|
||||
Foreground="{StaticResource WarnText}" Text="{Binding State}"
|
||||
IsVisible="{Binding !IsReadable}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock Classes="body" Margin="18,10,18,0"
|
||||
Text="A vault listed here that this phone has no key to stays listed and stays shut. That is the ordinary case rather than a fault: somebody has been added to the team and nobody has wrapped the key to them yet." />
|
||||
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
<!-- ============ ◆ giving somebody the key ============ -->
|
||||
<!--
|
||||
Raised over the column when both halves of the act have been chosen, as HOSTS raises its connect bar
|
||||
and SNIPPETS its insert bar, and for the reason written there: there is no second column to put it
|
||||
in, so it names what it will do rather than relying on a selection being visible beside the button.
|
||||
|
||||
Two wrappers rather than one condition. Sharing needs a member *and* a vault, and a binding cannot
|
||||
say `SelectedMember is not null && SelectedVault is not null` without a converter that does not
|
||||
exist — the log screen makes the same trade for the same reason. It also gets the halves in the
|
||||
right order: choosing who comes first, and until a vault is chosen there is nothing to offer them.
|
||||
-->
|
||||
<Panel Grid.Row="4" IsVisible="{Binding SelectedMember, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<Border IsVisible="{Binding SelectedVault, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
Background="{StaticResource Chrome}" BorderBrush="{StaticResource Border}"
|
||||
BorderThickness="0,1,0,0" Padding="14,12">
|
||||
<StackPanel Spacing="9">
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="label" Text="THE KEY TO" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding SelectedVault.Name}"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Classes="label" Text="FOR" />
|
||||
<TextBlock Classes="mono" FontSize="11" Text="{Binding SelectedMember.Name}"
|
||||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
|
||||
<Button Classes="primary" Content="SHARE KEY" Command="{Binding ShareVaultCommand}"
|
||||
IsEnabled="{Binding !IsBusy}" />
|
||||
|
||||
<!--
|
||||
The sentence the desktop hangs off a tooltip, which a phone cannot show — so it is body text
|
||||
under the button, where it is read before the tap rather than after it. It is not decoration:
|
||||
the key-log check proves this server has been consistent with itself and nothing more.
|
||||
-->
|
||||
<TextBlock Classes="body"
|
||||
Text="Their published key is checked against the server's append-only key log first, and nothing is wrapped if it does not appear there unchanged. That proves the server has been consistent with itself — not that the key is the right person's. Compare the fingerprint with them over a channel this server does not carry before sharing anything that matters." />
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Panel>
|
||||
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,10 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace DodoSSH.Client.Android.Views;
|
||||
|
||||
/// <summary>Teams, under MORE — who is in one, and which of its vaults this phone can open.</summary>
|
||||
internal sealed partial class TeamsScreen : UserControl
|
||||
{
|
||||
public TeamsScreen() => AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
Reference in new issue
Block a user