Update credentials better looking

This commit is contained in:
Geoffroy BONNEVILLE
2020-04-30 15:32:42 +02:00
parent d2814c6c67
commit 1b981b00d5
8 changed files with 145 additions and 45 deletions

View File

@@ -331,6 +331,7 @@ namespace ModernKeePass.Infrastructure.KeePass
public void UpdateCredentials(Credentials credentials) public void UpdateCredentials(Credentials credentials)
{ {
_pwDatabase.MasterKey = CreateCompositeKey(credentials); _pwDatabase.MasterKey = CreateCompositeKey(credentials);
_credentials = credentials;
} }
public IEnumerable<EntryEntity> Search(string groupId, string text) public IEnumerable<EntryEntity> Search(string groupId, string text)

View File

@@ -85,10 +85,10 @@ namespace ModernKeePass
? exception.InnerException ? exception.InnerException
: exception; : exception;
_hockey.TrackException(realException);
if (realException is SaveException) if (realException is SaveException)
{ {
unhandledExceptionEventArgs.Handled = true; unhandledExceptionEventArgs.Handled = true;
_hockey.TrackException(realException);
await _dialog.ShowMessage(realException.Message, await _dialog.ShowMessage(realException.Message,
_resource.GetResourceValue("MessageDialogSaveErrorTitle"), _resource.GetResourceValue("MessageDialogSaveErrorTitle"),
_resource.GetResourceValue("MessageDialogSaveErrorButtonSaveAs"), _resource.GetResourceValue("MessageDialogSaveErrorButtonSaveAs"),
@@ -116,10 +116,6 @@ namespace ModernKeePass
} }
}); });
} }
else
{
await _dialog.ShowError(realException, realException.Message, "OK", () => {});
}
} }
/// <summary> /// <summary>

View File

@@ -504,4 +504,13 @@
<data name="MoveButton.Content" xml:space="preserve"> <data name="MoveButton.Content" xml:space="preserve">
<value>Move</value> <value>Move</value>
</data> </data>
<data name="SetCredentialsControlMatchingPasswords.Text" xml:space="preserve">
<value>Passwords must match</value>
</data>
<data name="SetCredentialsControlMissingKeyFile.Text" xml:space="preserve">
<value>Key file can't be empty</value>
</data>
<data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve">
<value>Confirm password</value>
</data>
</root> </root>

View File

@@ -507,4 +507,13 @@
<data name="MoveButton.Content" xml:space="preserve"> <data name="MoveButton.Content" xml:space="preserve">
<value>Déplacer</value> <value>Déplacer</value>
</data> </data>
<data name="SetCredentialsControlMatchingPasswords.Text" xml:space="preserve">
<value>Les mots de passe doivent être égaux</value>
</data>
<data name="SetCredentialsControlMissingKeyFile.Text" xml:space="preserve">
<value>Vous devez sélectionner un fichier de clé</value>
</data>
<data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve">
<value>Confirmer le mot de passe</value>
</data>
</root> </root>

View File

