|
|
|
@@ -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>
|