Added dirty behavior

Removed restore action (-> Move action wip)
Added additional check on DB size before auto saving
Code cleanup
This commit is contained in:
Geoffroy BONNEVILLE
2020-04-14 13:44:07 +02:00
parent d972b6cb5a
commit a2eba91a3b
34 changed files with 236 additions and 190 deletions

View File

@@ -75,6 +75,7 @@
<None Include="project.json" /> <None Include="project.json" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Common\Behaviors\SetDirtyBehavior.cs" />
<Compile Include="Common\Interfaces\ICryptographyClient.cs" /> <Compile Include="Common\Interfaces\ICryptographyClient.cs" />
<Compile Include="Common\Interfaces\IDatabaseProxy.cs" /> <Compile Include="Common\Interfaces\IDatabaseProxy.cs" />
<Compile Include="Common\Interfaces\IEntityVm.cs" /> <Compile Include="Common\Interfaces\IEntityVm.cs" />

View File

@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.CloseDatabase;
using ModernKeePass.Application.Database.Commands.SaveDatabase;
namespace ModernKeePass.Application.Common.Behaviors
{
public class SetDirtyBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
{
private readonly List<string> _excludedCommands = new List<string>
{nameof(SaveDatabaseCommand), nameof(CloseDatabaseCommand)};
private readonly IDatabaseProxy _database;
public SetDirtyBehavior(IDatabaseProxy database)
{
_database = database;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next)
{
var response = await next();
var queryName = typeof(TRequest).Name;
if (queryName.Contains("Command") && !_excludedCommands.Contains(queryName))
{
_database.IsDirty = true;
}
return response;
}
}
}

View File

@@ -7,7 +7,6 @@ namespace ModernKeePass.Application.Common.Interfaces
{ {
public interface IDatabaseProxy public interface IDatabaseProxy
{ {
string ZeroId { get; }
bool IsOpen { get; } bool IsOpen { get; }
string Name { get; } string Name { get; }
string RootGroupId { get; } string RootGroupId { get; }
@@ -17,6 +16,8 @@ namespace ModernKeePass.Application.Common.Interfaces
string Compression { get; set; } string Compression { get; set; }
bool IsRecycleBinEnabled { get; set; } bool IsRecycleBinEnabled { get; set; }
string FileAccessToken { get; set; } string FileAccessToken { get; set; }
int Size { get; set; }
bool IsDirty { get; set; }
Task Open(byte[] file, Credentials credentials); Task Open(byte[] file, Credentials credentials);
Task ReOpen(byte[] file); Task ReOpen(byte[] file);

View File

@@ -48,8 +48,10 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
_file.ReleaseFile(_database.FileAccessToken); _file.ReleaseFile(_database.FileAccessToken);
_database.FileAccessToken = message.FilePath; _database.FileAccessToken = message.FilePath;
} }
_database.IsDirty = false;
} }
catch (Exception exception) catch (ArgumentException exception)
{ {
throw new SaveException(exception); throw new SaveException(exception);
} }

View File

@@ -10,5 +10,7 @@
public string Compression { get; set; } public string Compression { get; set; }
public string CipherId { get; set; } public string CipherId { get; set; }
public string KeyDerivationId { get; set; } public string KeyDerivationId { get; set; }
public int Size { get; internal set; }
public bool IsDirty { get; internal set; }
} }
} }

View File

@@ -31,6 +31,8 @@ namespace ModernKeePass.Application.Database.Queries.GetDatabase
database.Compression = _databaseProxy.Compression; database.Compression = _databaseProxy.Compression;
database.CipherId = _databaseProxy.CipherId; database.CipherId = _databaseProxy.CipherId;
database.KeyDerivationId = _databaseProxy.KeyDerivationId; database.KeyDerivationId = _databaseProxy.KeyDerivationId;
database.Size = _databaseProxy.Size;
database.IsDirty = _databaseProxy.IsDirty;
} }
return database; return database;
} }

View File

@@ -33,6 +33,7 @@ namespace ModernKeePass.Application.Database.Queries.OpenDatabase
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.OpenBinaryFile(message.KeyFilePath): null, KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.OpenBinaryFile(message.KeyFilePath): null,
Password = message.Password Password = message.Password
}); });
_database.Size = file.Length;
_database.FileAccessToken = message.FilePath; _database.FileAccessToken = message.FilePath;
} }

View File

@@ -2,6 +2,7 @@
using FluentValidation; using FluentValidation;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Behaviors;
namespace ModernKeePass.Application namespace ModernKeePass.Application
{ {
@@ -11,6 +12,7 @@ namespace ModernKeePass.Application
{ {
var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly; var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly;
services.AddMediatR(assembly); services.AddMediatR(assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(SetDirtyBehavior<,>));
//services.AddValidatorsFromAssembly(assembly); //services.AddValidatorsFromAssembly(assembly);
return services; return services;

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MediatR; using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Exceptions; using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.DeleteEntry namespace ModernKeePass.Application.Group.Commands.DeleteEntry
@@ -24,7 +25,7 @@ namespace ModernKeePass.Application.Group.Commands.DeleteEntry
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(_database.ZeroId))) if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(Constants.EmptyId)))
{ {
_database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true); _database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true);
} }

View File

@@ -1,6 +1,7 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using MediatR; using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Exceptions; using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.DeleteGroup namespace ModernKeePass.Application.Group.Commands.DeleteGroup
@@ -24,7 +25,7 @@ namespace ModernKeePass.Application.Group.Commands.DeleteGroup
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(_database.ZeroId))) if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(Constants.EmptyId)))
{ {
_database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true); _database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true);
} }

View File

@@ -90,6 +90,7 @@
<Compile Include="Enums\ImportFormat.cs" /> <Compile Include="Enums\ImportFormat.cs" />
<Compile Include="Exceptions\DatabaseClosedException.cs" /> <Compile Include="Exceptions\DatabaseClosedException.cs" />
<Compile Include="Exceptions\DatabaseOpenException.cs" /> <Compile Include="Exceptions\DatabaseOpenException.cs" />
<Compile Include="Exceptions\DatabaseTooBigException.cs" />
<Compile Include="Exceptions\NavigationException.cs" /> <Compile Include="Exceptions\NavigationException.cs" />
<Compile Include="Exceptions\SaveException.cs" /> <Compile Include="Exceptions\SaveException.cs" />
<Compile Include="Interfaces\IDateTime.cs" /> <Compile Include="Interfaces\IDateTime.cs" />

View File

@@ -0,0 +1,6 @@
using System;
namespace ModernKeePass.Domain.Exceptions
{
public class DatabaseTooBigException: Exception { }
}

View File