@@ -35,12 +35,14 @@
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</PasswordBox> </PasswordBox>
<CheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding HasKeyFile, Mode=TwoWay}" /> <CheckBox Grid.Row="1" Grid.Column="0" IsChecked="{Binding HasKeyFile, Mode=TwoWay}" />
<HyperlinkButton Grid.Row="1" Grid.Column="1" Margin="-15,0,0,0" <HyperlinkButton Grid.Row="1" Grid.Column="1" Margin="-15,0,0,0"
x:Name="HyperlinkButton" x:Name="HyperlinkButton"
Content="{Binding KeyFileText}" Content="{Binding KeyFileText}"
IsEnabled="{Binding HasKeyFile}" IsEnabled="{Binding HasKeyFile}"
Click="KeyFileButton_Click" /> Click="KeyFileButton_Click" />
<Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" <Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2"
x:Uid="OpenDatabaseControlButton" x:Uid="OpenDatabaseControlButton"
Visibility="{Binding IsOpening, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Visibility="{Binding IsOpening, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
@@ -49,7 +51,7 @@
<ProgressRing Grid.Column="0" Grid.Row="2" IsActive="{Binding IsOpening}" Visibility="{Binding IsOpening, Converter={StaticResource BooleanToVisibilityConverter}}" Foreground="{StaticResource MainColorBrush}" /> <ProgressRing Grid.Column="0" Grid.Row="2" IsActive="{Binding IsOpening}" Visibility="{Binding IsOpening, Converter={StaticResource BooleanToVisibilityConverter}}" Foreground="{StaticResource MainColorBrush}" />
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" Height="Auto" FontSize="14" FontWeight="Light" <TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" Height="Auto" FontSize="14" FontWeight="Light"
Text="{Binding Status}" Text="{Binding Status}"
Foreground="Red" Foreground="{StaticResource ErrorBrush}"
Visibility="{Binding Status, Converter={StaticResource EmptyStringToVisibilityConverter}}" /> Visibility="{Binding Status, Converter={StaticResource EmptyStringToVisibilityConverter}}" />
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>

View File

@@ -11,9 +11,13 @@
<UserControl.Resources> <UserControl.Resources>
<converters:ProgressBarLegalValuesConverter x:Key="ProgressBarLegalValuesConverter"/> <converters:ProgressBarLegalValuesConverter x:Key="ProgressBarLegalValuesConverter"/>
<converters:DoubleToSolidColorBrushConverter x:Key="DoubleToSolidColorBrushConverter"/> <converters:DoubleToSolidColorBrushConverter x:Key="DoubleToSolidColorBrushConverter"/>
<converters:EmptyStringToVisibilityConverter x:Key="EmptyStringToVisibilityConverter"/> <converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Grid x:Name="Grid" DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}"> <Grid x:Name="Grid" DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}">
<Grid.Resources>
<SolidColorBrush x:Key="ErrorBrush" Color="Red" />
<SolidColorBrush x:Key="ValidBrush" Color="Green" />
</Grid.Resources>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="50" /> <ColumnDefinition Width="50" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -21,29 +25,45 @@
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="45" /> <RowDefinition Height="45" />
<RowDefinition Height="45" /> <RowDefinition Height="45" />
<RowDefinition Height="40" /> <RowDefinition Height="Auto" />
<RowDefinition Height="40" /> <RowDefinition Height="40" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="40" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding HasPassword, Mode=TwoWay}" /> <CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding HasPassword, Mode=TwoWay}" />
<PasswordBox Grid.Row="0" Grid.Column="1" x:Uid="CompositeKeyPassword" Password="{Binding Password, Mode=TwoWay}" Height="30" IsPasswordRevealButtonEnabled="True" BorderBrush="{Binding StatusType, Converter={StaticResource DiscreteIntToSolidColorBrushConverter}}"> <PasswordBox Grid.Row="0" Grid.Column="1" Height="30"
x:Uid="CompositeKeyPassword"
x:Name="PasswordBox"
Password="{Binding Password, Mode=TwoWay}"
IsPasswordRevealButtonEnabled="True" >
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="GotFocus"> <core:EventTriggerBehavior EventName="GotFocus">
<core:ChangePropertyAction TargetObject="{Binding}" PropertyName="HasPassword" Value="True" /> <core:ChangePropertyAction TargetObject="{Binding}" PropertyName="HasPassword" Value="True" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</PasswordBox> </PasswordBox>
<PasswordBox Grid.Row="1" Grid.Column="1" x:Uid="CompositeKeyPassword" Password="{Binding ConfirmPassword, Mode=TwoWay}" Height="30" IsPasswordRevealButtonEnabled="True" BorderBrush="{Binding StatusType, Converter={StaticResource DiscreteIntToSolidColorBrushConverter}}" /> <PasswordBox Grid.Row="1" Grid.Column="1" Height="30"
x:Uid="CompositeKeyConfirmPassword"
x:Name="ConfirmPasswordBox"
Password="{Binding ConfirmPassword, Mode=TwoWay}"
IsPasswordRevealButtonEnabled="True" />
<ProgressBar Grid.Row="1" Grid.Column="1" <ProgressBar Grid.Row="1" Grid.Column="1"
Maximum="128" VerticalAlignment="Bottom" Maximum="128" VerticalAlignment="Bottom"
Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}"
Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToSolidColorBrushConverter}}"/> Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToSolidColorBrushConverter}}"/>
<CheckBox Grid.Row="2" Grid.Column="0" IsChecked="{Binding HasKeyFile, Mode=TwoWay}" /> <TextBlock Grid.Row="2" Grid.Column="1"
<HyperlinkButton Grid.Row="2" Grid.Column="1" Margin="-15,0,0,0" FontSize="14" FontWeight="Light"
x:Uid="SetCredentialsControlMatchingPasswords"
Foreground="{StaticResource ErrorBrush}"
Visibility="{Binding IsPasswordValid, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
<CheckBox Grid.Row="3" Grid.Column="0" IsChecked="{Binding HasKeyFile, Mode=TwoWay}" />
<HyperlinkButton Grid.Row="3" Grid.Column="1" Margin="-15,0,0,0"
x:Name="HyperlinkButton"
Content="{Binding KeyFileText}" Content="{Binding KeyFileText}"
IsEnabled="{Binding HasKeyFile}" IsEnabled="{Binding HasKeyFile}"
Click="KeyFileButton_Click" /> Click="KeyFileButton_Click" />
<HyperlinkButton Grid.Row="2" Grid.Column="1" HorizontalAlignment="Right" <HyperlinkButton Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right"
IsEnabled="{Binding HasKeyFile}" IsEnabled="{Binding HasKeyFile}"
Click="CreateKeyFileButton_Click"> Click="CreateKeyFileButton_Click">
<SymbolIcon Symbol="Add"> <SymbolIcon Symbol="Add">
@@ -52,12 +72,82 @@
</ToolTipService.ToolTip> </ToolTipService.ToolTip>
</SymbolIcon> </SymbolIcon>
</HyperlinkButton> </HyperlinkButton>
<Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="3" <TextBlock Grid.Row="4" Grid.Column="1" FontSize="14" FontWeight="Light"
x:Uid="SetCredentialsControlMissingKeyFile"
Foreground="{StaticResource ErrorBrush}"
Visibility="{Binding IsKeyFileValid, Converter={StaticResource InverseBooleanToVisibilityConverter}}" />
<Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="5"
Command="{Binding GenerateCredentialsCommand}" Command="{Binding GenerateCredentialsCommand}"
Content="{Binding ButtonLabel, ElementName=UserControl}" /> Content="{Binding ButtonLabel, ElementName=UserControl}" />
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="4" Height="Auto" FontSize="14" FontWeight="Light"
Text="{Binding Status}" <VisualStateManager.VisualStateGroups>
Foreground="{Binding StatusType, Converter={StaticResource DiscreteIntToSolidColorBrushConverter}}" <VisualStateGroup x:Name="CredentialStatus">
Visibility="{Binding Status, Converter={StaticResource EmptyStringToVisibilityConverter}}" /> <VisualState x:Name="PasswordError">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PasswordBox" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ErrorBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ConfirmPasswordBox" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ErrorBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PasswordValid">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PasswordBox" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ValidBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ConfirmPasswordBox" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ValidBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="KeyFileError">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HyperlinkButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ErrorBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="KeyFileValid">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HyperlinkButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ValidBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Initial">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PasswordBox" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource TextBoxBorderThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ConfirmPasswordBox" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource TextBoxBorderThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HyperlinkButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource HyperlinkForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsPasswordValid}" Value="False">
<core:GoToStateAction StateName="PasswordError"/>
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsPasswordValid}" Value="True">
<core:GoToStateAction StateName="PasswordValid"/>
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsKeyFileValid}" Value="False">
<core:GoToStateAction StateName="KeyFileError"/>
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsKeyFileValid}" Value="True">
<core:GoToStateAction StateName="KeyFileValid"/>
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsValid}" Value="True">
<core:GoToStateAction StateName="Initial"/>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -2,6 +2,6 @@
{ {
public class SaveErrorMessage public class SaveErrorMessage
{ {
public string Message { get; set; }
} }
} }

