More commands/queries

WIP on XAML EntryVm and GroupVm
This commit is contained in:
Geoffroy BONNEVILLE
2020-03-27 13:27:29 +01:00
parent 22072bb2fe
commit e3638c2f5c
24 changed files with 344 additions and 227 deletions

View File

@@ -54,6 +54,9 @@
<Compile Include="Common\Interfaces\ISettingsProxy.cs" />
<Compile Include="Common\Mappings\IMapFrom.cs" />
<Compile Include="Common\Mappings\MappingProfile.cs" />
<Compile Include="Cryptography\Commands\SetCipher\SetCipherCommand.cs" />
<Compile Include="Cryptography\Commands\SetCompression\SetCompressionCommand.cs" />
<Compile Include="Cryptography\Commands\SetKeyDerivation\SetKeyDerivationCommand.cs" />
<Compile Include="Cryptography\Models\CipherVm.cs" />
<Compile Include="Cryptography\Models\KeyDerivationVm.cs" />
<Compile Include="Cryptography\Queries\GetCiphers\GetCiphersQuery.cs" />
@@ -71,6 +74,7 @@
<Compile Include="Database\Queries\ReOpenDatabase\ReOpenDatabaseQuery.cs" />
<Compile Include="DependencyInjection.cs" />
<Compile Include="Entry\Commands\SetFieldValue\SetFieldValueCommand.cs" />
<Compile Include="Entry\Commands\SetFieldValue\SetFieldValueCommandValidator.cs" />
<Compile Include="Entry\Models\EntryVm.cs" />
<Compile Include="Group\Commands\AddEntry\AddEntryCommand.cs" />
<Compile Include="Group\Commands\AddGroup\AddGroupCommand.cs" />
@@ -78,6 +82,7 @@
<Compile Include="Group\Commands\CreateGroup\CreateGroupCommand.cs" />
<Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" />
<Compile Include="Group\Commands\DeleteGroup\DeleteGroupCommand.cs" />
<Compile Include="Group\Commands\InsertEntry\InsertEntryCommand.cs" />
<Compile Include="Group\Commands\RemoveEntry\RemoveEntryCommand.cs" />
<Compile Include="Group\Commands\RemoveGroup\RemoveGroupCommand.cs" />
<Compile Include="Group\Commands\SortEntries\SortEntriesCommand.cs" />
@@ -102,9 +107,6 @@
<Folder Include="Entry\Queries\GetEntry\" />
<Folder Include="Group\Commands\UpdateGroup\" />
<Folder Include="Group\Queries\GetGroup\" />
<Folder Include="Parameters\Commands\SetCipher\" />
<Folder Include="Parameters\Commands\SetCompression\" />
<Folder Include="Parameters\Commands\SetKeyDerivation\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ModernKeePass.Domain\Domain.csproj">

View File