@@ -26,8 +26,6 @@ namespace ModernKeePass.Infrastructure.KeePass
// Flag: Has Dispose already been called? // Flag: Has Dispose already been called?
private bool _disposed; private bool _disposed;
public string ZeroId => PwUuid.Zero.ToHexString();
// Main information // Main information
public bool IsOpen => (_pwDatabase?.IsOpen).GetValueOrDefault(); public bool IsOpen => (_pwDatabase?.IsOpen).GetValueOrDefault();
public string Name => _pwDatabase?.Name; public string Name => _pwDatabase?.Name;
@@ -35,6 +33,8 @@ namespace ModernKeePass.Infrastructure.KeePass
// TODO: find a correct place for this // TODO: find a correct place for this
public string FileAccessToken { get; set; } public string FileAccessToken { get; set; }
public int Size { get; set; }
public bool IsDirty { get; set; }
// Settings // Settings
public bool IsRecycleBinEnabled public bool IsRecycleBinEnabled

View File

@@ -211,16 +211,17 @@ namespace ModernKeePass
var deferral = e.SuspendingOperation.GetDeferral(); var deferral = e.SuspendingOperation.GetDeferral();
try try
{ {
if (_settings.GetSetting("SaveSuspend", true)) var database = await _mediator.Send(new GetDatabaseQuery());
if (database.IsOpen)
{ {
await _mediator.Send(new SaveDatabaseCommand()).ConfigureAwait(false); if (database.Size < Constants.File.OneMegaByte && database.IsDirty &&
} _settings.GetSetting(Constants.Settings.SaveSuspend, true))
{
await _mediator.Send(new SaveDatabaseCommand()).ConfigureAwait(false);
}
await _mediator.Send(new CloseDatabaseCommand()).ConfigureAwait(false); await _mediator.Send(new CloseDatabaseCommand()).ConfigureAwait(false);
} }
catch (DatabaseClosedException)
{
// Do nothing on purpose
} }
catch (Exception exception) catch (Exception exception)
{ {

View File

@@ -0,0 +1,15 @@
namespace ModernKeePass.Common
{
public class Constants
{
public class File
{
public static int OneMegaByte => 1048576;
}
public class Settings
{
public static string SaveSuspend => nameof(SaveSuspend);
}
}
}

View File

@@ -19,10 +19,6 @@ namespace ModernKeePass.Interfaces
/// </summary> /// </summary>
ICommand SaveCommand { get; } ICommand SaveCommand { get; }
/// <summary> /// <summary>
/// Restore ViewModel
/// </summary>
ICommand UndoDeleteCommand { get; }
/// <summary>
/// Move a entity to the destination group /// Move a entity to the destination group
/// </summary> /// </summary>
/// <param name="destination">The destination to move the entity to</param> /// <param name="destination">The destination to move the entity to</param>

View File

@@ -4,7 +4,9 @@
<!-- Common theme values --> <!-- Common theme values -->
<x:Double x:Key="MenuSize">60</x:Double> <x:Double x:Key="MenuSize">60</x:Double>
<x:Double x:Key="ExpandedMenuSize">300</x:Double>
<GridLength x:Key="MenuGridLength">60</GridLength> <GridLength x:Key="MenuGridLength">60</GridLength>
<GridLength x:Key="ExpandedMenuGridLength">300</GridLength>
<!-- Only available for Windows 10 --> <!-- Only available for Windows 10 -->
<!--<SolidColorBrush x:Key="MainColor" Color="{ThemeResource SystemAccentColor}" /> <!--<SolidColorBrush x:Key="MainColor" Color="{ThemeResource SystemAccentColor}" />
<SolidColorBrush x:Key="TextColor" Color="{ThemeResource SystemColorHighlightTextColor}" />--> <SolidColorBrush x:Key="TextColor" Color="{ThemeResource SystemColorHighlightTextColor}" />-->

View File

@@ -432,11 +432,11 @@
<data name="TopMenuMoreButton.Content" xml:space="preserve"> <data name="TopMenuMoreButton.Content" xml:space="preserve">
<value>More</value> <value>More</value>
</data> </data>
<data name="TopMenuRestoreButton.Content" xml:space="preserve"> <data name="TopMenuMoveButton.Content" xml:space="preserve">
<value>Restore</value> <value>Move</value>
</data> </data>
<data name="TopMenuRestoreFlyout.Text" xml:space="preserve"> <data name="TopMenuMoveFlyout.Text" xml:space="preserve">
<value>Restore</value> <value>Move</value>
</data> </data>
<data name="TopMenuSaveButton.Content" xml:space="preserve"> <data name="TopMenuSaveButton.Content" xml:space="preserve">
<value>Save</value> <value>Save</value>

View File

@@ -432,11 +432,11 @@
<data name="TopMenuMoreButton.Content" xml:space="preserve"> <data name="TopMenuMoreButton.Content" xml:space="preserve">
<value>Plus</value> <value>Plus</value>
</data> </data>
<data name="TopMenuRestoreButton.Content" xml:space="preserve"> <data name="TopMenuMoveButton.Content" xml:space="preserve">
<value>Restaurer</value> <value>Déplacer</value>
</data> </data>
<data name="TopMenuRestoreFlyout.Text" xml:space="preserve"> <data name="TopMenuMoveFlyout.Text" xml:space="preserve">
<value>Restaurer</value> <value>Déplacer</value>
</data> </data>
<data name="TopMenuSaveButton.Content" xml:space="preserve"> <data name="TopMenuSaveButton.Content" xml:space="preserve">
<value>Sauvegarder</value> <value>Sauvegarder</value>

View File

@@ -20,14 +20,13 @@ using ModernKeePass.Application.Security.Commands.GeneratePassword;
using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity; using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Interfaces;
using ModernKeePass.Interfaces; using ModernKeePass.Interfaces;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.AOP; using ModernKeePass.Domain.AOP;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class EntryDetailVm : NotifyPropertyChangedBase, IVmEntity, ISelectableModel public class EntryDetailVm : NotifyPropertyChangedBase, IVmEntity
{ {
public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password); public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password);
public bool HasExpired => HasExpirationDate && ExpiryDate < DateTime.Now; public bool HasExpired => HasExpirationDate && ExpiryDate < DateTime.Now;
@@ -65,7 +64,7 @@ namespace ModernKeePass.ViewModels
get { return _entry.Title; } get { return _entry.Title; }
set set
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Title), FieldValue = value}).Wait(); SetFieldValue(nameof(Title), value).Wait();
_entry.Title = value; _entry.Title = value;
} }
} }
@@ -81,7 +80,7 @@ namespace ModernKeePass.ViewModels
get { return _entry.Password; } get { return _entry.Password; }
set set
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Password), FieldValue = value }).Wait(); SetFieldValue(nameof(Password), value).Wait();
_entry.Password = value; _entry.Password = value;
OnPropertyChanged(); OnPropertyChanged();
OnPropertyChanged(nameof(PasswordComplexityIndicator)); OnPropertyChanged(nameof(PasswordComplexityIndicator));
@@ -93,7 +92,7 @@ namespace ModernKeePass.ViewModels
get { return _entry.Url?.ToString(); } get { return _entry.Url?.ToString(); }
set set
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Url), FieldValue = value }).Wait(); SetFieldValue(nameof(Url), value).Wait();
_entry.Url = new Uri(value); _entry.Url = new Uri(value);
} }
} }
@@ -103,7 +102,7 @@ namespace ModernKeePass.ViewModels
get { return _entry.Notes; } get { return _entry.Notes; }
set set
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Notes), FieldValue = value }).Wait(); SetFieldValue(nameof(Notes), value).Wait();
_entry.Notes = value; _entry.Notes = value;
} }
} }
@@ -117,7 +116,7 @@ namespace ModernKeePass.ViewModels
} }
set set
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Icon), FieldValue = value }).Wait(); SetFieldValue(nameof(Icon), value).Wait();
_entry.Icon = (Icon)value; _entry.Icon = (Icon)value;
} }
} }
@@ -128,7 +127,8 @@ namespace ModernKeePass.ViewModels
set set
{ {
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = "ExpirationDate", FieldValue = value.Date }).Wait();
SetFieldValue("ExpirationDate", value).Wait();
_entry.ExpirationDate = value.Date; _entry.ExpirationDate = value.Date;
} }
} }
@@ -139,7 +139,8 @@ namespace ModernKeePass.ViewModels
set set
{ {
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = "ExpirationDate", FieldValue = ExpiryDate.Date.Add(value) }).Wait();
SetFieldValue("ExpirationDate", value).Wait();
_entry.ExpirationDate = _entry.ExpirationDate.Date.Add(value); _entry.ExpirationDate = _entry.ExpirationDate.Date.Add(value);
} }
} }
@@ -149,7 +150,7 @@ namespace ModernKeePass.ViewModels
get { return _entry.HasExpirationDate; } get { return _entry.HasExpirationDate; }
set set
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(HasExpirationDate), FieldValue = value }).Wait(); SetFieldValue(nameof(HasExpirationDate), value).Wait();
_entry.HasExpirationDate = value; _entry.HasExpirationDate = value;
OnPropertyChanged(); OnPropertyChanged();
} }
@@ -162,7 +163,7 @@ namespace ModernKeePass.ViewModels
{ {
if (value != null) if (value != null)
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(BackgroundColor), FieldValue = value }).Wait(); SetFieldValue(nameof(BackgroundColor), value).Wait();
_entry.BackgroundColor = (Color)value; _entry.BackgroundColor = (Color)value;
} }
} }
@@ -175,12 +176,12 @@ namespace ModernKeePass.ViewModels
{ {
if (value != null) if (value != null)
{ {
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(ForegroundColor), FieldValue = value }).Wait(); SetFieldValue(nameof(ForegroundColor), value).Wait();
_entry.ForegroundColor = (Color)value; _entry.ForegroundColor = (Color)value;
} }
} }
} }
public IEnumerable<EntryVm> History => _history; public IEnumerable<EntryVm> History { get; }
public bool IsEditMode public bool IsEditMode
{ {
@@ -200,16 +201,14 @@ namespace ModernKeePass.ViewModels
set { SetProperty(ref _isRevealPassword, value); } set { SetProperty(ref _isRevealPassword, value); }
} }
public bool CanRestore => _entry.ParentGroupId == _database.RecycleBinId;
public ICommand SaveCommand { get; } public ICommand SaveCommand { get; }
public ICommand GeneratePasswordCommand { get; } public ICommand GeneratePasswordCommand { get; }
public ICommand UndoDeleteCommand { get; } public ICommand MoveCommand { get; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly DatabaseVm _database;
private readonly GroupVm _parent; private readonly GroupVm _parent;
private readonly IEnumerable<EntryVm> _history;
private EntryVm _entry; private EntryVm _entry;
private bool _isEditMode; private bool _isEditMode;
private bool _isRevealPassword; private bool _isRevealPassword;
@@ -224,17 +223,16 @@ namespace ModernKeePass.ViewModels
public EntryDetailVm(string entryId, IMediator mediator, bool isNewEntry = false) public EntryDetailVm(string entryId, IMediator mediator, bool isNewEntry = false)
{ {
_mediator = mediator; _mediator = mediator;
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
_entry = _mediator.Send(new GetEntryQuery {Id = entryId}).GetAwaiter().GetResult(); _entry = _mediator.Send(new GetEntryQuery {Id = entryId}).GetAwaiter().GetResult();
_parent = _mediator.Send(new GetGroupQuery {Id = _entry.ParentGroupId}).GetAwaiter().GetResult(); _parent = _mediator.Send(new GetGroupQuery {Id = _entry.ParentGroupId}).GetAwaiter().GetResult();
_history = _entry.History; History = _entry.History;
_isEditMode = isNewEntry; _isEditMode = isNewEntry;
if (isNewEntry) GeneratePassword().GetAwaiter().GetResult(); if (isNewEntry) GeneratePassword().GetAwaiter().GetResult();
IsSelected = true; IsSelected = true;
SaveCommand = new RelayCommand(async () => await _mediator.Send(new SaveDatabaseCommand())); SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword()); GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
UndoDeleteCommand = new RelayCommand(async () => await Move(_parent), () => _parent != null); MoveCommand = new RelayCommand(async () => await Move(_parent), () => _parent != null);
} }
public async Task GeneratePassword() public async Task GeneratePassword()
@@ -269,6 +267,7 @@ namespace ModernKeePass.ViewModels
public async Task SetFieldValue(string fieldName, object value) public async Task SetFieldValue(string fieldName, object value)
{ {
await _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = fieldName, FieldValue = value }); await _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = fieldName, FieldValue = value });
((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
} }
internal void SetEntry(EntryVm entry, int index) internal void SetEntry(EntryVm entry, int index)
@@ -276,5 +275,11 @@ namespace ModernKeePass.ViewModels
_entry = entry; _entry = entry;
IsSelected = index == 0; IsSelected = index == 0;
} }
private async Task SaveChanges()
{
await _mediator.Send(new SaveDatabaseCommand());
((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
}
} }
} }