View File

@@ -19,6 +19,7 @@ namespace ModernKeePass.ViewModels
set set
{ {
Set(() => HasPassword, ref _hasPassword, value); Set(() => HasPassword, ref _hasPassword, value);
RaisePropertyChanged(nameof(IsPasswordValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
} }
@@ -30,23 +31,19 @@ namespace ModernKeePass.ViewModels
set set
{ {
Set(() => HasKeyFile, ref _hasKeyFile, value); Set(() => HasKeyFile, ref _hasKeyFile, value);
RaisePropertyChanged(nameof(IsKeyFileValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
} }
} }
public string Status
{
get { return _status; }
set { Set(() => Status, ref _status, value); }
}
public string Password public string Password
{ {
get { return _password; } get { return _password; }
set set
{ {
_password = value; _password = value;
RaisePropertyChanged(nameof(IsPasswordValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
RaisePropertyChanged(nameof(PasswordComplexityIndicator)); RaisePropertyChanged(nameof(PasswordComplexityIndicator));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
@@ -58,6 +55,7 @@ namespace ModernKeePass.ViewModels
set set
{ {
_confirmPassword = value; _confirmPassword = value;
RaisePropertyChanged(nameof(IsPasswordValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
} }
@@ -69,6 +67,7 @@ namespace ModernKeePass.ViewModels
set set
{ {
_keyFilePath = value; _keyFilePath = value;
RaisePropertyChanged(nameof(IsKeyFileValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
} }
@@ -80,26 +79,20 @@ namespace ModernKeePass.ViewModels
set { Set(() => KeyFileText, ref _keyFileText, value); } set { Set(() => KeyFileText, ref _keyFileText, value); }
} }
public string OpenButtonLabel
{
get { return _openButtonLabel; }
set { Set(() => OpenButtonLabel, ref _openButtonLabel, value); }
}
public double PasswordComplexityIndicator => _credentials.EstimatePasswordComplexity(Password); public double PasswordComplexityIndicator => _credentials.EstimatePasswordComplexity(Password);
public bool IsValid => HasPassword && Password == ConfirmPassword || HasKeyFile && !string.IsNullOrEmpty(KeyFilePath); public bool IsPasswordValid => HasPassword && Password == ConfirmPassword || !HasPassword;
public bool IsKeyFileValid => HasKeyFile && !string.IsNullOrEmpty(KeyFilePath) || !HasKeyFile;
public bool IsValid => (IsPasswordValid || IsKeyFileValid) && (HasPassword || HasKeyFile);
public RelayCommand GenerateCredentialsCommand{ get; } public RelayCommand GenerateCredentialsCommand{ get; }
private bool _hasPassword; private bool _hasPassword;
private bool _hasKeyFile; private bool _hasKeyFile;
private string _password = string.Empty; private string _password = string.Empty;
private string _confirmPassword; private string _confirmPassword = string.Empty;
private string _status;
private string _keyFilePath; private string _keyFilePath;
private string _keyFileText; private string _keyFileText;
private string _openButtonLabel;
public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource) public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource)
{ {