@@ -25,8 +25,9 @@ namespace ModernKeePass.Application.Common.Interfaces
void CloseDatabase();
Task AddEntry(string parentGroupId, string entryId);
Task InsertEntry(string parentGroupId, string entryId, int messageIndex);
Task AddGroup(string parentGroupId, string groupId);
void UpdateEntry(string entryId, string fieldName, string fieldValue);
void UpdateEntry(string entryId, string fieldName, object fieldValue);
void UpdateGroup(string groupId);
Task RemoveEntry(string parentGroupId, string entryId);
Task RemoveGroup(string parentGroupId, string groupId);

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Cryptography.Commands.SetCipher
{
public class SetCipherCommand : IRequest
{
public string CipherId { get; set; }
public class SetCipherCommandHandler : IRequestHandler<SetCipherCommand>
{
private readonly IDatabaseProxy _database;
public SetCipherCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetCipherCommand message)
{
if (_database.IsOpen) _database.CipherId = message.CipherId;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Cryptography.Commands.SetCompression
{
public class SetCompressionCommand : IRequest
{
public string Compression { get; set; }
public class SetCompressionCommandHandler : IRequestHandler<SetCompressionCommand>
{
private readonly IDatabaseProxy _database;
public SetCompressionCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetCompressionCommand message)
{
if (_database.IsOpen) _database.Compression = message.Compression;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Cryptography.Commands.SetKeyDerivation
{
public class SetKeyDerivationCommand : IRequest
{
public string KeyDerivationId { get; set; }
public class SetKeyDerivationCommandHandler : IRequestHandler<SetKeyDerivationCommand>
{
private readonly IDatabaseProxy _database;
public SetKeyDerivationCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetKeyDerivationCommand message)
{
if (_database.IsOpen) _database.KeyDerivationId = message.KeyDerivationId;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -14,7 +14,7 @@ namespace ModernKeePass.Application.Database.Commands.UpdateCredentials
{
private readonly IDatabaseProxy _database;
public UpdateCredentialsCommandHandler(IDatabaseProxy database, IMediator mediator)
public UpdateCredentialsCommandHandler(IDatabaseProxy database)
{
_database = database;
}

View File

@@ -13,7 +13,7 @@ namespace ModernKeePass.Application
var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly;
services.AddAutoMapper(assembly);
services.AddMediatR(assembly);
//services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
//services.AddValidatorsFromAssembly(assembly);
return services;
}

View File

@@ -8,7 +8,7 @@ namespace ModernKeePass.Application.Entry.Commands.SetFieldValue
{
public string EntryId { get; set; }
public string FieldName { get; set; }
public string FieldValue { get; set; }
public object FieldValue { get; set; }
public class SetFieldValueCommandHandler : IRequestHandler<SetFieldValueCommand>
{

View File

@@ -0,0 +1,17 @@
using FluentValidation;
namespace ModernKeePass.Application.Entry.Commands.SetFieldValue
{
public class SetFieldValueCommandValidator: AbstractValidator<SetFieldValueCommand>
{
public SetFieldValueCommandValidator()
{
RuleFor(v => v.EntryId)
.NotNull()
.NotEmpty();
RuleFor(v => v.FieldName)
.NotNull()
.NotEmpty();
}
}
}

View File

@@ -17,7 +17,7 @@ namespace ModernKeePass.Application.Entry.Models
public string Notes { get; set; }
public Uri Url { get; set; }
public Dictionary<string, string> AdditionalFields { get; set; }
public IEnumerable<EntryEntity> History { get; set; }
public IEnumerable<EntryVm> History { get; set; }
public Icon Icon { get; set; }
public Color ForegroundColor { get; set; }
public Color BackgroundColor { get; set; }

View File

@@ -0,0 +1,34 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Group.Commands.InsertEntry
{
public class InsertEntryCommand : IRequest
{
public GroupVm ParentGroup { get; set; }
public EntryVm Entry { get; set; }
public int Index { get; set; }
public class InsertEntryCommandHandler : IAsyncRequestHandler<InsertEntryCommand>
{
private readonly IDatabaseProxy _database;
public InsertEntryCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(InsertEntryCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.InsertEntry(message.ParentGroup.Id, message.Entry.Id, message.Index);
message.ParentGroup.Entries.Insert(message.Index, message.Entry);
}
}
}
}

View File

@@ -5,6 +5,7 @@ using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Entities;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Exceptions;
using ModernKeePassLib;
using ModernKeePassLib.Cryptography.KeyDerivation;
@@ -45,6 +46,7 @@ namespace ModernKeePass.Infrastructure.KeePass
}
set { _pwDatabase.RecycleBinUuid = BuildIdFromString(value); }
}
public string CipherId
{
get { return _pwDatabase.DataCipherUuid.ToHexString(); }
@@ -168,6 +170,17 @@ namespace ModernKeePass.Infrastructure.KeePass
parentPwGroup.AddEntry(pwEntry, true);
});
}
public async Task InsertEntry(string parentGroupId, string entryId, int index)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
parentPwGroup.Entries.Insert((uint)index, pwEntry);
});
}
public async Task AddGroup(string parentGroupId, string groupId)
{
await Task.Run(() =>
@@ -197,15 +210,34 @@ namespace ModernKeePass.Infrastructure.KeePass
});
}
public void UpdateEntry(string entryId, string fieldName, string fieldValue)
public void UpdateEntry(string entryId, string fieldName, object fieldValue)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.Touch(true);
pwEntry.CreateBackup(null);
pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue));
switch (fieldName)
{
case EntryFieldName.Title:
case EntryFieldName.UserName:
case EntryFieldName.Password:
case EntryFieldName.Notes:
case EntryFieldName.Url:
pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue.ToString()));
break;
case EntryFieldName.HasExpirationDate:
pwEntry.Expires = (bool)fieldValue;
break;
case EntryFieldName.ExpirationDate:
pwEntry.ExpiryTime = (DateTime)fieldValue;
break;
case EntryFieldName.Icon:
pwEntry.IconId = IconMapper.MapIconToPwIcon((Icon)fieldValue);
break;
}
}
public void UpdateGroup(string group)
public void UpdateGroup(string groupId)
{
throw new NotImplementedException();
}
@@ -237,7 +269,7 @@ namespace ModernKeePass.Infrastructure.KeePass
var id = pwEntry.Uuid;
pwEntry.ParentGroup.Entries.Remove(pwEntry);
if (_pwDatabase.RecycleBinEnabled)
if (!_pwDatabase.RecycleBinEnabled || pwEntry.ParentGroup.Uuid.Equals(_pwDatabase.RecycleBinUuid))
{
_pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow));
}
@@ -252,7 +284,7 @@ namespace ModernKeePass.Infrastructure.KeePass
var id = pwGroup.Uuid;
pwGroup.ParentGroup.Groups.Remove(pwGroup);
if (_pwDatabase.RecycleBinEnabled)
if (!_pwDatabase.RecycleBinEnabled || pwGroup.ParentGroup.Uuid.Equals(_pwDatabase.RecycleBinUuid))
{
_pwDatabase.DeletedObjects.Add(new PwDeletedObject(id, DateTime.UtcNow));
}