View File

@@ -25,12 +25,11 @@ using ModernKeePass.Application.Group.Queries.GetGroup;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Domain.AOP; using ModernKeePass.Domain.AOP;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Interfaces;
using ModernKeePass.Interfaces; using ModernKeePass.Interfaces;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class GroupDetailVm : NotifyPropertyChangedBase, IVmEntity, ISelectableModel public class GroupDetailVm : NotifyPropertyChangedBase, IVmEntity
{ {
public ObservableCollection<EntryVm> Entries { get; } public ObservableCollection<EntryVm> Entries { get; }
@@ -51,24 +50,7 @@ namespace ModernKeePass.ViewModels
} }
} }
public bool IsNotRoot => _database.RootGroupId != _group.Id; public bool IsNotRoot => Database.RootGroupId != _group.Id;
public bool ShowRestore => IsNotRoot && _database.RecycleBinId != _group.Id;
/// <summary>
/// Is the Group the database Recycle Bin?
/// </summary>
public bool IsSelected
{
get
{
return _database.IsRecycleBinEnabled && _database.RecycleBinId == _group.Id;
}
set
{
if (value && _group != null) _database.RecycleBinId = _group.Id;
}
}
public IOrderedEnumerable<IGrouping<char, EntryVm>> EntriesZoomedOut => from e in Entries public IOrderedEnumerable<IGrouping<char, EntryVm>> EntriesZoomedOut => from e in Entries
group e by e.Title.ToUpper().FirstOrDefault() into grp group e by e.Title.ToUpper().FirstOrDefault() into grp
@@ -111,10 +93,11 @@ namespace ModernKeePass.ViewModels
public ICommand SaveCommand { get; } public ICommand SaveCommand { get; }
public ICommand SortEntriesCommand { get; } public ICommand SortEntriesCommand { get; }
public ICommand SortGroupsCommand { get; } public ICommand SortGroupsCommand { get; }
public ICommand UndoDeleteCommand { get; } public ICommand MoveCommand { get; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly DatabaseVm _database;
private readonly GroupVm _group; private readonly GroupVm _group;
private readonly GroupVm _parent; private readonly GroupVm _parent;
private bool _isEditMode; private bool _isEditMode;
@@ -129,7 +112,6 @@ namespace ModernKeePass.ViewModels
public GroupDetailVm(string groupId, IMediator mediator, bool isEditMode = false) public GroupDetailVm(string groupId, IMediator mediator, bool isEditMode = false)
{ {
_mediator = mediator; _mediator = mediator;
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
_group = _mediator.Send(new GetGroupQuery { Id = groupId }).GetAwaiter().GetResult(); _group = _mediator.Send(new GetGroupQuery { Id = groupId }).GetAwaiter().GetResult();
if (!string.IsNullOrEmpty(_group.ParentGroupId)) if (!string.IsNullOrEmpty(_group.ParentGroupId))
{ {
@@ -138,18 +120,43 @@ namespace ModernKeePass.ViewModels
} }
_isEditMode = isEditMode; _isEditMode = isEditMode;
SaveCommand = new RelayCommand(async () => await _mediator.Send(new SaveDatabaseCommand())); SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
SortEntriesCommand = new RelayCommand(async () => SortEntriesCommand = new RelayCommand(async () => await SortEntriesAsync(), () => IsEditMode);
await SortEntriesAsync(), () => IsEditMode); SortGroupsCommand = new RelayCommand(async () => await SortGroupsAsync(), () => IsEditMode);
SortGroupsCommand = new RelayCommand(async () => MoveCommand = new RelayCommand(async () => await Move(_parent), () => IsNotRoot);
await SortGroupsAsync(), () => IsEditMode);
UndoDeleteCommand = new RelayCommand(async () => await Move(_parent), () => _parent != null);
Entries = new ObservableCollection<EntryVm>(_group.Entries); Entries = new ObservableCollection<EntryVm>(_group.Entries);
Entries.CollectionChanged += Entries_CollectionChanged; Entries.CollectionChanged += Entries_CollectionChanged;
Groups = new ObservableCollection<GroupVm>(_group.SubGroups); Groups = new ObservableCollection<GroupVm>(_group.SubGroups);
} }
public async Task<string> AddNewGroup(string name = "")
{
return (await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group})).Id;
}
public async Task<string> AddNewEntry()
{
return (await _mediator.Send(new CreateEntryCommand { ParentGroup = _group })).Id;
}
public async Task MarkForDelete(string recycleBinTitle)
{
await _mediator.Send(new DeleteGroupCommand { GroupId = _group.Id, ParentGroupId = _group.ParentGroupId, RecycleBinName = recycleBinTitle });
}
public async Task Move(GroupVm destination)
{
await _mediator.Send(new AddGroupCommand {ParentGroup = destination, Group = _group });
await _mediator.Send(new RemoveGroupCommand {ParentGroup = _parent, Group = _group });
}
private async Task SaveChanges()
{
await _mediator.Send(new SaveDatabaseCommand());
((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
}
private async void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) private async void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{ {
switch (e.Action) switch (e.Action)
@@ -170,41 +177,21 @@ namespace ModernKeePass.ViewModels
} }
break; break;
} }
} ((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
public async Task<string> AddNewGroup(string name = "")
{
return (await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group})).Id;
}
public async Task<string> AddNewEntry()
{
return (await _mediator.Send(new CreateEntryCommand { ParentGroup = _group })).Id;
}
public async Task MarkForDelete(string recycleBinTitle)
{
await _mediator.Send(new DeleteGroupCommand { GroupId = _group.Id, ParentGroupId = _group.ParentGroupId, RecycleBinName = recycleBinTitle });
((RelayCommand)UndoDeleteCommand).RaiseCanExecuteChanged();
}
public async Task Move(GroupVm destination)
{
await _mediator.Send(new AddGroupCommand {ParentGroup = destination, Group = _group });
await _mediator.Send(new RemoveGroupCommand {ParentGroup = _parent, Group = _group });
} }
private async Task SortEntriesAsync() private async Task SortEntriesAsync()
{ {
await _mediator.Send(new SortEntriesCommand {Group = _group}); await _mediator.Send(new SortEntriesCommand {Group = _group});
OnPropertyChanged(nameof(Entries)); OnPropertyChanged(nameof(Entries));
((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
} }
private async Task SortGroupsAsync() private async Task SortGroupsAsync()
{ {
await _mediator.Send(new SortGroupsCommand {Group = _group}); await _mediator.Send(new SortGroupsCommand {Group = _group});
OnPropertyChanged(nameof(Groups)); OnPropertyChanged(nameof(Groups));
((RelayCommand)SaveCommand).RaiseCanExecuteChanged();
} }
} }
} }