View File

@@ -1,9 +1,9 @@
using System.Windows.Input;
using Windows.UI.Xaml;
using Microsoft.Xaml.Interactivity;
using ModernKeePass.Application.Resources.Queries;
using ModernKeePass.Common;
using ModernKeePass.Interfaces;
using ModernKeePass.Services;
using ModernKeePass.ViewModels;
namespace ModernKeePass.Actions
@@ -32,19 +32,23 @@ namespace ModernKeePass.Actions
public object Execute(object sender, object parameter)
{
var resource = new ResourcesService();
var mediator = App.Mediator;
var type = Entity is GroupVm ? "Group" : "Entry";
var message = Entity.IsRecycleOnDelete
? resource.GetResourceValue($"{type}RecyclingConfirmation")
: resource.GetResourceValue($"{type}DeletingConfirmation");
var text = Entity.IsRecycleOnDelete ? resource.GetResourceValue($"{type}Recycled") : resource.GetResourceValue($"{type}Deleted");
MessageDialogHelper.ShowActionDialog(resource.GetResourceValue("EntityDeleteTitle"), message,
resource.GetResourceValue("EntityDeleteActionButton"),
resource.GetResourceValue("EntityDeleteCancelButton"), a =>
? mediator.Send(new GetResourceQuery { Key = $"{type}RecyclingConfirmation" })
: mediator.Send(new GetResourceQuery { Key = $"{type}DeletingConfirmation" });
var text = Entity.IsRecycleOnDelete ?
mediator.Send(new GetResourceQuery { Key = $"{type}Recycled" }) :
mediator.Send(new GetResourceQuery { Key = $"{type}Deleted" });
MessageDialogHelper.ShowActionDialog(
mediator.Send(new GetResourceQuery { Key = "EntityDeleteTitle" }).GetAwaiter().GetResult(),
message.GetAwaiter().GetResult(),
mediator.Send(new GetResourceQuery { Key = "EntityDeleteActionButton" }).GetAwaiter().GetResult(),
mediator.Send(new GetResourceQuery { Key = "EntityDeleteCancelButton" }).GetAwaiter().GetResult(), async a =>
{
ToastNotificationHelper.ShowMovedToast(Entity, resource.GetResourceValue("EntityDeleting"), text);
Entity.MarkForDelete(resource.GetResourceValue("RecycleBinTitle"));
ToastNotificationHelper.ShowMovedToast(Entity, await mediator.Send(new GetResourceQuery { Key = "EntityDeleting" }), await text);
await Entity.MarkForDelete(await mediator.Send(new GetResourceQuery { Key = "RecycleBinTitle"}));
Command.Execute(null);
}, null).GetAwaiter();

View File

@@ -13,7 +13,7 @@ namespace ModernKeePass.Common
{
var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
var toastElements = notificationXml.GetElementsByTagName("text");
toastElements[0].AppendChild(notificationXml.CreateTextNode($"{action} {entity.Name}"));
toastElements[0].AppendChild(notificationXml.CreateTextNode($"{action} {entity.Title}"));
toastElements[1].AppendChild(notificationXml.CreateTextNode(text));
var toastNode = notificationXml.SelectSingleNode("/toast");

View File

@@ -9,9 +9,9 @@ namespace ModernKeePass.Interfaces
{
GroupVm ParentGroup { get; }
GroupVm PreviousGroup { get; }
int IconId { get; }
int Icon { get; }
string Id { get; }
string Name { get; set; }
string Title { get; set; }
IEnumerable<IVmEntity> BreadCrumb { get; }
bool IsEditMode { get; }
bool IsRecycleOnDelete { get; }

View File

@@ -14,7 +14,7 @@ namespace ModernKeePass.Services
foreach (var entity in data)
{
var entry = group.AddNewEntry();
entry.Name = entity["0"];
entry.Title = entity["0"];
entry.UserName = entity["1"];
entry.Password = entity["2"];
if (entity.Count > 3) entry.Url = entity["3"];

View File

@@ -1,25 +1,27 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Threading.Tasks;
using System.Windows.Input;
using MediatR;
using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Entry.Commands.SetFieldValue;
using ModernKeePass.Application.Group.Commands.DeleteEntry;
using ModernKeePass.Application.Resources.Queries;
using ModernKeePass.Application.Security.Commands.GeneratePassword;
using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity;
using ModernKeePass.Common;
using ModernKeePass.Interfaces;
using ModernKeePass.Services;
using ModernKeePassLib;
using ModernKeePassLib.Cryptography.PasswordGenerator;
using ModernKeePassLib.Security;
using ModernKeePassLib.Cryptography;
namespace ModernKeePass.ViewModels
{
public class EntryVm : INotifyPropertyChanged, IVmEntity, ISelectableModel
public class EntryVm : NotifyPropertyChangedBase, IVmEntity, ISelectableModel
{
public GroupVm ParentGroup { get; private set; }
public GroupVm PreviousGroup { get; private set; }
public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password);
public bool HasExpired => HasExpirationDate && _pwEntry.ExpiryTime < DateTime.Now;
public double PasswordComplexityIndicator => QualityEstimation.EstimatePasswordBits(Password?.ToCharArray());
public bool HasExpired => HasExpirationDate && ExpiryDate < DateTime.Now;
public double PasswordComplexityIndicator => _mediator.Send(new EstimatePasswordComplexityQuery {Password = Password}).GetAwaiter().GetResult();
public bool UpperCasePatternSelected { get; set; } = true;
public bool LowerCasePatternSelected { get; set; } = true;
public bool DigitsPatternSelected { get; set; } = true;
@@ -29,8 +31,7 @@ namespace ModernKeePass.ViewModels
public bool SpecialPatternSelected { get; set; }
public bool BracketsPatternSelected { get; set; }
public string CustomChars { get; set; } = string.Empty;
public PwUuid IdUuid => _pwEntry?.Uuid;
public string Id => _pwEntry?.Uuid.ToHexString();
public string Id => _entry.Id;
public bool IsRecycleOnDelete => _database.RecycleBinEnabled && !ParentGroup.IsSelected;
public IEnumerable<IVmEntity> BreadCrumb => new List<IVmEntity>(ParentGroup.BreadCrumb) {ParentGroup};
/// <summary>
@@ -44,80 +45,73 @@ namespace ModernKeePass.ViewModels
set
{
_passwordLength = value;
NotifyPropertyChanged("PasswordLength");
OnPropertyChanged();
}
}
public string Name
public string Title
{
get { return GetEntryValue(PwDefs.TitleField); }
set { SetEntryValue(PwDefs.TitleField, new ProtectedString(true, value)); }
get { return _entry.Title; }
set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Title), FieldValue = value}); }
}
public string UserName
{
get { return GetEntryValue(PwDefs.UserNameField); }
set { SetEntryValue(PwDefs.UserNameField, new ProtectedString(true, value)); }
get { return _entry.Username; }
set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(UserName), FieldValue = value }); }
}
public string Password
{
get { return GetEntryValue(PwDefs.PasswordField); }
get { return _entry.Password; }
set
{
SetEntryValue(PwDefs.PasswordField, new ProtectedString(true, value));
NotifyPropertyChanged("Password");
NotifyPropertyChanged("PasswordComplexityIndicator");
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Password), FieldValue = value });
OnPropertyChanged();
OnPropertyChanged(nameof(PasswordComplexityIndicator));
}
}
public string Url
{
get { return GetEntryValue(PwDefs.UrlField); }
set { SetEntryValue(PwDefs.UrlField, new ProtectedString(true, value)); }
get { return _entry.Url.ToString();}
set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Url), FieldValue = value }); }
}
public string Notes
{
get { return GetEntryValue(PwDefs.NotesField); }
set { SetEntryValue(PwDefs.NotesField, new ProtectedString(true, value)); }
get { return _entry.Notes; }
set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Notes), FieldValue = value }); }
}
public int IconId
public int Icon
{
get
{
if (HasExpired) return (int) PwIcon.Expired;
if (_pwEntry?.IconId != null) return (int) _pwEntry?.IconId;
return -1;
}
set
{
HandleBackup();
_pwEntry.IconId = (PwIcon)value;
if (HasExpired) return (int)Domain.Enums.Icon.ReportHacked;
return (int) _entry.Icon;
}
set { _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = nameof(Icon), FieldValue = value }); }
}
public DateTimeOffset ExpiryDate
{
get { return new DateTimeOffset(_pwEntry.ExpiryTime.Date); }
get { return _entry.ExpirationDate; }
set
{
if (!HasExpirationDate) return;
HandleBackup();
_pwEntry.ExpiryTime = value.DateTime;
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = "ExpirationDate", FieldValue = value.Date });
}
}
public TimeSpan ExpiryTime
{
get { return _pwEntry.ExpiryTime.TimeOfDay; }
get { return _entry.ExpirationDate.TimeOfDay; }
set
{
if (!HasExpirationDate) return;
HandleBackup();
_pwEntry.ExpiryTime = _pwEntry.ExpiryTime.Date.Add(value);
_mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = "ExpirationDate", FieldValue = ExpiryDate.Date.Add(value) });
}
}
@@ -127,7 +121,7 @@ namespace ModernKeePass.ViewModels
set
{
_isEditMode = value;
NotifyPropertyChanged("IsEditMode");
OnPropertyChanged();
}
}
@@ -137,7 +131,7 @@ namespace ModernKeePass.ViewModels
set
{
_isVisible = value;
NotifyPropertyChanged("IsVisible");
OnPropertyChanged();
}
}
@@ -147,16 +141,16 @@ namespace ModernKeePass.ViewModels
set
{
_isRevealPassword = value;
NotifyPropertyChanged("IsRevealPassword");
OnPropertyChanged();
}
}
public bool HasExpirationDate
{
get { return _pwEntry.Expires; }
get { return _entry.HasExpirationDate; }
set
{
_pwEntry.Expires = value;
NotifyPropertyChanged("HasExpirationDate");
_mediator.Send(new SetFieldValueCommand {EntryId = Id, FieldName = nameof(HasExpirationDate), FieldValue = value});
OnPropertyChanged();
}
}
@@ -165,7 +159,7 @@ namespace ModernKeePass.ViewModels
get
{
var history = new Stack<EntryVm>();
foreach (var historyEntry in _pwEntry.History)
foreach (var historyEntry in _entry.History)
{
history.Push(new EntryVm(historyEntry, ParentGroup) {IsSelected = false});
}
@@ -178,89 +172,68 @@ namespace ModernKeePass.ViewModels
public Color? BackgroundColor
{
get { return _pwEntry?.BackgroundColor; }
get { return _entry?.BackgroundColor; }
set
{
if (value != null) _pwEntry.BackgroundColor = (Color) value;
if (value != null) _entry.BackgroundColor = (Color) value;
}
}
public Color? ForegroundColor
{
get { return _pwEntry?.ForegroundColor; }
get { return _entry?.ForegroundColor; }
set
{
if (value != null) _pwEntry.ForegroundColor = (Color)value;
if (value != null) _entry.ForegroundColor = (Color)value;
}
}
public ICommand SaveCommand { get; }
public ICommand GeneratePasswordCommand { get; }
public ICommand UndoDeleteCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
private readonly PwEntry _pwEntry;
private readonly IDatabaseService _database;
private readonly IResourceService _resource;
private readonly Application.Entry.Models.EntryVm _entry;
private readonly IMediator _mediator;
private bool _isEditMode;
private bool _isDirty = true;
private bool _isRevealPassword;
private double _passwordLength = 25;
private bool _isVisible = true;
private void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public EntryVm() { }
internal EntryVm(PwEntry entry, GroupVm parent) : this(entry, parent, DatabaseService.Instance, new ResourcesService()) { }
internal EntryVm(Application.Entry.Models.EntryVm entry, GroupVm parent) : this(entry, parent, App.Mediator) { }
public EntryVm(PwEntry entry, GroupVm parent, IDatabaseService database, IResourceService resource)
public EntryVm(Application.Entry.Models.EntryVm entry, GroupVm parent, IMediator mediator)
{
_database = database;
_resource = resource;
_pwEntry = entry;
_entry = entry;
_mediator = mediator;
ParentGroup = parent;
SaveCommand = new RelayCommand(() => _database.Save());
GeneratePasswordCommand = new RelayCommand(GeneratePassword);
SaveCommand = new RelayCommand(() => _mediator.Send(new SaveDatabaseCommand()));
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
UndoDeleteCommand = new RelayCommand(() => Move(PreviousGroup), () => PreviousGroup != null);
}
public void GeneratePassword()
public async Task GeneratePassword()
{
var pwProfile = new PwProfile
Password = await _mediator.Send(new GeneratePasswordCommand
{
GeneratorType = PasswordGeneratorType.CharSet,
Length = (uint)PasswordLength,
CharSet = new PwCharSet()
};
if (UpperCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.UpperCase);
if (LowerCasePatternSelected) pwProfile.CharSet.Add(PwCharSet.LowerCase);
if (DigitsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Digits);
if (SpecialPatternSelected) pwProfile.CharSet.Add(PwCharSet.Special);
if (MinusPatternSelected) pwProfile.CharSet.Add('-');
if (UnderscorePatternSelected) pwProfile.CharSet.Add('_');
if (SpacePatternSelected) pwProfile.CharSet.Add(' ');
if (BracketsPatternSelected) pwProfile.CharSet.Add(PwCharSet.Brackets);
pwProfile.CharSet.Add(CustomChars);
ProtectedString password;
PwGenerator.Generate(out password, pwProfile, null, new CustomPwGeneratorPool());
SetEntryValue(PwDefs.PasswordField, password);
NotifyPropertyChanged("Password");
NotifyPropertyChanged("IsRevealPasswordEnabled");
NotifyPropertyChanged("PasswordComplexityIndicator");
BracketsPatternSelected = BracketsPatternSelected,
CustomChars = CustomChars,
DigitsPatternSelected = DigitsPatternSelected,
LowerCasePatternSelected = LowerCasePatternSelected,
MinusPatternSelected = MinusPatternSelected,
PasswordLength = (int)PasswordLength,
SpacePatternSelected = SpacePatternSelected,
SpecialPatternSelected = SpecialPatternSelected,
UnderscorePatternSelected = UnderscorePatternSelected,
UpperCasePatternSelected = UpperCasePatternSelected
});
OnPropertyChanged(nameof(IsRevealPasswordEnabled));
}
public void MarkForDelete(string recycleBinTitle)
public Task MarkForDelete(string recycleBinTitle)
{
if (_database.RecycleBinEnabled && _database.RecycleBin?.IdUuid == null)
_database.CreateRecycleBin(recycleBinTitle);
@@ -280,43 +253,21 @@ namespace ModernKeePass.ViewModels
ParentGroup.Entries.Add(this);
}
public void CommitDelete()
public async Task CommitDelete()
{
_pwEntry.ParentGroup.Entries.Remove(_pwEntry);
if (!_database.RecycleBinEnabled || PreviousGroup.IsSelected) _database.AddDeletedItem(IdUuid);
await _mediator.Send(new DeleteEntryCommand {Entry = _entry});
}
public PwEntry GetPwEntry()
public Application.Entry.Models.EntryVm GetEntry()
{
return _pwEntry;
}
public void Reset()
{
_isDirty = false;
return _entry;
}
public override string ToString()
{
return IsSelected ? _resource.GetResourceValue("EntryCurrent") : _pwEntry.LastModificationTime.ToString("g");
}
private void HandleBackup()
{
if (_isDirty) return;
_pwEntry?.Touch(true);
_pwEntry?.CreateBackup(null);
_isDirty = true;
}
private string GetEntryValue(string key)
{
return _pwEntry?.Strings.GetSafe(key).ReadString();
}
private void SetEntryValue(string key, ProtectedString newValue)
{
HandleBackup();
_pwEntry?.Strings.Set(key, newValue);
return IsSelected ?
_mediator.Send(new GetResourceQuery{Key = "EntryCurrent"}).GetAwaiter().GetResult() :
_entry.ModificationDate.ToString("g");
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
@@ -8,10 +7,17 @@ using System.Windows.Input;
using MediatR;
using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Group.Commands.AddEntry;
using ModernKeePass.Application.Group.Commands.CreateEntry;
using ModernKeePass.Application.Group.Commands.CreateGroup;
using ModernKeePass.Application.Group.Commands.DeleteGroup;
using ModernKeePass.Application.Group.Commands.InsertEntry;
using ModernKeePass.Application.Group.Commands.RemoveEntry;
using ModernKeePass.Application.Group.Commands.SortEntries;
using ModernKeePass.Application.Group.Commands.SortGroups;
using ModernKeePass.Common;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Interfaces;
using ModernKeePassLib;
namespace ModernKeePass.ViewModels
{
@@ -63,17 +69,17 @@ namespace ModernKeePass.ViewModels
}
public IOrderedEnumerable<IGrouping<char, EntryVm>> EntriesZoomedOut => from e in Entries
group e by e.Name.ToUpper().FirstOrDefault() into grp
group e by e.Title.ToUpper().FirstOrDefault() into grp
orderby grp.Key
select grp;
public string Name
public string Title
{
get { return _group == null ? string.Empty : _group.Title; }
set { _group.Title = value; }
}
public int IconId
public int Icon
{
get
{
@@ -152,38 +158,44 @@ namespace ModernKeePass.ViewModels
Groups = new ObservableCollection<GroupVm>(group.SubGroups.Select(g => new GroupVm(g, this, recycleBinId)));
}
private void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
private async void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
var oldIndex = (uint) e.OldStartingIndex;
_reorderedEntry = _group.Entries.GetAt(oldIndex);
_group.Entries.RemoveAt(oldIndex);
var oldIndex = e.OldStartingIndex;
_reorderedEntry = _group.Entries[oldIndex];
await _mediator.Send(new RemoveEntryCommand {Entry = _reorderedEntry, ParentGroup = _group});
break;
case NotifyCollectionChangedAction.Add:
if (_reorderedEntry == null) _group.AddEntry(((EntryVm) e.NewItems[0]).GetPwEntry(), true);
else _group.Entries.Insert((uint)e.NewStartingIndex, _reorderedEntry);
if (_reorderedEntry == null)
{
var entry = ((EntryVm) e.NewItems[0]).GetEntry();
await _mediator.Send(new AddEntryCommand {Entry = entry, ParentGroup = _group});
}
else
{
await _mediator.Send(new InsertEntryCommand {Entry = _reorderedEntry, ParentGroup = _group, Index = e.NewStartingIndex});
}
break;
}
}
public GroupVm AddNewGroup(string name = "")
public async Task<GroupVm> AddNewGroup(string name = "")
{
var pwGroup = new PwGroup(true, true, name, PwIcon.Folder);
_group.AddGroup(pwGroup, true);
var newGroup = new GroupVm(pwGroup, this) {Name = name, IsEditMode = string.IsNullOrEmpty(name)};
Groups.Add(newGroup);
return newGroup;
var newGroup = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group});
var newGroupVm = new GroupVm(newGroup, this) {Title = name, IsEditMode = string.IsNullOrEmpty(name)};
Groups.Add(newGroupVm);
return newGroupVm;
}
public EntryVm AddNewEntry()
public async Task<EntryVm> AddNewEntry()
{
var pwEntry = new PwEntry(true, true);
var newEntry = new EntryVm(pwEntry, this) {IsEditMode = true};
newEntry.GeneratePassword();
Entries.Add(newEntry);
return newEntry;
var newEntry = await _mediator.Send(new CreateEntryCommand { ParentGroup = _group });
var newEntryVm = new EntryVm(newEntry, this) {IsEditMode = true};
await newEntryVm.GeneratePassword();
Entries.Add(newEntryVm);
return newEntryVm;
}
public async Task MarkForDelete(string recycleBinTitle)
@@ -217,42 +229,26 @@ namespace ModernKeePass.ViewModels
public async Task CommitDelete()
{
_group.ParentGroup.Groups.Remove(_group);
if (await IsRecycleBinEnabled() && !PreviousGroup.IsSelected) _database.RecycleBin._group.AddGroup(_group, true);
else _database.AddDeletedItem(IdUuid);
await _mediator.Send(new DeleteGroupCommand { Group = _group });
}
public override string ToString()
{
return Name;
return Title;
}
private async Task SortEntriesAsync()
{
var comparer = new PwEntryComparer(PwDefs.TitleField, true, false);
try
{
_group.Entries.Sort(comparer);
Entries = new ObservableCollection<EntryVm>(Entries.OrderBy(e => e.Name));
}
catch (Exception e)
{
await MessageDialogHelper.ShowErrorDialog(e);
}
await _mediator.Send(new SortEntriesCommand {Group = _group});
Entries = new ObservableCollection<EntryVm>(Entries.OrderBy(e => e.Title));
}
private async Task SortGroupsAsync()
{
try
{
_group.SortSubGroups(false);
Groups = new ObservableCollection<GroupVm>(Groups.OrderBy(g => g.Name).ThenBy(g => g._group == null));
OnPropertyChanged("Groups");
}
catch (Exception e)
{
await MessageDialogHelper.ShowErrorDialog(e);
}
await _mediator.Send(new SortGroupsCommand {Group = _group});
Groups = new ObservableCollection<GroupVm>(Groups.OrderBy(g => g.Title).ThenBy(g => g._group == null));
// TODO: should not be needed
OnPropertyChanged(nameof(Groups));
}
private async Task<bool> IsRecycleBinEnabled()

View File

@@ -41,23 +41,23 @@ namespace ModernKeePass.ViewModels
var converter = new IntToSymbolConverter();
var bankingGroup = group.AddNewGroup("Banking");
bankingGroup.IconId = (int)converter.ConvertBack(Symbol.Calculator, null, null, string.Empty);
bankingGroup.Icon = (int)converter.ConvertBack(Symbol.Calculator, null, null, string.Empty);
var emailGroup = group.AddNewGroup("Email");
emailGroup.IconId = (int)converter.ConvertBack(Symbol.Mail, null, null, string.Empty);
emailGroup.Icon = (int)converter.ConvertBack(Symbol.Mail, null, null, string.Empty);
var internetGroup = group.AddNewGroup("Internet");
internetGroup.IconId = (int)converter.ConvertBack(Symbol.World, null, null, string.Empty);
internetGroup.Icon = (int)converter.ConvertBack(Symbol.World, null, null, string.Empty);
var sample1 = group.AddNewEntry();
sample1.Name = "Sample Entry";
sample1.Title = "Sample Entry";
sample1.UserName = "Username";
sample1.Url = PwDefs.HomepageUrl;
sample1.Password = "Password";
sample1.Notes = "You may safely delete this sample";
var sample2 = group.AddNewEntry();
sample2.Name = "Sample Entry #2";
sample2.Title = "Sample Entry #2";
sample2.UserName = "Michael321";
sample2.Url = PwDefs.HelpUrl + "kb/testform.html";
sample2.Password = "12345";