View File

@@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Common;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
@@ -17,8 +18,8 @@ namespace ModernKeePass.ViewModels
public bool IsSaveSuspend public bool IsSaveSuspend
{ {
get { return _settings.GetSetting("SaveSuspend", true); } get { return _settings.GetSetting(Constants.Settings.SaveSuspend, true); }
set { _settings.PutSetting("SaveSuspend", value); } set { _settings.PutSetting(Constants.Settings.SaveSuspend, value); }
} }
} }
} }

View File

@@ -390,7 +390,7 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView" ItemsSource="{Binding History}" ResizeTarget="{Binding ElementName=LeftListViewColumn}" SelectionChanged="HamburgerMenuUserControl_OnSelectionChanged" /> <userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView" ItemsSource="{Binding History}" ResizeTarget="{Binding ElementName=LeftListViewColumn}" SelectionChanged="HamburgerMenuUserControl_OnSelectionChanged" />
<Grid x:Name="StackPanel" Grid.Column="1"> <Grid Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20"> <StackPanel Margin="20,0,0,20">
<StackPanel.Resources> <StackPanel.Resources>
@@ -487,7 +487,7 @@
Style="{StaticResource NoBorderButtonStyle}"> Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" /> <SymbolIcon Symbol="Back" />
</Button> </Button>
<Grid Grid.Column="1" x:Name="TopGrid"> <Grid Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="60" /> <ColumnDefinition Width="60" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
@@ -524,11 +524,9 @@
</Grid> </Grid>
<userControls:TopMenuUserControl <userControls:TopMenuUserControl
x:Name="TopMenu" Grid.Column="2" x:Name="TopMenu" Grid.Column="2"
RestoreButtonVisibility="{Binding CanRestore, Converter={StaticResource BooleanToVisibilityConverter}}"
DeleteButtonVisibility="{Binding IsSelected, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
SaveCommand="{Binding SaveCommand}" SaveCommand="{Binding SaveCommand}"
RestoreCommand="{Binding UndoDeleteCommand}"> MoveCommand="{Binding MoveCommand}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="EditButtonClick"> <core:EventTriggerBehavior EventName="EditButtonClick">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" /> <actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
@@ -536,9 +534,9 @@
<core:EventTriggerBehavior EventName="DeleteButtonClick"> <core:EventTriggerBehavior EventName="DeleteButtonClick">
<actions:DeleteEntityAction Entity="{Binding}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" /> <actions:DeleteEntityAction Entity="{Binding}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RestoreButtonClick"> <core:EventTriggerBehavior EventName="MoveButtonClick">
<core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" /> <core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
<actions:ToastAction x:Uid="RestoreEntryCommand" Title="{Binding Title}" /> <!--<actions:ToastAction x:Uid="RestoreEntryCommand" Title="{Binding Title}" />-->
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl> </userControls:TopMenuUserControl>

View File

@@ -45,10 +45,10 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}" x:Name="LeftListViewColumn" /> <ColumnDefinition Width="{StaticResource ExpandedMenuGridLength}" x:Name="LeftListViewColumn" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl x:Uid="GroupsLeftListView" ItemsSource="{Binding Groups}" SelectionChanged="groups_SelectionChanged" ButtonClicked="CreateGroup_ButtonClick" ResizeTarget="{Binding ElementName=LeftListViewColumn}" IsButtonVisible="{Binding IsSelected, Converter={StaticResource InverseBooleanToVisibilityConverter}}" IsOpen="true" /> <userControls:HamburgerMenuUserControl x:Uid="GroupsLeftListView" ItemsSource="{Binding Groups}" SelectionChanged="groups_SelectionChanged" ButtonClicked="CreateGroup_ButtonClick" ResizeTarget="{Binding ElementName=LeftListViewColumn}" IsOpen="True" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -61,7 +61,7 @@
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" x:Uid="ReorderEntriesLabel" Margin="10,10,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" /> <TextBlock Grid.Column="0" Grid.Row="0" x:Uid="ReorderEntriesLabel" Margin="10,10,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" />
<!--<TextBlock Grid.Column="1" Grid.Row="0" x:Uid="EntrySymbol" Margin="40,20,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" />--> <!--<TextBlock Grid.Column="1" Grid.Row="0" x:Uid="EntrySymbol" Margin="40,20,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" />-->
<HyperlinkButton Grid.Column="2" Grid.Row="0" VerticalAlignment="Top" Click="CreateEntry_ButtonClick" Visibility="{Binding IsSelected, Converter={StaticResource InverseBooleanToVisibilityConverter}}" HorizontalAlignment="Right" Foreground="{StaticResource MainColor}" Style="{StaticResource MainColorHyperlinkButton}"> <HyperlinkButton Grid.Column="2" Grid.Row="0" VerticalAlignment="Top" Click="CreateEntry_ButtonClick" HorizontalAlignment="Right" Foreground="{StaticResource MainColor}" Style="{StaticResource MainColorHyperlinkButton}">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Add"> <SymbolIcon Symbol="Add">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
@@ -233,13 +233,12 @@
<userControls:BreadCrumbUserControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding BreadCrumb}" Margin="5,-5,0,0" /> <userControls:BreadCrumbUserControl Grid.Column="1" Grid.Row="1" ItemsSource="{Binding BreadCrumb}" Margin="5,-5,0,0" />
</Grid> </Grid>
<userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="2" <userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="2"
RestoreButtonVisibility="{Binding ShowRestore, Converter={StaticResource BooleanToVisibilityConverter}}"
DeleteButtonVisibility="{Binding IsSelected, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
IsMoveButtonEnabled="{Binding IsNotRoot}"
IsDeleteButtonEnabled="{Binding IsNotRoot}" IsDeleteButtonEnabled="{Binding IsNotRoot}"
SaveCommand="{Binding SaveCommand}" SaveCommand="{Binding SaveCommand}"
RestoreCommand="{Binding UndoDeleteCommand}" MoveCommand="{Binding MoveCommand}"
SortEntriesCommand="{Binding SortEntriesCommand}" SortEntriesCommand="{Binding SortEntriesCommand}"
SortGroupsCommand="{Binding SortGroupsCommand}"> SortGroupsCommand="{Binding SortGroupsCommand}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
@@ -249,9 +248,9 @@
<core:EventTriggerBehavior EventName="DeleteButtonClick"> <core:EventTriggerBehavior EventName="DeleteButtonClick">
<actions:DeleteEntityAction Entity="{Binding}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" /> <actions:DeleteEntityAction Entity="{Binding}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RestoreButtonClick"> <core:EventTriggerBehavior EventName="MoveButtonClick">
<core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" /> <core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
<actions:ToastAction x:Uid="RestoreGroupCommand" Title="{Binding Title}" /> <!--<actions:ToastAction x:Uid="RestoreGroupCommand" Title="{Binding Title}" />-->
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl> </userControls:TopMenuUserControl>

View File

@@ -21,7 +21,7 @@ namespace ModernKeePass.Views.UserControls
public static readonly DependencyProperty ItemsSourceProperty = public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register( DependencyProperty.Register(
"ItemsSource", nameof(ItemsSource),
typeof(IEnumerable<IEntityVm>), typeof(IEnumerable<IEntityVm>),
typeof(BreadCrumbUserControl), typeof(BreadCrumbUserControl),
new PropertyMetadata(new Stack<IEntityVm>(), (o, args) => { })); new PropertyMetadata(new Stack<IEntityVm>(), (o, args) => { }));

View File

@@ -26,7 +26,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SelectedColorProperty = public static readonly DependencyProperty SelectedColorProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SelectedColor", nameof(SelectedColor),
typeof(SolidColorBrush), typeof(SolidColorBrush),
typeof(ColorPickerUserControl), typeof(ColorPickerUserControl),
new PropertyMetadata(new SolidColorBrush(), (o, args) => { })); new PropertyMetadata(new SolidColorBrush(), (o, args) => { }));

View File