View File

@@ -415,7 +415,7 @@
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding UserName}" />
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Name}" />
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
@@ -425,7 +425,7 @@
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Password}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Name}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
@@ -498,13 +498,13 @@
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Viewbox MaxHeight="30" Width="50" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="80" Height="40" SelectedSymbol="{Binding IconId, Converter={StaticResource IntToSymbolConverter}, ConverterParameter=0, Mode=TwoWay}" />
<userControls:SymbolPickerUserControl Width="80" Height="40" SelectedSymbol="{Binding Icon, Converter={StaticResource IntToSymbolConverter}, ConverterParameter=0, Mode=TwoWay}" />
</Viewbox>
<Viewbox MaxHeight="30" Width="50" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding IconId, Converter={StaticResource IntToSymbolConverter}}" Width="80" Height="40" />
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IntToSymbolConverter}}" Width="80" Height="40" />
</Viewbox>
<TextBox Grid.Column="1" Grid.Row="0"
Text="{Binding Name, Mode=TwoWay}"
Text="{Binding Title, Mode=TwoWay}"
Foreground="{ThemeResource DefaultTextForegroundThemeBrush}"
Background="Transparent"
SelectionHighlightColor="{StaticResource MainColor}"
@@ -539,7 +539,7 @@
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RestoreButtonClick">
<core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
<actions:ToastAction x:Uid="RestoreEntryCommand" Title="{Binding Name}" />
<actions:ToastAction x:Uid="RestoreEntryCommand" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl>

View File

@@ -49,7 +49,6 @@ namespace ModernKeePass.Views
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
NavigationHelper.OnNavigatedFrom(e);
Model.Reset();
}
#endregion

View File

@@ -105,11 +105,11 @@
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="{Binding BackgroundColor, ConverterParameter={StaticResource MainColor}, Converter={StaticResource ColorToBrushConverter}}">
<Viewbox MaxHeight="50" Width="100">
<SymbolIcon Symbol="{Binding IconId, Converter={StaticResource IntToSymbolConverter}, ConverterParameter=0}" Foreground="{StaticResource TextColor}" />
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IntToSymbolConverter}, ConverterParameter=0}" Foreground="{StaticResource TextColor}" />
</Viewbox>
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="10,10,0,0" >
<TextBlock x:Name="NameTextBlock" Text="{Binding Name}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}"/>
<TextBlock x:Name="NameTextBlock" Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap" />
<TextBlock Text="{Binding UserName}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
<TextBlock Text="{Binding Url}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
@@ -122,7 +122,7 @@
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<actions:ClipboardAction Text="{Binding UserName}" />
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Name}" />
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</MenuFlyoutItem>
@@ -130,7 +130,7 @@
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<actions:ClipboardAction Text="{Binding Password}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Name}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</MenuFlyoutItem>
@@ -138,7 +138,7 @@
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<actions:NavigateToUrlAction Url="{Binding Url}" />
<actions:ToastAction x:Uid="ToastCopyUrl" Title="{Binding Name}" />
<actions:ToastAction x:Uid="ToastCopyUrl" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</MenuFlyoutItem>
@@ -159,7 +159,7 @@
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Width="100" Text="{Binding Name}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
<TextBlock Width="100" Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
@@ -207,14 +207,14 @@
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<Viewbox MaxHeight="30" Width="50" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="80" Height="40" SelectedSymbol="{Binding IconId, Converter={StaticResource IntToSymbolConverter}, ConverterParameter=48, Mode=TwoWay}" />
<userControls:SymbolPickerUserControl Width="80" Height="40" SelectedSymbol="{Binding Icon, Converter={StaticResource IntToSymbolConverter}, ConverterParameter=48, Mode=TwoWay}" />
</Viewbox>
<Viewbox MaxHeight="30" Width="50" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding IconId, Converter={StaticResource IntToSymbolConverter}}" Width="80" Height="40" />
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IntToSymbolConverter}}" Width="80" Height="40" />
</Viewbox>
<TextBox Grid.Column="1" Grid.Row="0"
x:Name="TitleTextBox"
Text="{Binding Name, Mode=TwoWay}"
Text="{Binding Title, Mode=TwoWay}"
Foreground="{ThemeResource DefaultTextForegroundThemeBrush}"
SelectionHighlightColor="{StaticResource MainColor}"
Background="Transparent"
@@ -252,7 +252,7 @@
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="RestoreButtonClick">
<core:InvokeCommandAction Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" />
<actions:ToastAction x:Uid="RestoreGroupCommand" Title="{Binding Name}" />
<actions:ToastAction x:Uid="RestoreGroupCommand" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl>

View File

@@ -122,10 +122,10 @@ namespace ModernKeePass.Views
private void SearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png"));
var results = Model.SubEntries.Where(e => e.Name.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5);
var results = Model.SubEntries.Where(e => e.Title.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5);
foreach (var result in results)
{
args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Name, result.ParentGroup.Name, result.Id, imageUri, string.Empty);
args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title, result.ParentGroup.Title, result.Id, imageUri, string.Empty);
}
}

View File

@@ -118,7 +118,7 @@ namespace ModernKeePassApp.Test
var database = new DatabaseServiceMock();
var entryVm = new EntryVm(new PwEntry(true, true), new GroupVm(), database, _resource)
{
Name = "Test",
Title = "Test",
UserName = "login",
Password = "password"
};
@@ -130,7 +130,7 @@ namespace ModernKeePassApp.Test
var database = new DatabaseServiceMock();
var entryVm = new GroupVm(new PwGroup(true, true), new GroupVm(), database)
{
Name = "Test"
Title = "Test"
};
}
}