@@ -34,7 +34,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty CreateNewProperty = public static readonly DependencyProperty CreateNewProperty =
DependencyProperty.Register( DependencyProperty.Register(
"CreateNew", nameof(CreateNew),
typeof(bool), typeof(bool),
typeof(CompositeKeyUserControl), typeof(CompositeKeyUserControl),
new PropertyMetadata(false, (o, args) => { })); new PropertyMetadata(false, (o, args) => { }));
@@ -46,7 +46,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty UpdateKeyProperty = public static readonly DependencyProperty UpdateKeyProperty =
DependencyProperty.Register( DependencyProperty.Register(
"UpdateKey", nameof(UpdateKey),
typeof(bool), typeof(bool),
typeof(CompositeKeyUserControl), typeof(CompositeKeyUserControl),
new PropertyMetadata(false, (o, args) => { })); new PropertyMetadata(false, (o, args) => { }));
@@ -58,7 +58,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty ButtonLabelProperty = public static readonly DependencyProperty ButtonLabelProperty =
DependencyProperty.Register( DependencyProperty.Register(
"ButtonLabel", nameof(ButtonLabel),
typeof(string), typeof(string),
typeof(CompositeKeyUserControl), typeof(CompositeKeyUserControl),
new PropertyMetadata("OK", (o, args) => { })); new PropertyMetadata("OK", (o, args) => { }));
@@ -70,7 +70,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty DatabaseFilePathProperty = public static readonly DependencyProperty DatabaseFilePathProperty =
DependencyProperty.Register( DependencyProperty.Register(
"DatabaseFilePath", nameof(DatabaseFilePath),
typeof(string), typeof(string),
typeof(CompositeKeyUserControl), typeof(CompositeKeyUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));

View File

@@ -23,7 +23,6 @@
Foreground="{ThemeResource DefaultTextForegroundThemeBrush}" Foreground="{ThemeResource DefaultTextForegroundThemeBrush}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}"> ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<ListView.Resources> <ListView.Resources>
<x:Double x:Key="HamburgerMenuSize">300</x:Double>
<DataTemplate x:Name="IsSpecial"> <DataTemplate x:Name="IsSpecial">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=48}" Margin="7,15,0,15"> <SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=48}" Margin="7,15,0,15">
@@ -51,13 +50,13 @@
<ListView.HeaderTemplate> <ListView.HeaderTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding Path={Binding IsOpen, ElementName=UserControl}}"> <ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip Content="{Binding HeaderLabel, ElementName=UserControl}" /> <ToolTip Content="{Binding HeaderLabel, ElementName=UserControl}" />
</ToolTipService.ToolTip> </ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Checked"> <core:EventTriggerBehavior EventName="Checked">
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource HamburgerMenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/> <core:ChangePropertyAction PropertyName="Width" Value="{StaticResource ExpandedMenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="Unchecked"> <core:EventTriggerBehavior EventName="Unchecked">
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource MenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/> <core:ChangePropertyAction PropertyName="Width" Value="{StaticResource MenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
@@ -72,7 +71,7 @@
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<Border BorderBrush="White" BorderThickness="0,0,0,1" /> <Border BorderBrush="White" BorderThickness="0,0,0,1" />
<Button Padding="0" Height="{StaticResource MenuSize}" Margin="0" Visibility="{Binding IsButtonVisible, ElementName=UserControl}" Style="{StaticResource NoBorderButtonStyle}" Background="Transparent" BorderThickness="0" Width="{StaticResource HamburgerMenuSize}" HorizontalContentAlignment="Left" Click="ButtonBase_OnClick"> <Button Padding="0" Height="{StaticResource MenuSize}" Margin="0" Visibility="{Binding IsButtonVisible, ElementName=UserControl}" Style="{StaticResource NoBorderButtonStyle}" Background="Transparent" BorderThickness="0" Width="{StaticResource ExpandedMenuSize}" HorizontalContentAlignment="Left" Click="ButtonBase_OnClick">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0"> <StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<SymbolIcon Symbol="Add"> <SymbolIcon Symbol="Add">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
@@ -82,7 +81,7 @@
<TextBlock Text="{Binding ButtonLabel, ElementName=UserControl}" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" /> <TextBlock Text="{Binding ButtonLabel, ElementName=UserControl}" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" />
</StackPanel> </StackPanel>
</Button> </Button>
<Button Padding="0" Height="{StaticResource MenuSize}" Margin="0" Style="{StaticResource NoBorderButtonStyle}" Background="Transparent" BorderThickness="0" Width="{StaticResource HamburgerMenuSize}" HorizontalContentAlignment="Left"> <Button Padding="0" Height="{StaticResource MenuSize}" Margin="0" Style="{StaticResource NoBorderButtonStyle}" Background="Transparent" BorderThickness="0" Width="{StaticResource ExpandedMenuSize}" HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0"> <StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<SymbolIcon Symbol="Home"> <SymbolIcon Symbol="Home">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
@@ -97,7 +96,7 @@
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</Button> </Button>
<Button Padding="0" Height="{StaticResource MenuSize}" Margin="0" Style="{StaticResource NoBorderButtonStyle}" Background="Transparent" BorderThickness="0" Width="{StaticResource HamburgerMenuSize}" HorizontalContentAlignment="Left"> <Button Padding="0" Height="{StaticResource MenuSize}" Margin="0" Style="{StaticResource NoBorderButtonStyle}" Background="Transparent" BorderThickness="0" Width="{StaticResource ExpandedMenuSize}" HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0"> <StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<SymbolIcon Symbol="Setting"> <SymbolIcon Symbol="Setting">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>

View File

@@ -22,7 +22,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty HeaderLabelProperty = public static readonly DependencyProperty HeaderLabelProperty =
DependencyProperty.Register( DependencyProperty.Register(
"HeaderLabel", nameof(HeaderLabel),
typeof(string), typeof(string),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata("Header", (o, args) => { })); new PropertyMetadata("Header", (o, args) => { }));
@@ -34,7 +34,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty ButtonLabelProperty = public static readonly DependencyProperty ButtonLabelProperty =
DependencyProperty.Register( DependencyProperty.Register(
"ButtonLabel", nameof(ButtonLabel),
typeof(string), typeof(string),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata("Button", (o, args) => { })); new PropertyMetadata("Button", (o, args) => { }));
@@ -46,7 +46,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty DisplayMemberPathProperty = public static readonly DependencyProperty DisplayMemberPathProperty =
DependencyProperty.Register( DependencyProperty.Register(
"DisplayMemberPath", nameof(DisplayMemberPath),
typeof(string), typeof(string),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata("Title", (o, args) => { })); new PropertyMetadata("Title", (o, args) => { }));
@@ -58,7 +58,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty ResizeTargetProperty = public static readonly DependencyProperty ResizeTargetProperty =
DependencyProperty.Register( DependencyProperty.Register(
"ResizeTarget", nameof(ResizeTarget),
typeof(object), typeof(object),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
@@ -70,7 +70,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty IsButtonVisibleProperty = public static readonly DependencyProperty IsButtonVisibleProperty =
DependencyProperty.Register( DependencyProperty.Register(
"IsButtonVisible", nameof(IsButtonVisible),
typeof(Visibility), typeof(Visibility),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(Visibility.Collapsed, (o, args) => { })); new PropertyMetadata(Visibility.Collapsed, (o, args) => { }));
@@ -83,7 +83,7 @@ namespace ModernKeePass.Views.UserControls
public static readonly DependencyProperty ItemsSourceProperty = public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register( DependencyProperty.Register(
"ItemsSource", nameof(ItemsSource),
typeof(IEnumerable<IEntityVm>), typeof(IEnumerable<IEntityVm>),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(new List<IEntityVm>(), (o, args) => { })); new PropertyMetadata(new List<IEntityVm>(), (o, args) => { }));
@@ -95,7 +95,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SelectedItemProperty = public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SelectedItem", nameof(SelectedItem),
typeof(object), typeof(object),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
@@ -107,10 +107,10 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty IsOpenProperty = public static readonly DependencyProperty IsOpenProperty =
DependencyProperty.Register( DependencyProperty.Register(
"IsOpen", nameof(IsOpen),
typeof(bool), typeof(bool),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(false, (o, args) => { })); new PropertyMetadata(true, (o, args) => { }));
public event EventHandler<SelectionChangedEventArgs> SelectionChanged; public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)

View File

@@ -19,7 +19,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SelectedSymbolProperty = public static readonly DependencyProperty SelectedSymbolProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SelectedSymbol", nameof(SelectedSymbol),
typeof(Symbol), typeof(Symbol),
typeof(SymbolPickerUserControl), typeof(SymbolPickerUserControl),
new PropertyMetadata(Symbol.Stop, (o, args) => { })); new PropertyMetadata(Symbol.Stop, (o, args) => { }));

View File

@@ -42,10 +42,10 @@
</VisualStateGroup> </VisualStateGroup>
</VisualStateManager.VisualStateGroups> </VisualStateManager.VisualStateGroups>
<StackPanel x:Name="OverflowButtons" Orientation="Horizontal"> <StackPanel x:Name="OverflowButtons" Orientation="Horizontal">
<Button Command="{Binding RestoreCommand, ElementName=UserControl}" Visibility="{Binding RestoreButtonVisibility, ElementName=UserControl}" Click="RestoreButton_Click" Style="{StaticResource MenuButtonStyle}"> <Button Command="{Binding MoveCommand, ElementName=UserControl}" IsEnabled="{Binding IsMoveButtonEnabled, ElementName=UserControl}" Click="MoveButton_Click" Style="{StaticResource MenuButtonStyle}">
<SymbolIcon Symbol="Undo"> <SymbolIcon Symbol="Undo">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip x:Uid="TopMenuRestoreButton" /> <ToolTip x:Uid="TopMenuMoveButton" />
</ToolTipService.ToolTip> </ToolTipService.ToolTip>
</SymbolIcon> </SymbolIcon>
</Button> </Button>
@@ -76,7 +76,7 @@
</ToolTipService.ToolTip> </ToolTipService.ToolTip>
</SymbolIcon> </SymbolIcon>
</ToggleButton> </ToggleButton>
<Button Command="{Binding DeleteCommand, ElementName=UserControl}" IsEnabled="{Binding IsDeleteButtonEnabled, ElementName=UserControl}" Visibility="{Binding DeleteButtonVisibility, ElementName=UserControl}" Click="DeleteButton_Click" Style="{StaticResource MenuButtonStyle}"> <Button Command="{Binding DeleteCommand, ElementName=UserControl}" IsEnabled="{Binding IsDeleteButtonEnabled, ElementName=UserControl}" Click="DeleteButton_Click" Style="{StaticResource MenuButtonStyle}">
<SymbolIcon Symbol="Delete"> <SymbolIcon Symbol="Delete">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip x:Uid="TopMenuDeleteButton" /> <ToolTip x:Uid="TopMenuDeleteButton" />
@@ -88,10 +88,10 @@
<SymbolIcon Symbol="More" /> <SymbolIcon Symbol="More" />
<Button.Flyout> <Button.Flyout>
<MenuFlyout Opening="OverflowFlyout_OnOpening"> <MenuFlyout Opening="OverflowFlyout_OnOpening">
<MenuFlyoutItem x:Uid="TopMenuRestoreFlyout" x:Name="RestoreFlyout" Command="{Binding RestoreCommand, ElementName=UserControl}" Click="RestoreButton_Click" Visibility="{Binding RestoreButtonVisibility, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuMoveFlyout" x:Name="MoveFlyout" Command="{Binding MoveCommand, ElementName=UserControl}" Click="MoveButton_Click" IsEnabled="{Binding IsMoveButtonEnabled, ElementName=UserControl}" />
<MenuFlyoutItem x:Uid="TopMenuSaveFlyout" Command="{Binding SaveCommand, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuSaveFlyout" Command="{Binding SaveCommand, ElementName=UserControl}" />
<ToggleMenuFlyoutItem x:Uid="TopMenuEditFlyout" x:Name="EditFlyout" Command="{Binding EditCommand, ElementName=UserControl}" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Click="EditButton_Click" /> <ToggleMenuFlyoutItem x:Uid="TopMenuEditFlyout" x:Name="EditFlyout" Command="{Binding EditCommand, ElementName=UserControl}" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Click="EditButton_Click" />
<MenuFlyoutItem x:Uid="TopMenuDeleteFlyout" x:Name="DeleteFlyout" Command="{Binding DeleteCommand, ElementName=UserControl}" Click="DeleteButton_Click" Visibility="{Binding DeleteButtonVisibility, ElementName=UserControl}" IsEnabled="{Binding IsDeleteButtonEnabled, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuDeleteFlyout" x:Name="DeleteFlyout" Command="{Binding DeleteCommand, ElementName=UserControl}" Click="DeleteButton_Click" IsEnabled="{Binding IsDeleteButtonEnabled, ElementName=UserControl}" />
<MenuFlyoutItem x:Uid="TopMenuSortEntriesFlyout" x:Name="SortEntriesFlyout" Command="{Binding SortEntriesCommand, ElementName=UserControl}" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuSortEntriesFlyout" x:Name="SortEntriesFlyout" Command="{Binding SortEntriesCommand, ElementName=UserControl}" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" />
<MenuFlyoutItem x:Uid="TopMenuSortGroupsFlyout" x:Name="SortGroupsFlyout" Command="{Binding SortGroupsCommand, ElementName=UserControl}" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuSortGroupsFlyout" x:Name="SortGroupsFlyout" Command="{Binding SortGroupsCommand, ElementName=UserControl}" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" />
</MenuFlyout> </MenuFlyout>

View File

@@ -15,7 +15,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SaveCommandProperty = public static readonly DependencyProperty SaveCommandProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SaveCommand", nameof(SaveCommand),
typeof(ICommand), typeof(ICommand),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
@@ -27,7 +27,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty EditCommandProperty = public static readonly DependencyProperty EditCommandProperty =
DependencyProperty.Register( DependencyProperty.Register(
"EditCommand", nameof(EditCommand),
typeof(ICommand), typeof(ICommand),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
@@ -39,19 +39,19 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty DeleteCommandProperty = public static readonly DependencyProperty DeleteCommandProperty =
DependencyProperty.Register( DependencyProperty.Register(
"DeleteCommand", nameof(DeleteCommand),
typeof(ICommand), typeof(ICommand),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
public ICommand RestoreCommand public ICommand MoveCommand
{ {
get { return (ICommand)GetValue(RestoreCommandProperty); } get { return (ICommand)GetValue(MoveCommandProperty); }
set { SetValue(RestoreCommandProperty, value); } set { SetValue(MoveCommandProperty, value); }
} }
public static readonly DependencyProperty RestoreCommandProperty = public static readonly DependencyProperty MoveCommandProperty =
DependencyProperty.Register( DependencyProperty.Register(
"RestoreCommand", nameof(MoveCommand),
typeof(ICommand), typeof(ICommand),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
@@ -63,7 +63,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SortEntriesCommandProperty = public static readonly DependencyProperty SortEntriesCommandProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SortEntriesCommand", nameof(SortEntriesCommand),
typeof(ICommand), typeof(ICommand),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
@@ -75,34 +75,22 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SortGroupsCommandProperty = public static readonly DependencyProperty SortGroupsCommandProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SortGroupsCommand", nameof(SortGroupsCommand),
typeof(ICommand), typeof(ICommand),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
public Visibility RestoreButtonVisibility public bool IsMoveButtonEnabled
{ {
get { return (Visibility)GetValue(RestoreButtonVisibilityProperty); } get { return (bool)GetValue(IsMoveButtonEnabledProperty); }
set { SetValue(RestoreButtonVisibilityProperty, value); } set { SetValue(IsMoveButtonEnabledProperty, value); }
} }
public static readonly DependencyProperty RestoreButtonVisibilityProperty = public static readonly DependencyProperty IsMoveButtonEnabledProperty =
DependencyProperty.Register( DependencyProperty.Register(
"RestoreButtonVisibility", nameof(IsMoveButtonEnabled),
typeof(Visibility), typeof(bool),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(Visibility.Collapsed, (o, args) => { })); new PropertyMetadata(true, (o, args) => { }));
public Visibility DeleteButtonVisibility
{
get { return (Visibility)GetValue(DeleteButtonVisibilityProperty); }
set { SetValue(DeleteButtonVisibilityProperty, value); }
}
public static readonly DependencyProperty DeleteButtonVisibilityProperty =
DependencyProperty.Register(
"DeleteButtonVisibility",
typeof(Visibility),
typeof(TopMenuUserControl),
new PropertyMetadata(Visibility.Collapsed, (o, args) => { }));
public Visibility SortButtonVisibility public Visibility SortButtonVisibility
{ {
@@ -111,7 +99,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty SortButtonVisibilityProperty = public static readonly DependencyProperty SortButtonVisibilityProperty =
DependencyProperty.Register( DependencyProperty.Register(
"SortButtonVisibility", nameof(SortButtonVisibility),
typeof(Visibility), typeof(Visibility),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(Visibility.Collapsed, (o, args) => { })); new PropertyMetadata(Visibility.Collapsed, (o, args) => { }));
@@ -123,7 +111,7 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty IsDeleteButtonEnabledProperty = public static readonly DependencyProperty IsDeleteButtonEnabledProperty =
DependencyProperty.Register( DependencyProperty.Register(
"IsDeleteButtonEnabled", nameof(IsDeleteButtonEnabled),
typeof(bool), typeof(bool),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(true, (o, args) => { })); new PropertyMetadata(true, (o, args) => { }));
@@ -135,14 +123,14 @@ namespace ModernKeePass.Views.UserControls
} }
public static readonly DependencyProperty IsEditButtonCheckedProperty = public static readonly DependencyProperty IsEditButtonCheckedProperty =
DependencyProperty.Register( DependencyProperty.Register(
"IsEditButtonChecked", nameof(IsEditButtonChecked),
typeof(bool), typeof(bool),
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(false, (o, args) => { })); new PropertyMetadata(false, (o, args) => { }));
public event EventHandler<RoutedEventArgs> EditButtonClick; public event EventHandler<RoutedEventArgs> EditButtonClick;
public event EventHandler<RoutedEventArgs> DeleteButtonClick; public event EventHandler<RoutedEventArgs> DeleteButtonClick;
public event EventHandler<RoutedEventArgs> RestoreButtonClick; public event EventHandler<RoutedEventArgs> MoveButtonClick;
public TopMenuUserControl() public TopMenuUserControl()
{ {
@@ -166,19 +154,19 @@ namespace ModernKeePass.Views.UserControls
DeleteButtonClick?.Invoke(sender, e); DeleteButtonClick?.Invoke(sender, e);
} }
private void RestoreButton_Click(object sender, RoutedEventArgs e) private void MoveButton_Click(object sender, RoutedEventArgs e)
{ {
RestoreButtonClick?.Invoke(sender, e); MoveButtonClick?.Invoke(sender, e);
} }
private void OverflowFlyout_OnOpening(object sender, object e) private void OverflowFlyout_OnOpening(object sender, object e)
{ {
DeleteFlyout.IsEnabled = IsDeleteButtonEnabled; DeleteFlyout.IsEnabled = IsDeleteButtonEnabled;
DeleteFlyout.Visibility = DeleteButtonVisibility; DeleteFlyout.IsEnabled = IsDeleteButtonEnabled;
EditFlyout.IsChecked = IsEditButtonChecked; EditFlyout.IsChecked = IsEditButtonChecked;
RestoreFlyout.Visibility = RestoreButtonVisibility; MoveFlyout.IsEnabled = IsMoveButtonEnabled;
SortEntriesFlyout.Visibility = SortButtonVisibility; SortEntriesFlyout.Visibility = SortButtonVisibility;
SortGroupsFlyout.Visibility = SortButtonVisibility; SortGroupsFlyout.Visibility = SortButtonVisibility;

View File

@@ -97,6 +97,7 @@
<Compile Include="App.xaml.cs"> <Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Common\Constants.cs" />
<Compile Include="Common\ResourceHelper.cs" /> <Compile Include="Common\ResourceHelper.cs" />
<Compile Include="Converters\IconToSymbolConverter.cs" /> <Compile Include="Converters\IconToSymbolConverter.cs" />
<Compile Include="DependencyInjection.cs" /> <Compile Include="DependencyInjection.cs" />

View File

@@ -1,5 +1,4 @@
Improved search box Database corruption issues should now be a thing of the past !
Changing entry icon creates a new history entry Added the ability to move entries and groups
Corrected startup crash on some versions of Windows Edits are now in a popup instead of inline
Entry delete button now shows up correctly Updated KeePass lib to version 2.44
List of icons now only displays valid values