14 Commits
V1.17 ... V1.18

Author SHA1 Message Date
Geoffroy BONNEVILLE
7a2ce30512 Design improvements 2020-05-14 16:09:06 +02:00
Geoffroy BONNEVILLE
d497f69a5e Updated deleted text information
Improved dirty status detection (restored removed variable...)
This corrected history creation on navigation when entry deleted
2020-05-14 13:32:44 +02:00
Geoffroy BONNEVILLE
72e5bf4ee1 Added a cryptography service to encrypt protected information (unused atm)
Corrected a bug when deleting recycle bin
2020-05-14 12:05:05 +02:00
Geoffroy BONNEVILLE
2e01fa2212 Changed tooltip styles
Removed useless isdirty field in entry
When an app can't be saved on suspend, don't reopen it to avoid possible de-sync
2020-05-13 18:59:26 +02:00
Geoffroy BONNEVILLE
0adb44bc81 Some design changes
Again fixed open url bug
2020-05-13 15:14:58 +02:00
Geoffroy BONNEVILLE
d38d6461bd Updated Settings page
Added new settings (history and clipboard)
Renamed and moved Settings Vms
2020-05-13 13:50:33 +02:00
Geoffroy BONNEVILLE
7ac1595aaa Clipboard action now sets an expiration timer
WIP Max History count (back-end done, front-end todo)
2020-05-12 18:43:37 +02:00
Geoffroy BONNEVILLE
f8f7c19f65 Display a big database size warning
Auto rename additional field when it matches standard
Treated all fields as new Field class
Added the Is Protected property
2020-05-12 17:14:30 +02:00
Geoffroy BONNEVILLE
d6dc6a74a3 Groups can now also be manually reordered
Design improvements
2020-05-11 19:22:41 +02:00
Geoffroy BONNEVILLE
bb2b99ed66 Additional fields Add, Update and Delete work (too well)
SelectableListView works when programmatically setting selection
2020-05-11 10:53:14 +02:00
Geoffroy BONNEVILLE
71b6009a29 Remove some useless code (again)
Improve some visuals
2020-05-07 19:10:25 +02:00
Geoffroy BONNEVILLE
fbcc354809 Additional fields rendering done
Removed lots of unused classes
2020-05-07 16:01:59 +02:00
Geoffroy BONNEVILLE
e901afaf29 Attachment Add and Delete commands implemented 2020-05-07 12:11:12 +02:00
Geoffroy BONNEVILLE
ca04a6c8ee Entry page is now a Hub
EntryDetailVM and GroupDetailVM are now singleton
Read-only Additional fields and Attachments
2020-05-06 18:54:39 +02:00
111 changed files with 2308 additions and 949 deletions

View File

@@ -77,6 +77,7 @@
<ItemGroup>
<Compile Include="Common\Behaviors\DirtyStatusBehavior.cs" />
<Compile Include="Common\Interfaces\ICryptographyClient.cs" />
<Compile Include="Common\Interfaces\IDatabaseSettingsProxy.cs" />
<Compile Include="Common\Interfaces\IDatabaseProxy.cs" />
<Compile Include="Common\Interfaces\IEntityVm.cs" />
<Compile Include="Common\Interfaces\IFileProxy.cs" />
@@ -88,12 +89,17 @@
<Compile Include="Common\Interfaces\ISettingsProxy.cs" />
<Compile Include="Common\Mappings\IMapFrom.cs" />
<Compile Include="Common\Mappings\MappingProfile.cs" />
<Compile Include="Entry\Commands\AddAttachment\AddAttachmentCommand.cs" />
<Compile Include="Entry\Commands\AddHistory\AddHistoryCommand.cs" />
<Compile Include="Entry\Commands\DeleteAttachment\DeleteAttachmentCommand.cs" />
<Compile Include="Entry\Commands\DeleteField\DeleteFieldCommand.cs" />
<Compile Include="Entry\Commands\DeleteHistory\DeleteHistoryCommand.cs" />
<Compile Include="Entry\Commands\RestoreHistory\RestoreHistoryCommand.cs" />
<Compile Include="Entry\Models\FieldVm.cs" />
<Compile Include="Entry\Queries\GetEntry\GetEntryQuery.cs" />
<Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" />
<Compile Include="Group\Commands\DeleteGroup\DeleteGroupCommand.cs" />
<Compile Include="Group\Commands\MoveGroup\MoveGroupCommand.cs" />
<Compile Include="Group\Commands\UpdateGroup\UpdateGroupCommand.cs" />
<Compile Include="Group\Queries\GetAllGroups\GetAllGroupsQuery.cs" />
<Compile Include="Group\Queries\GetGroup\GetGroupQuery.cs" />
@@ -101,7 +107,9 @@
<Compile Include="Parameters\Commands\SetCipher\SetCipherCommand.cs" />
<Compile Include="Parameters\Commands\SetCompression\SetCompressionCommand.cs" />
<Compile Include="Parameters\Commands\SetHasRecycleBin\SetHasRecycleBinCommand.cs" />
<Compile Include="Parameters\Commands\SetMaxHistoryCount\SetHistoryCountCommand.cs" />
<Compile Include="Parameters\Commands\SetKeyDerivation\SetKeyDerivationCommand.cs" />
<Compile Include="Parameters\Commands\SetMaxHistorySize\SetMaxHistorySizeCommand.cs" />
<Compile Include="Parameters\Commands\SetRecycleBin\SetRecycleBinCommand.cs" />
<Compile Include="Parameters\Models\CipherVm.cs" />
<Compile Include="Parameters\Models\KeyDerivationVm.cs" />
@@ -119,8 +127,8 @@
<Compile Include="Database\Queries\OpenDatabase\OpenDatabaseQueryValidator.cs" />
<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\Commands\UpsertField\UpsertFieldCommand.cs" />
<Compile Include="Entry\Commands\UpsertField\UpsertFieldCommandValidator.cs" />
<Compile Include="Entry\Models\EntryVm.cs" />
<Compile Include="Group\Commands\AddEntry\AddEntryCommand.cs" />
<Compile Include="Group\Commands\AddGroup\AddGroupCommand.cs" />

View File

@@ -1,12 +1,10 @@
using System.Collections.Generic;
using ModernKeePass.Domain.Entities;
using System.Threading.Tasks;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface ICryptographyClient
{
IEnumerable<BaseEntity> Ciphers { get; }
IEnumerable<BaseEntity> KeyDerivations { get; }
IEnumerable<string> CompressionAlgorithms { get; }
Task<string> Protect(string value);
Task<string> UnProtect(string value);
}
}

View File

@@ -22,6 +22,8 @@ namespace ModernKeePass.Application.Common.Interfaces
string FileAccessToken { get; set; }
int Size { get; set; }
bool IsDirty { get; set; }
int MaxHistoryCount { get; set; }
long MaxHistorySize { get; set; }
Task Open(byte[] file, Credentials credentials);
Task ReOpen(byte[] file);
@@ -31,19 +33,25 @@ namespace ModernKeePass.Application.Common.Interfaces
void CloseDatabase();
EntryEntity GetEntry(string id);
GroupEntity GetGroup(string id);
Task AddEntry(string parentGroupId, string entryId);
Task MoveEntry(string parentGroupId, string entryId, int index);
Task AddGroup(string parentGroupId, string groupId);
void UpdateEntry(string entryId, string fieldName, object fieldValue);
void UpdateGroup(GroupEntity group);
void UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected);
void DeleteField(string entryId, string fieldName);
Task RemoveEntry(string parentGroupId, string entryId);
EntryEntity CreateEntry(string parentGroupId);
void SortEntries(string groupId);
GroupEntity GetGroup(string id);
Task AddGroup(string parentGroupId, string groupId);
Task MoveGroup(string parentGroupId, string groupId, int index);
void UpdateGroup(GroupEntity group);
Task RemoveGroup(string parentGroupId, string groupId);
void DeleteEntity(string entityId);
EntryEntity CreateEntry(string parentGroupId);
GroupEntity CreateGroup(string parentGroupId, string name, bool isRecycleBin = false);
void SortEntries(string groupId);
void SortSubGroups(string groupId);
void AddAttachment(string entryId, string attachmentName, byte[] attachmentContent);
void DeleteAttachment(string entryId, string attachmentName);
EntryEntity AddHistory(string entryId);
EntryEntity RestoreFromHistory(string entryId, int historyIndex);

View File

@@ -0,0 +1,12 @@
using System.Collections.Generic;
using ModernKeePass.Domain.Entities;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IDatabaseSettingsProxy
{
IEnumerable<BaseEntity> Ciphers { get; }
IEnumerable<BaseEntity> KeyDerivations { get; }
IEnumerable<string> CompressionAlgorithms { get; }
}
}

View File

@@ -5,7 +5,6 @@ namespace ModernKeePass.Application.Common.Interfaces
public interface IEntityVm
{
string Id { get; set; }
string Title { get; set; }
Icon Icon { get; set; }
string ParentGroupId { get; set; }
string ParentGroupName { get; set; }

View File

@@ -17,6 +17,8 @@ namespace ModernKeePass.Application.Database.Commands.CloseDatabase
public void Handle(CloseDatabaseCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
// Prevent reopening the database due to possible de-synchronization between app and data
if (_database.IsDirty) _database.FileAccessToken = null;
_database.CloseDatabase();
}
}

View File

@@ -64,17 +64,17 @@ namespace ModernKeePass.Application.Database.Commands.CreateDatabase
_database.UpdateGroup(internetGroup);
var sample1 = _database.CreateEntry(_database.RootGroupId);
_database.UpdateEntry(sample1.Id, EntryFieldName.Title, "Sample Entry" );
_database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Notes, "You may safely delete this sample" );
_database.UpdateEntry(sample1.Id, EntryFieldName.Title, "Sample Entry", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password", true);
_database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.Notes, "You may safely delete this sample", false);
var sample2 = _database.CreateEntry(_database.RootGroupId);
_database.UpdateEntry(sample2.Id, EntryFieldName.Title, "Sample Entry #2" );
_database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321" );
_database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345" );
_database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html" );
_database.UpdateEntry(sample2.Id, EntryFieldName.Title, "Sample Entry #2", false);
_database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321", false);
_database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345", true);
_database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html", false);
}
}
}

View File

@@ -12,5 +12,7 @@
public string KeyDerivationId { get; set; }
public int Size { get; internal set; }
public bool IsDirty { get; internal set; }
public int MaxHistoryCount { get; set; }
public long MaxHistorySize { get; set; }
}
}

View File

@@ -33,6 +33,8 @@ namespace ModernKeePass.Application.Database.Queries.GetDatabase
database.KeyDerivationId = _databaseProxy.KeyDerivationId;
database.Size = _databaseProxy.Size;
database.IsDirty = _databaseProxy.IsDirty;
database.MaxHistoryCount = _databaseProxy.MaxHistoryCount;
database.MaxHistorySize = _databaseProxy.MaxHistorySize;
}
return database;
}

View File

@@ -0,0 +1,34 @@
using System;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.AddAttachment
{
public class AddAttachmentCommand : IRequest
{
public EntryVm Entry { get; set; }
public string AttachmentName { get; set; }
public byte[] AttachmentContent { get; set; }
public class AddAttachmentCommandHandler : IRequestHandler<AddAttachmentCommand>
{
private readonly IDatabaseProxy _database;
public AddAttachmentCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(AddAttachmentCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
if (message.Entry.Attachments.ContainsKey(message.AttachmentName)) throw new ArgumentException("AttachmentAlreadyExists", nameof(message.AttachmentName));
_database.AddAttachment(message.Entry.Id, message.AttachmentName, message.AttachmentContent);
message.Entry.Attachments.Add(message.AttachmentName, message.AttachmentContent);
}
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Collections.Generic;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.DeleteAttachment
{
public class DeleteAttachmentCommand : IRequest
{
public EntryVm Entry { get; set; }
public string AttachmentName { get; set; }
public class DeleteAttachmentCommandHandler : IRequestHandler<DeleteAttachmentCommand>
{
private readonly IDatabaseProxy _database;
public DeleteAttachmentCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(DeleteAttachmentCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
if (!message.Entry.Attachments.ContainsKey(message.AttachmentName)) throw new KeyNotFoundException("AttachmentDoesntExist");
_database.DeleteAttachment(message.Entry.Id, message.AttachmentName);
message.Entry.Attachments.Remove(message.AttachmentName);
}
}
}
}

View File

@@ -0,0 +1,29 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.DeleteField
{
public class DeleteFieldCommand: IRequest
{
public string EntryId { get; set; }
public string FieldName { get; set; }
public class DeleteFieldCommandHandler : IRequestHandler<DeleteFieldCommand>
{
private readonly IDatabaseProxy _database;
public DeleteFieldCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(DeleteFieldCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.DeleteField(message.EntryId, message.FieldName);
}
}
}
}

View File

@@ -2,28 +2,29 @@
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.SetFieldValue
namespace ModernKeePass.Application.Entry.Commands.UpsertField
{
public class SetFieldValueCommand : IRequest
public class UpsertFieldCommand : IRequest
{
public string EntryId { get; set; }
public string FieldName { get; set; }
public object FieldValue { get; set; }
public bool IsProtected { get; set; } = true;
public class SetFieldValueCommandHandler : IRequestHandler<SetFieldValueCommand>
public class UpsertFieldCommandHandler : IRequestHandler<UpsertFieldCommand>
{
private readonly IDatabaseProxy _database;
public SetFieldValueCommandHandler(IDatabaseProxy database)
public UpsertFieldCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetFieldValueCommand message)
public void Handle(UpsertFieldCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue);
_database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue, message.IsProtected);
}
}
}

View File

@@ -1,10 +1,10 @@
using FluentValidation;
namespace ModernKeePass.Application.Entry.Commands.SetFieldValue
namespace ModernKeePass.Application.Entry.Commands.UpsertField
{
public class SetFieldValueCommandValidator: AbstractValidator<SetFieldValueCommand>
public class UpsertFieldCommandValidator: AbstractValidator<UpsertFieldCommand>
{
public SetFieldValueCommandValidator()
public UpsertFieldCommandValidator()
{
RuleFor(v => v.EntryId)
.NotNull()

View File

@@ -15,13 +15,13 @@ namespace ModernKeePass.Application.Entry.Models
public string ParentGroupId { get; set; }
public string ParentGroupName { get; set; }
public string Id { get; set; }
public string Title { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public string Url { get; set; }
public bool HasUrl => !string.IsNullOrEmpty(Url);
public Dictionary<string, string> AdditionalFields { get; set; }
public FieldVm Title { get; set; }
public FieldVm Username { get; set; }
public FieldVm Password { get; set; }
public FieldVm Notes { get; set; }
public FieldVm Url { get; set; }
public bool IsValidUrl => Uri.IsWellFormedUriString(Url.Value, UriKind.Absolute);
public List<FieldVm> AdditionalFields { get; set; }
public List<EntryVm> History { get; set; }
public Icon Icon { get; set; }
public Color ForegroundColor { get; set; }
@@ -29,6 +29,7 @@ namespace ModernKeePass.Application.Entry.Models
public bool HasExpirationDate { get; set; }
public DateTimeOffset ExpirationDate { get; set; }
public DateTimeOffset ModificationDate { get; set; }
public Dictionary<string, byte[]> Attachments { get; set; }
public override string ToString()
{
@@ -41,19 +42,26 @@ namespace ModernKeePass.Application.Entry.Models
.ForMember(d => d.ParentGroupId, opts => opts.MapFrom(s => s.ParentId))
.ForMember(d => d.ParentGroupName, opts => opts.MapFrom(s => s.ParentName))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Id))
.ForMember(d => d.Title, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Username, opts => opts.MapFrom(s => s.UserName))
.ForMember(d => d.Password, opts => opts.MapFrom(s => s.Password))
.ForMember(d => d.Url, opts => opts.MapFrom(s => s.Url))
.ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Notes))
.ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => s.AdditionalFields))
.ForMember(d => d.Title, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Title, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Title, IsProtected = true } ))
.ForMember(d => d.Username, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.UserName, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.UserName, IsProtected = true } ))
.ForMember(d => d.Password, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Password, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Password, IsProtected = true } ))
.ForMember(d => d.Url, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Url, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Url, IsProtected = true } ))
.ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Notes, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Notes, IsProtected = true } ))
.ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s =>
s.Fields.Where(f => !EntryFieldName.StandardFieldNames.Contains(f.Name, StringComparer.OrdinalIgnoreCase))))
.ForMember(d => d.History, opts => opts.MapFrom(s => s.History.Reverse()))
.ForMember(d => d.HasExpirationDate, opts => opts.MapFrom(s => s.HasExpirationDate))
.ForMember(d => d.ExpirationDate, opts => opts.MapFrom(s => s.ExpirationDate))
.ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationDate))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => s.HasExpirationDate && s.ExpirationDate < DateTimeOffset.Now ? Icon.ReportHacked : s.Icon))
.ForMember(d => d.ForegroundColor, opts => opts.MapFrom(s => s.ForegroundColor))
.ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor));
.ForMember(d => d.BackgroundColor, opts => opts.MapFrom(s => s.BackgroundColor))
.ForMember(d => d.Attachments, opts => opts.MapFrom(s => s.Attachments));
}
}
}

View File

@@ -0,0 +1,20 @@
using AutoMapper;
using ModernKeePass.Application.Common.Mappings;
using ModernKeePass.Domain.Entities;
namespace ModernKeePass.Application.Entry.Models
{
public class FieldVm: IMapFrom<FieldEntity>
{
public string Name { get; set; }
public string Value { get; set; }
public bool IsProtected { get; set; }
public override string ToString() => Value;
public void Mapping(Profile profile)
{
profile.CreateMap<FieldEntity, FieldVm>();
}
}
}

View File

@@ -25,12 +25,13 @@ namespace ModernKeePass.Application.Group.Commands.DeleteGroup
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var isRecycleBin = message.GroupId.Equals(_database.RecycleBinId);
if (_database.IsRecycleBinEnabled && (string.IsNullOrEmpty(_database.RecycleBinId) || _database.RecycleBinId.Equals(Constants.EmptyId)))
{
_database.CreateGroup(_database.RootGroupId, message.RecycleBinName, true);
}
if (!_database.IsRecycleBinEnabled || message.ParentGroupId.Equals(_database.RecycleBinId))
if (!_database.IsRecycleBinEnabled || message.ParentGroupId.Equals(_database.RecycleBinId) || isRecycleBin)
{
_database.DeleteEntity(message.GroupId);
}
@@ -40,6 +41,7 @@ namespace ModernKeePass.Application.Group.Commands.DeleteGroup
}
await _database.RemoveGroup(message.ParentGroupId, message.GroupId);
if (isRecycleBin) _database.RecycleBinId = Constants.EmptyId;
}
}
}

View File

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

View File

@@ -24,7 +24,7 @@ namespace ModernKeePass.Application.Group.Commands.SortEntries
if (!_database.IsOpen) throw new DatabaseClosedException();
_database.SortEntries(message.Group.Id);
message.Group.Entries = message.Group.Entries.OrderBy(e => e.Title).ToList();
message.Group.Entries = message.Group.Entries.OrderBy(e => e.Title.Value).ToList();
}
}
}

View File

@@ -0,0 +1,27 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetMaxHistoryCount
{
public class SetMaxHistoryCountCommand : IRequest
{
public int MaxHistoryCount { get; set; }
public class SetMaxHistoryCountCommandHandler : IRequestHandler<SetMaxHistoryCountCommand>
{
private readonly IDatabaseProxy _database;
public SetMaxHistoryCountCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetMaxHistoryCountCommand message)
{
if (_database.IsOpen) _database.MaxHistoryCount = message.MaxHistoryCount;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -0,0 +1,28 @@
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Parameters.Commands.SetMaxHistorySize
{
public class SetMaxHistorySizeCommand : IRequest
{
public long MaxHistorySize { get; set; }
public class SetMaxHistorySizeCommandHandler : IRequestHandler<SetMaxHistorySizeCommand>
{
private readonly IDatabaseProxy _database;
public SetMaxHistorySizeCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public void Handle(SetMaxHistorySizeCommand message)
{
if (_database.IsOpen) _database.MaxHistorySize = message.MaxHistorySize;
else throw new DatabaseClosedException();
}
}
}
}

View File

@@ -10,16 +10,16 @@ namespace ModernKeePass.Application.Parameters.Queries.GetCiphers
{
public class GetCiphersQueryHandler: IRequestHandler<GetCiphersQuery, IEnumerable<CipherVm>>
{
private readonly ICryptographyClient _cryptography;
private readonly IDatabaseSettingsProxy _databaseSettings;
public GetCiphersQueryHandler(ICryptographyClient cryptography)
public GetCiphersQueryHandler(IDatabaseSettingsProxy databaseSettings)
{
_cryptography = cryptography;
_databaseSettings = databaseSettings;
}
public IEnumerable<CipherVm> Handle(GetCiphersQuery message)
{
return _cryptography.Ciphers.Select(c => new CipherVm
return _databaseSettings.Ciphers.Select(c => new CipherVm
{
Id = c.Id,
Name = c.Name

View File

@@ -9,16 +9,16 @@ namespace ModernKeePass.Application.Parameters.Queries.GetCompressions
{
public class GetCompressionsQueryHandler : IRequestHandler<GetCompressionsQuery, IEnumerable<string>>
{
private readonly ICryptographyClient _cryptography;
private readonly IDatabaseSettingsProxy _databaseSettings;
public GetCompressionsQueryHandler(ICryptographyClient cryptography)
public GetCompressionsQueryHandler(IDatabaseSettingsProxy databaseSettings)
{
_cryptography = cryptography;
_databaseSettings = databaseSettings;
}
public IEnumerable<string> Handle(GetCompressionsQuery message)
{
return _cryptography.CompressionAlgorithms.OrderBy(c => c);
return _databaseSettings.CompressionAlgorithms.OrderBy(c => c);
}
}
}

View File

@@ -10,16 +10,16 @@ namespace ModernKeePass.Application.Parameters.Queries.GetKeyDerivations
{
public class GetKeyDerivationsQueryHandler : IRequestHandler<GetKeyDerivationsQuery, IEnumerable<KeyDerivationVm>>
{
private readonly ICryptographyClient _cryptography;
private readonly IDatabaseSettingsProxy _databaseSettings;
public GetKeyDerivationsQueryHandler(ICryptographyClient cryptography)
public GetKeyDerivationsQueryHandler(IDatabaseSettingsProxy databaseSettings)
{
_cryptography = cryptography;
_databaseSettings = databaseSettings;
}
public IEnumerable<KeyDerivationVm> Handle(GetKeyDerivationsQuery message)
{
return _cryptography.KeyDerivations.Select(c => new KeyDerivationVm
return _databaseSettings.KeyDerivations.Select(c => new KeyDerivationVm
{
Id = c.Id,
Name = c.Name

View File

@@ -76,11 +76,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Common\Constants.cs" />
<Compile Include="Dtos\Attachment.cs" />
<Compile Include="Dtos\Credentials.cs" />
<Compile Include="Dtos\FileInfo.cs" />
<Compile Include="Dtos\PasswordGenerationOptions.cs" />
<Compile Include="Entities\BaseEntity.cs" />
<Compile Include="Entities\EntryEntity.cs" />
<Compile Include="Entities\FieldEntity.cs" />
<Compile Include="Entities\GroupEntity.cs" />
<Compile Include="Enums\CredentialStatusTypes.cs" />
<Compile Include="Enums\DatabaseVersion.cs" />

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Domain.Dtos
{
public class Attachment
{
public string Name { get; set; }
public byte[] Content { get; set; }
}
}

View File

@@ -7,13 +7,10 @@ namespace ModernKeePass.Domain.Entities
{
public class EntryEntity: BaseEntity
{
public string UserName { get; set; }
public string Password { get; set; }
public string Url { get; set; }
public string Notes { get; set; }
public DateTimeOffset ExpirationDate { get; set; }
public Dictionary<string, string> AdditionalFields { get; set; } = new Dictionary<string, string>();
public IEnumerable<FieldEntity> Fields { get; set; } = new List<FieldEntity>();
public IEnumerable<EntryEntity> History { get; set; } = new List<EntryEntity>();
public Dictionary<string, byte[]> Attachments { get; set; } = new Dictionary<string, byte[]>();
public DateTimeOffset ExpirationDate { get; set; }
public Icon Icon { get; set; }
public Color ForegroundColor { get; set; }
public Color BackgroundColor { get; set; }

View File

@@ -0,0 +1,9 @@
namespace ModernKeePass.Domain.Entities
{
public class FieldEntity
{
public string Name { get; set; }
public string Value { get; set; }
public bool IsProtected { get; set; }
}
}

View File

@@ -1,4 +1,6 @@
namespace ModernKeePass.Domain.Enums
using System.Collections.Generic;
namespace ModernKeePass.Domain.Enums
{
public static class EntryFieldName
{
@@ -12,5 +14,19 @@
public const string HasExpirationDate = nameof(HasExpirationDate);
public const string BackgroundColor = nameof(BackgroundColor);
public const string ForegroundColor = nameof(ForegroundColor);
public static IEnumerable<string> StandardFieldNames = new[]
{
Title,
UserName,
Password,
Url,
Notes,
Icon,
ExpirationDate,
HasExpirationDate,
BackgroundColor,
ForegroundColor
};
}
}

View File

@@ -18,7 +18,7 @@ namespace ModernKeePass.Infrastructure
public static IServiceCollection AddInfrastructureKeePass(this IServiceCollection services)
{
services.AddSingleton(typeof(IDatabaseProxy), typeof(KeePassDatabaseClient));
services.AddTransient(typeof(ICryptographyClient), typeof(KeePassCryptographyClient));
services.AddTransient(typeof(IDatabaseSettingsProxy), typeof(KeePassDatabaseSettingsProxy));
services.AddTransient(typeof(ICredentialsProxy), typeof(KeePassCredentialsClient));
return services;
}
@@ -29,6 +29,7 @@ namespace ModernKeePass.Infrastructure
services.AddTransient(typeof(ISettingsProxy), typeof(UwpSettingsClient));
services.AddTransient(typeof(IRecentProxy), typeof(UwpRecentFilesClient));
services.AddTransient(typeof(IResourceProxy), typeof(UwpResourceClient));
services.AddTransient(typeof(ICryptographyClient), typeof(UwpCryptographyClient));
services.AddTransient(typeof(INotificationService), typeof(ToastNotificationService));
return services;
}

View File

@@ -80,15 +80,15 @@
<Compile Include="DependencyInjection.cs" />
<Compile Include="File\CsvImportFormat.cs" />
<Compile Include="KeePass\EntryFieldMapper.cs" />
<Compile Include="KeePass\EntryMappingProfile.cs" />
<Compile Include="KeePass\GroupMappingProfile.cs" />
<Compile Include="KeePass\MappingProfiles.cs" />
<Compile Include="KeePass\IconMapper.cs" />
<Compile Include="KeePass\KeePassCryptographyClient.cs" />
<Compile Include="KeePass\KeePassDatabaseSettingsProxy.cs" />
<Compile Include="KeePass\KeePassDatabaseClient.cs" />
<Compile Include="KeePass\KeePassCredentialsClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UWP\StorageFileClient.cs" />
<Compile Include="UWP\ToastNotificationService.cs" />
<Compile Include="UWP\UwpCryptographyClient.cs" />
<Compile Include="UWP\UwpRecentFilesClient.cs" />
<Compile Include="UWP\UwpResourceClient.cs" />
<Compile Include="UWP\UwpSettingsClient.cs" />

View File

@@ -1,35 +0,0 @@
using System;
using System.Linq;
using AutoMapper;
using ModernKeePass.Domain.Entities;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public class EntryMappingProfile: Profile
{
public EntryMappingProfile()
{
CreateMap<PwEntry, EntryEntity>()
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString()))
.ForMember(dest => dest.ParentName, opt => opt.MapFrom(src => src.ParentGroup.Name))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString()))
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.TitleField)))
.ForMember(dest => dest.UserName, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.UserNameField)))
.ForMember(dest => dest.Password, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.PasswordField)))
.ForMember(dest => dest.Url, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.UrlField)))
.ForMember(dest => dest.Notes, opt => opt.MapFrom(src => GetEntryValue(src, PwDefs.NotesField)))
.ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor))
.ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor))
.ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime)))
.ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires))
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId)))
.ForMember(dest => dest.AdditionalFields, opt => opt.MapFrom(src =>
src.Strings.Where(s => !PwDefs.GetStandardFields().Contains(s.Key))
.ToDictionary(s => s.Key, s => GetEntryValue(src, s.Key))))
.ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime)));
}
private string GetEntryValue(PwEntry entry, string key) => entry.Strings.GetSafe(key).ReadString();
}
}

View File

@@ -1,23 +0,0 @@
using AutoMapper;
using ModernKeePass.Domain.Entities;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public class GroupMappingProfile : Profile
{
public GroupMappingProfile()
{
CreateMap<PwGroup, GroupEntity>()
.ForMember(d => d.ParentId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString()))
.ForMember(d => d.ParentName, opts => opts.MapFrom(s => s.ParentGroup.Name))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Uuid.ToHexString()))
.ForMember(d => d.Name, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId)))
.ForMember(d => d.LastModificationDate, opts => opts.MapFrom(s => s.LastModificationTime))
.ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries))
.ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.Groups))
.MaxDepth(2);
}
}
}

View File

@@ -40,6 +40,18 @@ namespace ModernKeePass.Infrastructure.KeePass
public int Size { get; set; }
public bool IsDirty { get; set; }
public int MaxHistoryCount
{
get { return _pwDatabase.HistoryMaxItems; }
set { _pwDatabase.HistoryMaxItems = value; }
}
public long MaxHistorySize
{
get { return _pwDatabase.HistoryMaxSize; }
set { _pwDatabase.HistoryMaxSize = value; }
}
// Settings
public bool IsRecycleBinEnabled
{
@@ -189,6 +201,20 @@ namespace ModernKeePass.Infrastructure.KeePass
parentPwGroup.AddGroup(pwGroup, true);
});
}
public async Task MoveGroup(string parentGroupId, string groupId, int index)
{
await Task.Run(() =>
{
var parentPwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(parentGroupId), true);
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
var currentIndex = (uint)parentPwGroup.Groups.IndexOf(pwGroup);
parentPwGroup.Groups.RemoveAt(currentIndex);
parentPwGroup.Groups.Insert((uint)index, pwGroup);
});
}
public async Task RemoveEntry(string parentGroupId, string entryId)
{
await Task.Run(() =>
@@ -214,7 +240,7 @@ namespace ModernKeePass.Infrastructure.KeePass
_pwDatabase.DeletedObjects.Add(new PwDeletedObject(BuildIdFromString(entityId), _dateTime.Now));
}
public void UpdateEntry(string entryId, string fieldName, object fieldValue)
public void UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
@@ -225,7 +251,7 @@ namespace ModernKeePass.Infrastructure.KeePass
case EntryFieldName.Password:
case EntryFieldName.Notes:
case EntryFieldName.Url:
pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue.ToString()));
pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(isProtected, fieldValue.ToString()));
break;
case EntryFieldName.HasExpirationDate:
pwEntry.Expires = (bool)fieldValue;
@@ -242,9 +268,18 @@ namespace ModernKeePass.Infrastructure.KeePass
case EntryFieldName.ForegroundColor:
pwEntry.ForegroundColor = (Color)fieldValue;
break;
default:
pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, fieldValue.ToString()));
break;
}
}
public void DeleteField(string entryId, string fieldName)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.Strings.Remove(fieldName);
}
public EntryEntity AddHistory(string entryId)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
@@ -306,6 +341,18 @@ namespace ModernKeePass.Infrastructure.KeePass
var pwGroup = _pwDatabase.RootGroup.FindGroup(BuildIdFromString(groupId), true);
pwGroup.SortSubGroups(false);
}
public void AddAttachment(string entryId, string attachmentName, byte[] attachmentContent)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.Binaries.Set(attachmentName, new ProtectedBinary(true, attachmentContent));
}
public void DeleteAttachment(string entryId, string attachmentName)
{
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
pwEntry.Binaries.Remove(attachmentName);
}
public EntryEntity GetEntry(string id)
{

View File

@@ -9,7 +9,7 @@ using ModernKeePassLib.Cryptography.KeyDerivation;
namespace ModernKeePass.Infrastructure.KeePass
{
public class KeePassCryptographyClient: ICryptographyClient
public class KeePassDatabaseSettingsProxy: IDatabaseSettingsProxy
{
public IEnumerable<BaseEntity> Ciphers
{

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using ModernKeePass.Domain.Entities;
using ModernKeePassLib;
using ModernKeePassLib.Security;
namespace ModernKeePass.Infrastructure.KeePass
{
public class MappingProfiles: Profile
{
public MappingProfiles()
{
CreateMap<KeyValuePair<string, ProtectedString>, FieldEntity>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key))
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value.ReadString()))
.ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected));
CreateMap<PwEntry, EntryEntity>()
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString()))
.ForMember(dest => dest.ParentName, opt => opt.MapFrom(src => src.ParentGroup.Name))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString()))
.ForMember(dest => dest.Fields, opt => opt.MapFrom(src => src.Strings))
.ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor))
.ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor))
.ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime)))
.ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires))
.ForMember(dest => dest.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId)))
.ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime)))
.ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Binaries.Select(b => new KeyValuePair<string, byte[]> (b.Key, b.Value.ReadData()) )));
CreateMap<PwGroup, GroupEntity>()
.ForMember(d => d.ParentId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString()))
.ForMember(d => d.ParentName, opts => opts.MapFrom(s => s.ParentGroup.Name))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Uuid.ToHexString()))
.ForMember(d => d.Name, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId)))
.ForMember(d => d.LastModificationDate, opts => opts.MapFrom(s => s.LastModificationTime))
.ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries))
.ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.Groups))
.MaxDepth(2);
}
}
}

View File

@@ -0,0 +1,47 @@
using System;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.DataProtection;
using ModernKeePass.Application.Common.Interfaces;
namespace ModernKeePass.Infrastructure.UWP
{
public class UwpCryptographyClient: ICryptographyClient
{
public async Task<string> Protect(string value)
{
// Create a DataProtectionProvider object for the specified descriptor.
var provider = new DataProtectionProvider();
// Encode the plaintext input message to a buffer.
var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8);
// Encrypt the message.
var buffProtected = await provider.ProtectAsync(buffMsg);
// Encode buffer to Base64
var stringProtected = CryptographicBuffer.EncodeToBase64String(buffProtected);
// Return the encrypted string.
return stringProtected;
}
public async Task<string> UnProtect(string value)
{
// Create a DataProtectionProvider object.
var provider = new DataProtectionProvider();
// Decode from Base64 string
var buffProtected = CryptographicBuffer.DecodeFromBase64String(value);
// Decrypt the protected message specified on input.
var buffUnprotected = await provider.UnprotectAsync(buffProtected);
// Convert the unprotected message from an IBuffer object to a string.
var strClearText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffUnprotected);
// Return the plaintext string.
return strClearText;
}
}
}

View File

@@ -3,7 +3,7 @@
"dependencies": {
"AutoMapper": "5.2.0",
"Microsoft.NETCore.Portable.Compatibility": "1.0.1",
"ModernKeePassLib": "2.44.3",
"ModernKeePassLib": "2.45.1",
"NETStandard.Library": "2.0.3"
},
"frameworks": {

View File

@@ -32,7 +32,8 @@ namespace ModernKeePass
/// Provides application-specific behavior to supplement the default Application class.
/// </summary>
sealed partial class App
{
{ public static IServiceProvider Services { get; private set; }
private readonly IResourceProxy _resource;
private readonly IMediator _mediator;
private readonly ISettingsProxy _settings;
@@ -41,9 +42,7 @@ namespace ModernKeePass
private readonly INotificationService _notification;
private readonly IFileProxy _file;
private readonly IMessenger _messenger;
public static IServiceProvider Services { get; private set; }
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
/// executed, and as such is the logical equivalent of main() or WinMain().
@@ -166,14 +165,6 @@ namespace ModernKeePass
#if DEBUG
_notification.Show("App resumed", "Nothing to do, no previous database opened");
#endif
}
catch (Exception ex)
{
_hockey.TrackException(ex);
_hockey.Flush();
}
finally
{
_navigation.NavigateTo(Constants.Navigation.MainPage);
}
}
@@ -214,7 +205,7 @@ namespace ModernKeePass
}
catch (SaveException ex)
{
_notification.Show(_resource.GetResourceValue("MessageDialogSaveErrorTitle"), ex.Message);
_notification.Show(ex.Source, ex.Message);
}
catch (Exception ex)
{

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Package xmlns="http://schemas.microsoft.com/appx/2010/manifest" xmlns:m2="http://schemas.microsoft.com/appx/2013/manifest">
<Identity Name="wismna.ModernKeePass" Publisher="CN=0719A91A-C322-4EE0-A257-E60733EECF06" Version="1.17.0.12" />
<Identity Name="wismna.ModernKeePass" Publisher="CN=0719A91A-C322-4EE0-A257-E60733EECF06" Version="1.18.0.12" />
<Properties>
<DisplayName>ModernKeePass</DisplayName>
<PublisherDisplayName>wismna</PublisherDisplayName>

View File

@@ -191,7 +191,7 @@
To="1" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource MainColor}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -358,13 +358,13 @@
</Grid>
</Border>
<Border x:Name="SelectedLeftIndicator"
BorderBrush="{StaticResource MainColor}"
BorderBrush="{ThemeResource MainColorBrush}"
BorderThickness="5,0,0,0"
Opacity="0"/>
<Rectangle x:Name="SelectedBorder"
IsHitTestVisible="False"
Opacity="0"
Stroke="{StaticResource MainColor}"
Stroke="{ThemeResource MainColorBrush}"
StrokeThickness="{ThemeResource ListViewItemSelectedBorderThemeThickness}"
Margin="0" />
<Border x:Name="SelectedCheckMarkOuter"
@@ -373,7 +373,7 @@
VerticalAlignment="Top"
Margin="4">
<Grid x:Name="SelectedCheckMark" Opacity="0" Height="40" Width="40">
<Path x:Name="SelectedEarmark" Data="M0,0 L40,0 L40,40 z" Fill="{StaticResource MainColor}" Stretch="Fill"/>
<Path x:Name="SelectedEarmark" Data="M0,0 L40,0 L40,40 z" Fill="{ThemeResource MainColorBrush}" Stretch="Fill"/>
<Path Data="F1 M133.1,17.9 L137.2,13.2 L144.6,19.6 L156.4,5.8 L161.2,9.9 L145.6,28.4 z" Fill="{ThemeResource ListViewItemCheckThemeBrush}" Height="13" Stretch="Fill" Width="15" HorizontalAlignment="Right" Margin="0,5.5,5.5,0" VerticalAlignment="Top" FlowDirection="LeftToRight"/>
</Grid>
</Border>

View File

@@ -5,10 +5,10 @@
<!-- Common theme values -->
<x:Double x:Key="MenuWidth">60</x:Double>
<x:Double x:Key="MenuHeight">40</x:Double>
<x:Double x:Key="ExpandedMenuSize">300</x:Double>
<x:Double x:Key="ExpandedMenuSize">250</x:Double>
<GridLength x:Key="MenuHeightGridLength">40</GridLength>
<GridLength x:Key="MenuWidthGridLength">60</GridLength>
<GridLength x:Key="ExpandedMenuGridLength">300</GridLength>
<GridLength x:Key="ExpandedMenuGridLength">250</GridLength>
<Color x:Key="MainColor">SlateBlue</Color>
<Color x:Key="MainColorLight">MediumPurple</Color>
@@ -16,17 +16,22 @@
<Color x:Key="TextColorLight">WhiteSmoke</Color>
<Color x:Key="BorderColor">DarkGray</Color>
<Color x:Key="FlyoutColor">#FFF0F0F0</Color>
<Color x:Key="HubSectionColor">#FF777777</Color>
<SolidColorBrush x:Key="MainColorBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="MainColorLightBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="MainColorDarkBrush" Color="{ThemeResource MainColorDark}" />
<SolidColorBrush x:Key="TextColorLightBrush" Color="{ThemeResource TextColorLight}" />
<SolidColorBrush x:Key="HubSectionBrush" Color="{ThemeResource HubSectionColor}" />
<Style TargetType="TextBlock" x:Key="TextBlockSettingsHeaderStyle" >
<Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="SemiLight" />
</Style>
<Style TargetType="HyperlinkButton">
<Setter Property="FontWeight" Value="SemiLight" />
</Style>
<SolidColorBrush x:Key="TextBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
<SolidColorBrush x:Key="TextSelectionHighlightColorThemeBrush" Color="{ThemeResource MainColor}" />
@@ -42,7 +47,6 @@
<SolidColorBrush x:Key="HyperlinkForegroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="HyperlinkPointerOverForegroundThemeBrush" Color="{ThemeResource MainColorLight}" />
<!--<SolidColorBrush x:Key="SearchBoxPointerOverBorderThemeBrush" Color="{ThemeResource MainColorLight}" />-->
<SolidColorBrush x:Key="SearchBoxPointerOverTextThemeBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="SearchBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
<SolidColorBrush x:Key="SearchBoxFocusedBorderThemeBrush" Color="{ThemeResource MainColor}" />
@@ -81,4 +85,8 @@
<Thickness x:Key="FlyoutBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="{ThemeResource FlyoutColor}" />
<SolidColorBrush x:Key="FlyoutBackgroundThemeBrush" Color="{ThemeResource FlyoutColor}" />
<Thickness x:Key="ToolTipBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="ToolTipBorderThemeBrush" Color="{ThemeResource FlyoutColor}" />
<SolidColorBrush x:Key="ToolTipBackgroundThemeBrush" Color="{ThemeResource FlyoutColor}" />
</ResourceDictionary>

View File

@@ -300,7 +300,6 @@
Grid.Column="1"
Style="{StaticResource ActionButtonStyle}"
Content="{TemplateBinding ButtonSymbol}"
IsEnabled="{TemplateBinding IsButtonEnabled}"
Command="{TemplateBinding ButtonCommand}"
CommandParameter="{TemplateBinding ButtonCommandParameter}">
<ToolTipService.ToolTip>

View File

@@ -147,35 +147,17 @@
<data name="EntityRestoredTitle" xml:space="preserve">
<value>Restored</value>
</data>
<data name="EntryDeleted" xml:space="preserve">
<value>Entry permanently removed</value>
</data>
<data name="EntryDeletingConfirmation" xml:space="preserve">
<value>Are you sure you want to delete this entry?</value>
<value>Are you sure you want to delete {0} ?</value>
</data>
<data name="EntryRecycled" xml:space="preserve">
<value>Entry moved to the Recycle bin</value>
</data>
<data name="EntryRecyclingConfirmation" xml:space="preserve">
<value>Are you sure you want to send this entry to the recycle bin?</value>
<value>{0} moved to the Recycle bin</value>
</data>
<data name="EntryRestored" xml:space="preserve">
<value>Entry returned to its original group</value>
</data>
<data name="GroupDeleted" xml:space="preserve">
<value>Group permanently removed</value>
</data>
<data name="GroupDeletingConfirmation" xml:space="preserve">
<value>Are you sure you want to delete the whole group and all its entries?</value>
</data>
<data name="GroupRecycled" xml:space="preserve">
<value>Group moved to the Recycle bin</value>
</data>
<data name="GroupRecyclingConfirmation" xml:space="preserve">
<value>Are you sure you want to send the whole group and all its entries to the recycle bin?</value>
</data>
<data name="GroupRestored" xml:space="preserve">
<value>Group returned to its original group</value>
<value>{0} moved to the Recycle bin</value>
</data>
<data name="MainMenuItemAbout" xml:space="preserve">
<value>About</value>
@@ -216,8 +198,8 @@
<data name="CompositeKeyErrorUserAccount" xml:space="preserve">
<value>- user account</value>
</data>
<data name="SettingsMenuItemSave" xml:space="preserve">
<value>Saving</value>
<data name="SettingsMenuItemCredentials" xml:space="preserve">
<value>Credentials</value>
</data>
<data name="RecycleBinTitle" xml:space="preserve">
<value>Recycle Bin</value>
@@ -276,4 +258,19 @@
<data name="CompositeKeyFileNameSuggestion" xml:space="preserve">
<value>Key</value>
</data>
<data name="DatabaseTooBigDescription" xml:space="preserve">
<value>Database size is too big for auto-save on suspend. Please save your changes before closing the app !</value>
</data>
<data name="DatabaseTooBigTitle" xml:space="preserve">
<value>Attention</value>
</data>
<data name="SettingsMenuItemHistory" xml:space="preserve">
<value>History</value>
</data>
<data name="SettingsMenuItemRecycleBin" xml:space="preserve">
<value>Recycle Bin</value>
</data>
<data name="GroupDeletingConfirmation" xml:space="preserve">
<value>Are you sure you want to delete the {0} and all its entries?</value>
</data>
</root>

View File

@@ -121,7 +121,7 @@
<value>Dominik Reichl for the KeePass application and file format</value>
</data>
<data name="AboutCredits2.Text" xml:space="preserve">
<value>David Lechner for his PCL adapatation of the KeePass Library and his correlated tests</value>
<value>David Lechner for his PCL adapatation of the KeePass Library and his related tests</value>
</data>
<data name="AboutCreditsLabel.Text" xml:space="preserve">
<value>Credits</value>
@@ -297,9 +297,6 @@
<data name="SettingsDatabaseKdf.Text" xml:space="preserve">
<value>Key Derivation Algorithm</value>
</data>
<data name="SettingsDatabaseRecycleBin.Header" xml:space="preserve">
<value>Recycle bin</value>
</data>
<data name="SettingsDatabaseRecycleBin.OffContent" xml:space="preserve">
<value>Disabled</value>
</data>
@@ -516,4 +513,55 @@
<data name="NewGroupTextBox.ButtonTooltip" xml:space="preserve">
<value>New group name</value>
</data>
<data name="EntryHubAdditional.Header" xml:space="preserve">
<value>Additional</value>
</data>
<data name="EntryHubAttachments.Header" xml:space="preserve">
<value>Attachments</value>
</data>
<data name="EntryHubMain.Header" xml:space="preserve">
<value>Main</value>
</data>
<data name="EntryHubPresentation.Header" xml:space="preserve">
<value>Presentation</value>
</data>
<data name="EntryIcon.Text" xml:space="preserve">
<value>Icon</value>
</data>
<data name="EntryAddAttachment.Text" xml:space="preserve">
<value>Add attachment</value>
</data>
<data name="EntryAddAdditionalField.Text" xml:space="preserve">
<value>Add field</value>
</data>
<data name="EntryDeleteAdditionalField.Content" xml:space="preserve">
<value>Delete</value>
</data>
<data name="ReorderGroupsLabel.Text" xml:space="preserve">
<value>Drag and drop groups to reorder them</value>
</data>
<data name="EntryAdditionalFieldNameReserved.Text" xml:space="preserve">
<value>Invalid field name</value>
</data>
<data name="EntryEnableFieldProtection.Header" xml:space="preserve">
<value>Enable protection ?</value>
</data>
<data name="EntryEnableFieldProtection.OffContent" xml:space="preserve">
<value>No</value>
</data>
<data name="EntryEnableFieldProtection.OnContent" xml:space="preserve">
<value>Yes</value>
</data>
<data name="TopMenuRestoreButton.Content" xml:space="preserve">
<value>Restore</value>
</data>
<data name="SettingsHistoryMaxCount.Text" xml:space="preserve">
<value>Max history items</value>
</data>
<data name="SettingsHistoryMaxSize.Text" xml:space="preserve">
<value>Max history size (MB)</value>
</data>
<data name="SettingsCopyExpiration.Text" xml:space="preserve">
<value>Delete copied value from clipboard after how many seconds ?</value>
</data>
</root>

View File

@@ -147,35 +147,17 @@
<data name="EntityRestoredTitle" xml:space="preserve">
<value>Restauré</value>
</data>
<data name="EntryDeleted" xml:space="preserve">
<value>Entrée supprimée définitivement</value>
</data>
<data name="EntryDeletingConfirmation" xml:space="preserve">
<value>Êtes-vous sûr de vouloir supprimer cette entrée ?</value>
<value>Êtes-vous sûr de vouloir supprimer {0} ?</value>
</data>
<data name="EntryRecycled" xml:space="preserve">
<value>Entrée placée dans la Corbeille</value>
</data>
<data name="EntryRecyclingConfirmation" xml:space="preserve">
<value>Êtes-vous sûr de vouloir placer cette entrée dans la Corbeille ?</value>
</data>
<data name="EntryRestored" xml:space="preserve">
<value>Entrée replacée dans son groupe d'origine</value>
</data>
<data name="GroupDeleted" xml:space="preserve">
<value>Groupe supprimé définitivement</value>
<value>{0} placé dans la Corbeille</value>
</data>
<data name="GroupDeletingConfirmation" xml:space="preserve">
<value>Êtes-vous sûr de vouloir supprimer ce groupe et toutes ses entrées ?</value>
<value>Êtes-vous sûr de vouloir supprimer {0} et toutes ses entrées ?</value>
</data>
<data name="GroupRecycled" xml:space="preserve">
<value>Groupe placé dans la Corbeille</value>
</data>
<data name="GroupRecyclingConfirmation" xml:space="preserve">
<value>Êtes-vous sûr de vouloir envoyer ce groupe et toutes ses entrées vers la Corbeille ?</value>
</data>
<data name="GroupRestored" xml:space="preserve">
<value>Groupe replacé à sa place originelle</value>
<value>{0} placé dans la Corbeille</value>
</data>
<data name="MainMenuItemAbout" xml:space="preserve">
<value>A propos</value>
@@ -204,9 +186,6 @@
<data name="SettingsMenuGroupDatabase" xml:space="preserve">
<value>Base de données</value>
</data>
<data name="SettingsMenuItemGeneral" xml:space="preserve">
<value>Général</value>
</data>
<data name="SettingsMenuItemNew" xml:space="preserve">
<value>Nouveau</value>
</data>
@@ -216,9 +195,6 @@
<data name="CompositeKeyErrorUserAccount" xml:space="preserve">
<value>- compte utilisateur</value>
</data>
<data name="SettingsMenuItemSave" xml:space="preserve">
<value>Sauvegardes</value>
</data>
<data name="RecycleBinTitle" xml:space="preserve">
<value>Corbeille</value>
</data>
@@ -276,4 +252,22 @@
<data name="CompositeKeyFileNameSuggestion" xml:space="preserve">
<value>Clé</value>
</data>
<data name="DatabaseTooBigDescription" xml:space="preserve">
<value>La base de données est trop grosse pour sauvegarder automatiquement lors de la suspension. Pensez à bien sauvegarder vos changements avant de fermer l'app !</value>
</data>
<data name="DatabaseTooBigTitle" xml:space="preserve">
<value>Attention</value>
</data>
<data name="SettingsMenuItemCredentials" xml:space="preserve">
<value>Identifiants</value>
</data>
<data name="SettingsMenuItemHistory" xml:space="preserve">
<value>Historique</value>
</data>
<data name="SettingsMenuItemRecycleBin" xml:space="preserve">
<value>Corbeille</value>
</data>
<data name="SettingsMenuItemGeneral" xml:space="preserve">
<value>Général</value>
</data>
</root>

View File

@@ -297,9 +297,6 @@
<data name="SettingsDatabaseKdf.Text" xml:space="preserve">
<value>Algorithme de dérivation de clé</value>
</data>
<data name="SettingsDatabaseRecycleBin.Header" xml:space="preserve">
<value>Corbeille</value>
</data>
<data name="SettingsDatabaseRecycleBin.OffContent" xml:space="preserve">
<value>Désactivé</value>
</data>
@@ -516,4 +513,55 @@
<data name="NewGroupTextBox.ButtonTooltip" xml:space="preserve">
<value>Nom du groupe</value>
</data>
<data name="EntryHubAdditional.Header" xml:space="preserve">
<value>Additionnel</value>
</data>
<data name="EntryHubAttachments.Header" xml:space="preserve">
<value>Pièce jointes</value>
</data>
<data name="EntryHubMain.Header" xml:space="preserve">
<value>Principal</value>
</data>
<data name="EntryHubPresentation.Header" xml:space="preserve">
<value>Affichage</value>
</data>
<data name="EntryIcon.Text" xml:space="preserve">
<value>Icone</value>
</data>
<data name="EntryAddAttachment.Text" xml:space="preserve">
<value>Ajouter une pièce jointe</value>
</data>
<data name="EntryDeleteAdditionalField.Content" xml:space="preserve">
<value>Supprimer</value>
</data>
<data name="ReorderGroupsLabel.Text" xml:space="preserve">
<value>Drag and drop groups to reorder them</value>
</data>
<data name="EntryAdditionalFieldNameReserved.Text" xml:space="preserve">
<value>Nom de champ invalide</value>
</data>
<data name="EntryEnableFieldProtection.Header" xml:space="preserve">
<value>Activer la protection ?</value>
</data>
<data name="EntryEnableFieldProtection.OffContent" xml:space="preserve">
<value>Non</value>
</data>
<data name="EntryEnableFieldProtection.OnContent" xml:space="preserve">
<value>Oui</value>
</data>
<data name="TopMenuRestoreButton.Content" xml:space="preserve">
<value>Restaurer</value>
</data>
<data name="SettingsHistoryMaxCount.Text" xml:space="preserve">
<value>Nombre d'éléments d'historique max</value>
</data>
<data name="SettingsHistoryMaxSize.Text" xml:space="preserve">
<value>Taille de l'historique (MO)</value>
</data>
<data name="SettingsCopyExpiration.Text" xml:space="preserve">
<value>Supprimer la valeur copiée dans le presse papier après combien de secondes ?</value>
</data>
<data name="EntryAddAdditionalField.Text" xml:space="preserve">
<value>Ajouter un champ</value>
</data>
</root>

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
@@ -14,10 +13,13 @@ using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Entry.Commands.AddAttachment;
using ModernKeePass.Application.Entry.Commands.AddHistory;
using ModernKeePass.Application.Entry.Commands.DeleteAttachment;
using ModernKeePass.Application.Entry.Commands.DeleteField;
using ModernKeePass.Application.Entry.Commands.DeleteHistory;
using ModernKeePass.Application.Entry.Commands.RestoreHistory;
using ModernKeePass.Application.Entry.Commands.SetFieldValue;
using ModernKeePass.Application.Entry.Commands.UpsertField;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Application.Entry.Queries.GetEntry;
using ModernKeePass.Application.Group.Commands.AddEntry;
@@ -29,25 +31,67 @@ using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Common;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Extensions;
using ModernKeePass.Models;
using ModernKeePass.ViewModels.ListItems;
namespace ModernKeePass.ViewModels
{
public class EntryDetailVm : ViewModelBase
{
public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password);
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;
public bool MinusPatternSelected { get; set; }
public bool UnderscorePatternSelected { get; set; }
public bool SpacePatternSelected { get; set; }
public bool SpecialPatternSelected { get; set; }
public bool BracketsPatternSelected { get; set; }
public double PasswordLength
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.PasswordLength, 25); }
set
{
_settings.PutSetting(Constants.Settings.PasswordGenerationOptions.PasswordLength, value);
RaisePropertyChanged(nameof(PasswordLength));
}
}
public bool UpperCasePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.UpperCasePattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.UpperCasePattern, value); }
}
public bool LowerCasePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.LowerCasePattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.LowerCasePattern, value); }
}
public bool DigitsPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.DigitsPattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.DigitsPattern, value); }
}
public bool MinusPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.MinusPattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.MinusPattern, value); }
}
public bool UnderscorePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.UnderscorePattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.UnderscorePattern, value); }
}
public bool SpacePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.SpacePattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.SpacePattern, value); }
}
public bool SpecialPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.SpecialPattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.SpecialPattern, value); }
}
public bool BracketsPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.BracketsPattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.BracketsPattern, value); }
}
public string CustomChars { get; set; } = string.Empty;
public string Id => SelectedItem.Id;
@@ -62,9 +106,10 @@ namespace ModernKeePass.ViewModels
return database.IsRecycleBinEnabled && _parent.Id != database.RecycleBinId;
}
}
public IEnumerable<GroupVm> BreadCrumb => new List<GroupVm> { _parent };
public ObservableCollection<EntryVm> History { get; private set; }
public ObservableCollection<EntryFieldVm> AdditionalFields { get; private set; }
public ObservableCollection<Attachment> Attachments { get; private set; }
/// <summary>
/// Determines if the Entry is current or from history
@@ -76,10 +121,26 @@ namespace ModernKeePass.ViewModels
get { return _selectedItem; }
set
{
Set(() => SelectedItem, ref _selectedItem, value);
if (value != null) RaisePropertyChanged();
Set(() => SelectedItem, ref _selectedItem, value, true);
if (value != null)
{
AdditionalFields =
new ObservableCollection<EntryFieldVm>(
SelectedItem.AdditionalFields.Select(f => new EntryFieldVm(f.Name, f.Value, f.IsProtected)));
Attachments = new ObservableCollection<Attachment>(SelectedItem.Attachments.Select(f => new Attachment
{
Name = f.Key,
Content = f.Value
}));
Attachments.CollectionChanged += (sender, args) =>
{
UpdateDirtyStatus(true);
};
RaisePropertyChanged(string.Empty);
}
}
}
public int SelectedIndex
{
get { return _selectedIndex; }
@@ -87,43 +148,49 @@ namespace ModernKeePass.ViewModels
{
Set(() => SelectedIndex, ref _selectedIndex, value);
RaisePropertyChanged(nameof(IsCurrentEntry));
AddAttachmentCommand.RaiseCanExecuteChanged();
}
}
public double PasswordLength
public int AdditionalFieldSelectedIndex
{
get { return _passwordLength; }
set { Set(() => PasswordLength, ref _passwordLength, value); }
get { return _additionalFieldSelectedIndex; }
set
{
Set(() => AdditionalFieldSelectedIndex, ref _additionalFieldSelectedIndex, value);
DeleteAdditionalField.RaiseCanExecuteChanged();
}
}
public string Title
{
get { return SelectedItem.Title; }
get { return SelectedItem.Title.Value; }
set
{
SelectedItem.Title = value;
SetFieldValue(nameof(Title), value).Wait();
SelectedItem.Title.Value = value;
SetFieldValue(nameof(Title), value, false).Wait();
}
}
public string UserName
{
get { return SelectedItem.Username; }
get { return SelectedItem.Username.Value; }
set
{
SelectedItem.Username = value;
SetFieldValue(nameof(UserName), value).Wait();
SelectedItem.Username.Value = value;
SetFieldValue(nameof(UserName), value, false).Wait();
RaisePropertyChanged(nameof(UserName));
}
}
public string Password
{
get { return SelectedItem.Password; }
get { return SelectedItem.Password.Value; }
set
{
SelectedItem.Password = value;
SetFieldValue(nameof(Password), value).Wait();
SelectedItem.Password.Value = value;
SetFieldValue(nameof(Password), value, true).Wait();
RaisePropertyChanged(nameof(Password));
RaisePropertyChanged(nameof(PasswordComplexityIndicator));
}
@@ -131,22 +198,22 @@ namespace ModernKeePass.ViewModels
public string Url
{
get { return SelectedItem.Url; }
get { return SelectedItem.Url.Value; }
set
{
SelectedItem.Url = value;
SetFieldValue(nameof(Url), value).Wait();
SelectedItem.Url.Value = value;
SetFieldValue(nameof(Url), value, false).Wait();
RaisePropertyChanged(nameof(Url));
}
}
public string Notes
{
get { return SelectedItem.Notes; }
get { return SelectedItem.Notes.Value; }
set
{
SelectedItem.Notes = value;
SetFieldValue(nameof(Notes), value).Wait();
SelectedItem.Notes.Value = value;
SetFieldValue(nameof(Notes), value, false).Wait();
}
}
@@ -156,7 +223,7 @@ namespace ModernKeePass.ViewModels
set
{
SelectedItem.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString());
SetFieldValue(nameof(Icon), SelectedItem.Icon).Wait();
SetFieldValue(nameof(Icon), SelectedItem.Icon, false).Wait();
}
}
@@ -168,7 +235,7 @@ namespace ModernKeePass.ViewModels
if (!HasExpirationDate) return;
SelectedItem.ExpirationDate = value.Date;
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait();
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate, false).Wait();
}
}
@@ -180,7 +247,7 @@ namespace ModernKeePass.ViewModels
if (!HasExpirationDate) return;
SelectedItem.ExpirationDate = SelectedItem.ExpirationDate.Date.Add(value);
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait();
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate, false).Wait();
}
}
@@ -190,7 +257,7 @@ namespace ModernKeePass.ViewModels
set
{
SelectedItem.HasExpirationDate = value;
SetFieldValue(nameof(HasExpirationDate), value).Wait();
SetFieldValue(nameof(HasExpirationDate), value, false).Wait();
RaisePropertyChanged(nameof(HasExpirationDate));
}
}
@@ -201,7 +268,7 @@ namespace ModernKeePass.ViewModels
set
{
SelectedItem.BackgroundColor = value.ToColor();
SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor).Wait();
SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor, false).Wait();
}
}
@@ -211,11 +278,10 @@ namespace ModernKeePass.ViewModels
set
{
SelectedItem.ForegroundColor = value.ToColor();
SetFieldValue(nameof(ForegroundColor), SelectedItem.ForegroundColor).Wait();
SetFieldValue(nameof(ForegroundColor), SelectedItem.ForegroundColor, false).Wait();
}
}
public bool IsEditMode
{
get { return IsCurrentEntry && _isEditMode; }
@@ -235,29 +301,38 @@ namespace ModernKeePass.ViewModels
public RelayCommand DeleteCommand { get; }
public RelayCommand GoBackCommand { get; }
public RelayCommand GoToParentCommand { get; set; }
public RelayCommand AddAdditionalField { get; set; }
public RelayCommand<EntryFieldVm> DeleteAdditionalField { get; set; }
public RelayCommand<Attachment> OpenAttachmentCommand { get; set; }
public RelayCommand AddAttachmentCommand { get; set; }
public RelayCommand<Attachment> DeleteAttachmentCommand { get; set; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
private readonly IMediator _mediator;
private readonly INavigationService _navigation;
private readonly IResourceProxy _resource;
private readonly IDialogService _dialog;
private readonly INotificationService _notification;
private readonly IFileProxy _file;
private readonly ISettingsProxy _settings;
private GroupVm _parent;
private EntryVm _selectedItem;
private int _selectedIndex;
private int _additionalFieldSelectedIndex = -1;
private bool _isEditMode;
private bool _isRevealPassword;
private double _passwordLength = 25;
private bool _isDirty;
public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification)
public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification, IFileProxy file, ISettingsProxy settings)
{
_mediator = mediator;
_navigation = navigation;
_resource = resource;
_dialog = dialog;
_notification = notification;
_file = file;
_settings = settings;
SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
@@ -266,12 +341,20 @@ namespace ModernKeePass.ViewModels
DeleteCommand = new RelayCommand(async () => await AskForDelete());
GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToParentCommand = new RelayCommand(() => GoToGroup(_parent.Id));
AddAdditionalField = new RelayCommand(AddField, () => IsCurrentEntry);
DeleteAdditionalField = new RelayCommand<EntryFieldVm>(async field => await DeleteField(field), field => field != null && IsCurrentEntry);
OpenAttachmentCommand = new RelayCommand<Attachment>(async attachment => await OpenAttachment(attachment));
AddAttachmentCommand = new RelayCommand(async () => await AddAttachment(), () => IsCurrentEntry);
DeleteAttachmentCommand = new RelayCommand<Attachment>(async attachment => await DeleteAttachment(attachment), _ => IsCurrentEntry);
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
MessengerInstance.Register<EntryFieldValueChangedMessage>(this, async message => await SetFieldValue(message.FieldName, message.FieldValue, message.IsProtected));
MessengerInstance.Register<EntryFieldNameChangedMessage>(this, async message => await UpdateFieldName(message.OldName, message.NewName, message.Value, message.IsProtected));
}
public async Task Initialize(string entryId)
{
SelectedIndex = 0;
SelectedItem = await _mediator.Send(new GetEntryQuery { Id = entryId });
_parent = await _mediator.Send(new GetGroupQuery { Id = SelectedItem.ParentGroupId });
History = new ObservableCollection<EntryVm> { SelectedItem };
@@ -279,43 +362,11 @@ namespace ModernKeePass.ViewModels
{
History.Add(entry);
}
SelectedIndex = 0;
}
private async Task AskForDelete()
{
if (IsCurrentEntry)
History.CollectionChanged += (sender, args) =>
{
if (IsRecycleOnDelete)
{
await Delete();
_notification.Show(_resource.GetResourceValue("EntryRecyclingConfirmation"), _resource.GetResourceValue("EntryRecycled"));
}
else
{
await _dialog.ShowMessage(_resource.GetResourceValue("EntryDeletingConfirmation"),
_resource.GetResourceValue("EntityDeleteTitle"),
_resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"),
async isOk =>
{
if (isOk) await Delete();
});
}
}
else
{
await _dialog.ShowMessage(_resource.GetResourceValue("HistoryDeleteDescription"), _resource.GetResourceValue("HistoryDeleteTitle"),
_resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"), async isOk =>
{
if (!isOk) return;
await _mediator.Send(new DeleteHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 });
History.RemoveAt(SelectedIndex);
SelectedIndex = 0;
SaveCommand.RaiseCanExecuteChanged();
});
}
SelectedIndex = 0;
SaveCommand.RaiseCanExecuteChanged();
};
}
public async Task GeneratePassword()
@@ -333,21 +384,6 @@ namespace ModernKeePass.ViewModels
UnderscorePatternSelected = UnderscorePatternSelected,
UpperCasePatternSelected = UpperCasePatternSelected
});
RaisePropertyChanged(nameof(IsRevealPasswordEnabled));
}
public async Task Move(string destination)
{
await _mediator.Send(new AddEntryCommand { ParentGroupId = destination, EntryId = Id });
await _mediator.Send(new RemoveEntryCommand { ParentGroupId = _parent.Id, EntryId = Id });
GoToGroup(destination);
}
public async Task SetFieldValue(string fieldName, object value)
{
await _mediator.Send(new SetFieldValueCommand { EntryId = Id, FieldName = fieldName, FieldValue = value });
SaveCommand.RaiseCanExecuteChanged();
_isDirty = true;
}
public async Task AddHistory()
@@ -355,12 +391,86 @@ namespace ModernKeePass.ViewModels
if (_isDirty) await _mediator.Send(new AddHistoryCommand { Entry = History[0] });
}
public void GoToGroup(string groupId)
{
_navigation.NavigateTo(Constants.Navigation.GroupPage, new NavigationItem { Id = groupId });
}
private async Task Move(string destination)
{
await _mediator.Send(new AddEntryCommand { ParentGroupId = destination, EntryId = Id });
await _mediator.Send(new RemoveEntryCommand { ParentGroupId = _parent.Id, EntryId = Id });
GoToGroup(destination);
}
private async Task SetFieldValue(string fieldName, object value, bool isProtected)
{
await _mediator.Send(new UpsertFieldCommand { EntryId = Id, FieldName = fieldName, FieldValue = value, IsProtected = isProtected});
UpdateDirtyStatus(true);
}
private async Task UpdateFieldName(string oldName, string newName, string value, bool isProtected)
{
if (!string.IsNullOrEmpty(oldName)) await _mediator.Send(new DeleteFieldCommand { EntryId = Id, FieldName = oldName });
await SetFieldValue(newName, value, isProtected);
}
private void AddField()
{
AdditionalFields.Add(new EntryFieldVm(string.Empty, string.Empty, false));
AdditionalFieldSelectedIndex = AdditionalFields.Count - 1;
}
private async Task DeleteField(EntryFieldVm field)
{
AdditionalFields.Remove(field);
if (!string.IsNullOrEmpty(field.Name))
{
await _mediator.Send(new DeleteFieldCommand {EntryId = Id, FieldName = field.Name});
UpdateDirtyStatus(true);
}
}
private async Task AskForDelete()
{
if (IsCurrentEntry)
{
if (IsRecycleOnDelete)
{
await Delete();
_notification.Show(_resource.GetResourceValue("EntityDeleting"), string.Format(_resource.GetResourceValue("EntryRecycled"), Title));
}
else
{
await _dialog.ShowMessage(
string.Format(_resource.GetResourceValue("EntryDeletingConfirmation"), Title),
_resource.GetResourceValue("EntityDeleting"),
_resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"),
async isOk =>
{
if (isOk) await Delete();
});
}
}
else
{
await _dialog.ShowMessage(_resource.GetResourceValue("HistoryDeleteDescription"),
_resource.GetResourceValue("HistoryDeleteTitle"),
_resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"), async isOk =>
{
if (!isOk) return;
await _mediator.Send(new DeleteHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 });
History.RemoveAt(SelectedIndex);
});
}
}
private async Task RestoreHistory()
{
await _mediator.Send(new RestoreHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 });
History.Insert(0, SelectedItem);
SelectedIndex = 0;
SaveCommand.RaiseCanExecuteChanged();
}
private async Task SaveChanges()
@@ -374,8 +484,7 @@ namespace ModernKeePass.ViewModels
{
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
}
SaveCommand.RaiseCanExecuteChanged();
_isDirty = false;
UpdateDirtyStatus(false);
}
private async Task Delete()
@@ -386,12 +495,40 @@ namespace ModernKeePass.ViewModels
ParentGroupId = SelectedItem.ParentGroupId,
RecycleBinName = _resource.GetResourceValue("RecycleBinTitle")
});
_isDirty = false;
_navigation.GoBack();
}
public void GoToGroup(string groupId)
private async Task OpenAttachment(Attachment attachment)
{
_navigation.NavigateTo(Constants.Navigation.GroupPage, new NavigationItem { Id = groupId });
var extensionIndex = attachment.Name.LastIndexOf('.');
var fileInfo = await _file.CreateFile(attachment.Name,
attachment.Name.Substring(extensionIndex, attachment.Name.Length - extensionIndex),
string.Empty,
false);
if (fileInfo == null) return;
await _file.WriteBinaryContentsToFile(fileInfo.Id, attachment.Content);
}
private async Task AddAttachment()
{
var fileInfo = await _file.OpenFile(string.Empty, Domain.Common.Constants.Extensions.Any, false);
if (fileInfo == null) return;
var contents = await _file.ReadBinaryFile(fileInfo.Id);
await _mediator.Send(new AddAttachmentCommand { Entry = SelectedItem, AttachmentName = fileInfo.Name, AttachmentContent = contents });
Attachments.Add(new Attachment { Name = fileInfo.Name, Content = contents });
}
private async Task DeleteAttachment(Attachment attachment)
{
await _mediator.Send(new DeleteAttachmentCommand { Entry = SelectedItem, AttachmentName = attachment.Name });
Attachments.Remove(attachment);
}
private void UpdateDirtyStatus(bool isDirty)
{
SaveCommand.RaiseCanExecuteChanged();
_isDirty = isDirty;
}
}
}

View File

@@ -20,6 +20,7 @@ using ModernKeePass.Application.Group.Commands.CreateEntry;
using ModernKeePass.Application.Group.Commands.CreateGroup;
using ModernKeePass.Application.Group.Commands.DeleteGroup;
using ModernKeePass.Application.Group.Commands.MoveEntry;
using ModernKeePass.Application.Group.Commands.MoveGroup;
using ModernKeePass.Application.Group.Commands.RemoveGroup;
using ModernKeePass.Application.Group.Commands.SortEntries;
using ModernKeePass.Application.Group.Commands.SortGroups;
@@ -42,7 +43,7 @@ namespace ModernKeePass.ViewModels
public bool IsNotRoot => Database.RootGroupId != _group.Id;
public IOrderedEnumerable<IGrouping<char, EntryVm>> EntriesZoomedOut => from e in Entries
group e by e.Title.ToUpper().FirstOrDefault() into grp
group e by (e.Title.Value ?? string.Empty).ToUpper().FirstOrDefault() into grp
orderby grp.Key
select grp;
@@ -106,7 +107,8 @@ namespace ModernKeePass.ViewModels
private GroupVm _parent;
private bool _isEditMode;
private EntryVm _reorderedEntry;
private GroupVm _reorderedGroup;
public GroupDetailVm(IMediator mediator, IResourceProxy resource, INavigationService navigation, IDialogService dialog, INotificationService notification)
{
_mediator = mediator;
@@ -139,6 +141,7 @@ namespace ModernKeePass.ViewModels
Entries = new ObservableCollection<EntryVm>(_group.Entries);
Entries.CollectionChanged += Entries_CollectionChanged;
Groups = new ObservableCollection<GroupVm>(_group.SubGroups);
Groups.CollectionChanged += Groups_CollectionChanged;
}
public void GoToEntry(string entryId, bool isNew = false)
@@ -213,6 +216,29 @@ namespace ModernKeePass.ViewModels
SaveCommand.RaiseCanExecuteChanged();
}
private async void Groups_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
var oldIndex = e.OldStartingIndex;
_reorderedGroup = _group.SubGroups[oldIndex];
break;
case NotifyCollectionChangedAction.Add:
if (_reorderedGroup == null)
{
var group = (GroupVm)e.NewItems[0];
await _mediator.Send(new AddGroupCommand() { GroupId = group.Id, ParentGroupId = Id });
}
else
{
await _mediator.Send(new MoveGroupCommand { Group = _reorderedGroup, ParentGroup = _group, Index = e.NewStartingIndex });
}
break;
}
SaveCommand.RaiseCanExecuteChanged();
}
private async Task SortEntriesAsync()
{
await _mediator.Send(new SortEntriesCommand {Group = _group});
@@ -235,11 +261,13 @@ namespace ModernKeePass.ViewModels
if (IsRecycleOnDelete)
{
await Delete();
_notification.Show(_resource.GetResourceValue("GroupRecyclingConfirmation"), _resource.GetResourceValue("GroupRecycled"));
_notification.Show(_resource.GetResourceValue("EntityDeleting"), string.Format(_resource.GetResourceValue("GroupRecycled"), Title));
}
else
{
await _dialog.ShowMessage(_resource.GetResourceValue("GroupDeletingConfirmation"), _resource.GetResourceValue("EntityDeleteTitle"),
await _dialog.ShowMessage(
string.Format(_resource.GetResourceValue("GroupDeletingConfirmation"), Title),
_resource.GetResourceValue("EntityDeleting"),
_resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"),
async isOk =>

View File

@@ -8,6 +8,7 @@ using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Domain.Interfaces;
using ModernKeePass.ViewModels.ListItems;
using ModernKeePass.Views;
using ModernKeePass.Views.SettingsPageFrames;
namespace ModernKeePass.ViewModels
{
@@ -57,19 +58,11 @@ namespace ModernKeePass.ViewModels
IsSelected = true
},
new ListMenuItemVm
{
Title = resource.GetResourceValue("SettingsMenuItemSave"),
Group = resource.GetResourceValue("SettingsMenuGroupApplication"),
SymbolIcon = Symbol.Save,
PageType = typeof(SettingsSavePage)
},
new ListMenuItemVm
{
Title = resource.GetResourceValue("SettingsMenuItemGeneral"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
Group = resource.GetResourceValue("SettingsMenuGroupApplication"),
SymbolIcon = Symbol.Setting,
PageType = typeof(SettingsDatabasePage),
IsEnabled = database.IsOpen
PageType = typeof(SettingsGeneralPage)
},
new ListMenuItemVm
{
@@ -78,6 +71,30 @@ namespace ModernKeePass.ViewModels
SymbolIcon = Symbol.Permissions,
PageType = typeof(SettingsSecurityPage),
IsEnabled = database.IsOpen
},
new ListMenuItemVm
{
Title = resource.GetResourceValue("SettingsMenuItemHistory"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Undo,
PageType = typeof(SettingsHistoryPage),
IsEnabled = database.IsOpen
},
new ListMenuItemVm
{
Title = resource.GetResourceValue("SettingsMenuItemRecycleBin"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Delete,
PageType = typeof(SettingsRecycleBinPage),
IsEnabled = database.IsOpen
},
new ListMenuItemVm
{
Title = resource.GetResourceValue("SettingsMenuItemCredentials"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Account,
PageType = typeof(SettingsCredentialsPage),
IsEnabled = database.IsOpen
}
};
SelectedItem = menuItems.FirstOrDefault(m => m.IsSelected);

View File

@@ -1,4 +1,4 @@
<Page
<Page x:Name="Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -380,84 +380,194 @@
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Grid Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20">
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="18"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
<Style TargetType="CheckBox">
<Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</StackPanel.Resources>
<TextBlock x:Uid="EntryLogin" />
<local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding UserName}" />
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryPassword" />
<local:PasswordBoxWithButton Password="{Binding Password, Mode=TwoWay}" IsPasswordRevealEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsCurrentEntry}" ButtonSymbol="&#xE15E;" />
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Password}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" />
<CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" IsEnabled="{Binding IsRevealPasswordEnabled}" />
<TextBlock TextWrapping="Wrap" Text="URL" FontSize="18"/>
<local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:NavigateToUrlAction Url="{Binding Url}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryNotes" />
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" />
<CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<SymbolIcon Grid.Column="0" Symbol="Important" Foreground="DarkRed" Visibility="{Binding HasExpired, Converter={StaticResource BooleanToVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="EntryExpirationTooltip" />
</ToolTipService.ToolTip>
</SymbolIcon>
<StackPanel Grid.Column="1" x:Name="ExpirationDatePanel" Orientation="Horizontal" Visibility="{Binding HasExpirationDate, Converter={StaticResource BooleanToVisibilityConverter}}">
<DatePicker Margin="0,0,20,0" Date="{Binding ExpiryDate, Mode=TwoWay}" />
<TimePicker Time="{Binding ExpiryTime, Mode=TwoWay}" />
<Hub Padding="0">
<Hub.Resources>
<Style TargetType="TextBlock" x:Key="EntryTextBlockStyle">
<Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="16"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</Hub.Resources>
<HubSection x:Uid="EntryHubMain">
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20" MinWidth="400">
<StackPanel.Resources>
<Style TargetType="CheckBox">
<Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="18"/>
</Style>
</StackPanel.Resources>
<TextBlock x:Uid="EntryLogin" Style="{StaticResource EntryTextBlockStyle}" />
<local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding UserName}" />
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryPassword" Style="{StaticResource EntryTextBlockStyle}" />
<local:PasswordBoxWithButton Password="{Binding Password, Mode=TwoWay}" IsPasswordRevealEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsCurrentEntry}" ButtonSymbol="&#xE15E;" />
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Password}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" />
<CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" />
<TextBlock Text="URL" Style="{StaticResource EntryTextBlockStyle}"/>
<local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:NavigateToUrlAction Url="{Binding Url}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryNotes" Style="{StaticResource EntryTextBlockStyle}" />
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" />
<CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<SymbolIcon Grid.Column="0" Symbol="Important" Foreground="DarkRed" Visibility="{Binding HasExpired, Converter={StaticResource BooleanToVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="EntryExpirationTooltip" />
</ToolTipService.ToolTip>
</SymbolIcon>
<StackPanel Grid.Column="1" x:Name="ExpirationDatePanel" Orientation="Vertical" Visibility="{Binding HasExpirationDate, Converter={StaticResource BooleanToVisibilityConverter}}">
<DatePicker Margin="10,5,20,0" Date="{Binding ExpiryDate, Mode=TwoWay}" />
<TimePicker Margin="10,10,0,0" Time="{Binding ExpiryTime, Mode=TwoWay}" />
</StackPanel>
</Grid>
</StackPanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Small">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Horizontal"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</ScrollViewer>
</DataTemplate>
</HubSection>
<HubSection x:Uid="EntryHubPresentation">
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock x:Uid="EntryIcon" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:SymbolPickerUserControl SelectedSymbol="{Binding Icon, Mode=TwoWay}" HorizontalAlignment="Left" IsEnabled="{Binding IsCurrentEntry}" />
<TextBlock x:Uid="EntryBackgroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" />
<TextBlock x:Uid="EntryForegroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" />
</StackPanel>
</Grid>
<StackPanel x:Name="EditDesign" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Orientation="Horizontal">
<StackPanel Width="250" HorizontalAlignment="Left">
<TextBlock x:Uid="EntryBackgroundColor" />
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</StackPanel>
<StackPanel Width="250" HorizontalAlignment="Left">
<TextBlock x:Uid="EntryForegroundColor" />
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</DataTemplate>
</HubSection>
<HubSection x:Uid="EntryHubAdditional">
<DataTemplate>
<local:SelectableTemplateListView
ItemsSource="{Binding AdditionalFields}"
SelectedIndex="{Binding AdditionalFieldSelectedIndex, Mode=TwoWay}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<local:SelectableTemplateListView.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<HyperlinkButton Command="{Binding Path=DataContext.AddAdditionalField, ElementName=Page}">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Add" />
<TextBlock x:Uid="EntryAddAdditionalField" VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
</HyperlinkButton>
<Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
</StackPanel>
</DataTemplate>
</local:SelectableTemplateListView.HeaderTemplate>
<local:SelectableTemplateListView.SelectedItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Name, Mode=TwoWay}" Width="350"
IsEnabled="{Binding Path=DataContext.IsCurrentEntry, ElementName=Page}" />
<TextBox Grid.Row="1" AcceptsReturn="True" Height="100" TextWrapping="Wrap" Width="350" Margin="0,5,0,0"
Text="{Binding Value, Mode=TwoWay}"
IsEnabled="{Binding Path=DataContext.IsCurrentEntry, ElementName=Page}" />
<ToggleSwitch Grid.Row="2" x:Uid="EntryEnableFieldProtection" HorizontalAlignment="Left" IsOn="{Binding IsProtected, Mode=TwoWay}" />
<Button Grid.Row="2" x:Uid="EntryDeleteAdditionalField" HorizontalAlignment="Right" Margin="0,15,0,0" Command="{Binding Path=DataContext.DeleteAdditionalField, ElementName=Page}" CommandParameter="{Binding}" />
</Grid>
</DataTemplate>
</local:SelectableTemplateListView.SelectedItemTemplate>
<local:SelectableTemplateListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="{Binding Name}" Style="{StaticResource EntryTextBlockStyle}" FontWeight="SemiBold" />
<TextBlock HorizontalAlignment="Left" MaxLines="3" FontSize="12" Margin="2,0,2,5" Text="*****" Visibility="{Binding IsProtected, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource EntryTextBlockStyle}"/>
<TextBlock HorizontalAlignment="Left" MaxLines="3" FontSize="12" Margin="2,0,2,5" Text="{Binding Value}" Visibility="{Binding IsProtected, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource EntryTextBlockStyle}"/>
</StackPanel>
</DataTemplate>
</local:SelectableTemplateListView.ItemTemplate>
</local:SelectableTemplateListView>
</DataTemplate>
</HubSection>
<HubSection x:Uid="EntryHubAttachments" Foreground="{StaticResource HubSectionBrush}">
<DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<HyperlinkButton Command="{Binding Path=DataContext.AddAttachmentCommand, ElementName=Page}">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Attach" />
<TextBlock x:Uid="EntryAddAttachment" VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
</HyperlinkButton>
<Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
<ItemsControl ItemsSource="{Binding Attachments}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<HyperlinkButton Content="{Binding Name}" Command="{Binding Path=DataContext.OpenAttachmentCommand, ElementName=Page}" CommandParameter="{Binding}" />
<HyperlinkButton Grid.Column="1" Margin="10,0,0,0" HorizontalAlignment="Right" Command="{Binding Path=DataContext.DeleteAttachmentCommand, ElementName=Page}" CommandParameter="{Binding}">
<SymbolIcon Symbol="Delete" />
</HyperlinkButton>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</DataTemplate>
</HubSection>
</Hub>
</Grid>
</Grid>
<!-- Bouton Précédent et titre de la page -->
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
@@ -471,47 +581,41 @@
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" />
</Button>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
Command="{Binding GoToParentCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Up" />
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox
x:Uid="EntryTitle"
x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}"
Background="Transparent"
IsHitTestVisible="{Binding IsEditMode}"
FontSize="20"
FontWeight="Light"
TextWrapping="NoWrap"
MinWidth="360"
VerticalAlignment="Center">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
</StackPanel>
<Button Grid.Column="1"
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
Command="{Binding GoToParentCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Up" />
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox Grid.Column="2" MaxHeight="200">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox Grid.Column="3"
x:Uid="EntryTitle"
x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}"
Background="Transparent"
IsHitTestVisible="{Binding IsEditMode}"
FontSize="20"
FontWeight="Light"
TextWrapping="NoWrap"
VerticalAlignment="Center">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
<userControls:TopMenuUserControl
x:Name="TopMenu" Grid.Column="2"
x:Name="TopMenu" Grid.Column="4"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
MoveButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource BooleanToVisibilityConverter}}"
RestoreButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
@@ -526,29 +630,5 @@
</interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="Small">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="EditDesign" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Vertical"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ExpirationDatePanel" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Horizontal"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="EditDesign" Storyboard.TargetProperty="Orientation">
<DiscreteObjectKeyFrame KeyTime="0" Value="Horizontal"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>

View File

@@ -48,6 +48,7 @@
x:Name="HamburgerMenu"
x:Uid="GroupsLeftListView"
ItemsSource="{Binding Groups}"
CanDragItems="{Binding IsEditMode}"
SelectionChanged="groups_SelectionChanged"
ActionButtonCommand="{Binding CreateGroupCommand}"
IsButtonVisible="Visible" />
@@ -62,7 +63,6 @@
<RowDefinition Height="*" />
</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="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" Command="{Binding CreateEntryCommand}" HorizontalAlignment="Right">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Add">
@@ -83,17 +83,15 @@
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Entries"
TabIndex="1"
Margin="10,0,0,0"
SelectionChanged="entries_SelectionChanged"
IsSynchronizedWithCurrentItem="False"
BorderBrush="{StaticResource ListViewItemSelectedBackgroundThemeBrush}"
AllowDrop="True"
CanReorderItems="True"
CanDragItems="True"
DragItemsStarting="GridView_DragItemsStarting">
CanReorderItems="{Binding IsEditMode}"
CanDragItems="{Binding IsEditMode}">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<actions:SetupFocusAction TargetObject="{Binding ElementName=GridView}" />
<actions:SetupFocusAction TargetObject="{Binding}" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<GridView.ItemTemplate>
@@ -135,7 +133,7 @@
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</MenuFlyoutItem>
<MenuFlyoutItem x:Uid="EntryItemCopyUrl" IsEnabled="{Binding HasUrl}">
<MenuFlyoutItem x:Uid="EntryItemCopyUrl" IsEnabled="{Binding IsValidUrl}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<actions:NavigateToUrlAction Url="{Binding Url}" />
@@ -183,6 +181,8 @@
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
@@ -196,24 +196,23 @@
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" />
</Button>
<StackPanel Grid.Column="1" Orientation="Horizontal">
<Button
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
Command="{Binding GoToParentCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Up" />
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox
<Button Grid.Column="1"
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
Command="{Binding GoToParentCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Up" />
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox Grid.Column="2" MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox>
<Viewbox Grid.Column="2" MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox Grid.Column="3"
x:Uid="GroupTitle"
x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}"
@@ -222,20 +221,18 @@
FontSize="20"
FontWeight="Light"
TextWrapping="NoWrap"
MinWidth="360"
VerticalAlignment="Center">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
</StackPanel>
<userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="2"
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
<userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="4"
SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
SaveCommand="{Binding SaveCommand}"
@@ -252,22 +249,6 @@
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DragDropGroup">
<VisualState x:Name="Dragging">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="GridView" Storyboard.TargetProperty="BorderThickness">
<DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Dropped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="GridView" Storyboard.TargetProperty="BorderThickness">
<DiscreteObjectKeyFrame KeyTime="0" Value="0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="PageLayout">
<VisualState x:Name="Small">
<Storyboard>

View File

@@ -1,5 +1,4 @@
using Windows.ApplicationModel.DataTransfer;
using Windows.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using ModernKeePass.Application.Entry.Models;
@@ -74,12 +73,6 @@ namespace ModernKeePass.Views
e.DestinationItem.Item = e.SourceItem.Item;
}
}
private void GridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
e.Cancel = !Model.IsEditMode;
e.Data.RequestedOperation = DataPackageOperation.Move;
}
private void GroupDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
{

View File

@@ -53,7 +53,7 @@
</Button>
<TextBlock x:Name="TitleTextBox" Text="{Binding Name}" Grid.Column="1" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<controls:ListViewWithDisable
<controls:DisableListView
Grid.Column="0"
Grid.Row="1"
x:Name="MenuListView"
@@ -62,15 +62,15 @@
ItemsSource="{Binding Source={StaticResource MenuItemsSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<controls:ListViewWithDisable.ItemTemplate>
<controls:DisableListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding SymbolIcon}" />
<TextBlock Text="{Binding Title}" Margin="10,5,0,0" />
</StackPanel>
</DataTemplate>
</controls:ListViewWithDisable.ItemTemplate>
<controls:ListViewWithDisable.GroupStyle>
</controls:DisableListView.ItemTemplate>
<controls:DisableListView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
@@ -80,8 +80,8 @@
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</controls:ListViewWithDisable.GroupStyle>
</controls:ListViewWithDisable>
</controls:DisableListView.GroupStyle>
</controls:DisableListView>
<TextBlock x:Name="PageTitleTextBlock" Grid.Column="1" Grid.Row="0" FontSize="24" VerticalAlignment="Center" Margin="10,0,0,0" >
<Run Text="{Binding SelectedItem}" />
</TextBlock>

View File

@@ -3,14 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:ModernKeePass.Converters"
xmlns:userControls="using:ModernKeePass.Views.UserControls"
xmlns:controls="using:ModernKeePass.Controls"
x:Class="ModernKeePass.Views.RecentDatabasesPage"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=Recent}">
<Page.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Page.Resources>
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="40" />
@@ -22,11 +19,11 @@
<TextBlock x:Uid="RecentClear" VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
</HyperlinkButton>
<ListView Grid.Row="1"
<controls:SelectableTemplateListView Grid.Row="1"
SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding RecentItems}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<ListView.ItemTemplate>
<controls:SelectableTemplateListView.SelectedItemTemplate>
<DataTemplate>
<Grid Margin="10,0,10,0">
<Grid.RowDefinitions>
@@ -35,17 +32,24 @@
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Name}" Padding="5,0,0,0" />
<TextBlock Grid.Row="1" Text="{Binding Path}" Padding="5,0,0,0" FontSize="10" />
<userControls:OpenDatabaseUserControl Grid.Row="2"
HorizontalAlignment="Stretch" MinWidth="400" Margin="0,10,0,0"
Visibility="{Binding IsSelected, Converter={StaticResource BooleanToVisibilityConverter}}"
<userControls:OpenDatabaseUserControl Grid.Row="2"
HorizontalAlignment="Stretch" MinWidth="400" Margin="0,10,0,0"
DatabaseFilePath="{Binding Token}" />
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</controls:SelectableTemplateListView.SelectedItemTemplate>
<controls:SelectableTemplateListView.ItemTemplate>
<DataTemplate>
<StackPanel Margin="10,0,10,0" Orientation="Vertical">
<TextBlock Text="{Binding Name}" Padding="5,0,0,0" />
<TextBlock Text="{Binding Path}" Padding="5,0,0,0" FontSize="10" />
</StackPanel>
</DataTemplate>
</controls:SelectableTemplateListView.ItemTemplate>
</controls:SelectableTemplateListView>
</Grid>
</Page>

View File

@@ -48,7 +48,7 @@
</Button>
<TextBlock x:Name="TitleTextBox" x:Uid="SettingsTitle" Grid.Column="1" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
<controls:ListViewWithDisable
<controls:DisableListView
Grid.Column="0"
Grid.Row="1"
x:Name="MenuListView"
@@ -58,15 +58,15 @@
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<controls:ListViewWithDisable.ItemTemplate>
<controls:DisableListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding SymbolIcon}" />
<TextBlock Text="{Binding Title}" Margin="10,5,0,0" />
</StackPanel>
</DataTemplate>
</controls:ListViewWithDisable.ItemTemplate>
<controls:ListViewWithDisable.GroupStyle>
</controls:DisableListView.ItemTemplate>
<controls:DisableListView.GroupStyle>
<GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate>
<DataTemplate>
@@ -79,8 +79,8 @@
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</controls:ListViewWithDisable.GroupStyle>
</controls:ListViewWithDisable>
</controls:DisableListView.GroupStyle>
</controls:DisableListView>
<TextBlock x:Name="PageTitleTextBlock" Grid.Column="1" Grid.Row="0" FontSize="24" VerticalAlignment="Center" Margin="10,0,0,0" >
<Run Text="{Binding SelectedItem}" />
</TextBlock>

View File

@@ -0,0 +1,20 @@
<Page
x:Class="ModernKeePass.Views.SettingsCredentialsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:userControls="using:ModernKeePass.Views.UserControls"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=Credentials}">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Uid="SettingsSecurityTitle" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,0" />
<TextBlock TextWrapping="WrapWholeWords" Margin="5,0,0,0">
<Run x:Uid="SettingsSecurityDesc1" />
<Run x:Uid="SettingsSecurityDesc2" FontWeight="SemiBold" />
<Run x:Uid="SettingsSecurityDesc3" />
</TextBlock>
<userControls:SetCredentialsUserControl Margin="0,20,0,0" x:Uid="SettingsSecurityUpdateButton"/>
</StackPanel>
</Page>

View File

@@ -5,9 +5,9 @@ namespace ModernKeePass.Views
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsDatabasePage
public sealed partial class SettingsCredentialsPage
{
public SettingsDatabasePage()
public SettingsCredentialsPage()
{
InitializeComponent();
}

View File

@@ -1,15 +1,17 @@
<Page
x:Class="ModernKeePass.Views.SettingsSavePage"
x:Class="ModernKeePass.Views.SettingsGeneralPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=SettingsSave}">
DataContext="{Binding Source={StaticResource Locator}, Path=General}">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Uid="SettingsSaveDatabaseSuspendTitle" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,10"/>
<TextBlock x:Uid="SettingsSaveDatabaseSuspendDesc" TextWrapping="WrapWholeWords" Margin="5,0,0,10"/>
<ToggleSwitch x:Uid="SettingsSaveDatabaseSuspend" IsOn="{Binding IsSaveSuspend, Mode=TwoWay}" />
<TextBlock x:Uid="SettingsCopyExpiration" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<TextBox Text="{Binding CopyExpiration, Mode=TwoWay}" InputScope="Number" KeyDown="UIElement_OnKeyDown" />
</StackPanel>
</Page>

View File

@@ -0,0 +1,26 @@
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
using Windows.System;
using Windows.UI.Xaml.Input;
namespace ModernKeePass.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsGeneralPage
{
public SettingsGeneralPage()
{
InitializeComponent();
}
private void UIElement_OnKeyDown(object sender, KeyRoutedEventArgs e)
{
if ((e.Key < VirtualKey.NumberPad0 || e.Key > VirtualKey.NumberPad9) & (e.Key < VirtualKey.Number0 || e.Key > VirtualKey.Number9))
{
e.Handled = true;
}
}
}
}

View File

@@ -0,0 +1,16 @@
<Page
x:Class="ModernKeePass.Views.SettingsPageFrames.SettingsHistoryPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=History}">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Uid="SettingsHistoryMaxCount" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<TextBox Text="{Binding MaxCount, Mode=TwoWay}" InputScope="Number" KeyDown="UIElement_OnKeyDown" />
<TextBlock x:Uid="SettingsHistoryMaxSize" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<TextBox Text="{Binding MaxSize, Mode=TwoWay}" InputScope="Number" KeyDown="UIElement_OnKeyDown" />
</StackPanel>
</Page>

View File

@@ -0,0 +1,26 @@
using Windows.System;
using Windows.UI.Xaml.Input;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views.SettingsPageFrames
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsHistoryPage
{
public SettingsHistoryPage()
{
InitializeComponent();
}
private void UIElement_OnKeyDown(object sender, KeyRoutedEventArgs e)
{
if ((e.Key < VirtualKey.NumberPad0 || e.Key > VirtualKey.NumberPad9) & (e.Key < VirtualKey.Number0 || e.Key > VirtualKey.Number9))
{
e.Handled = true;
}
}
}
}

View File

@@ -1,12 +1,12 @@
<Page
x:Class="ModernKeePass.Views.SettingsDatabasePage"
x:Class="ModernKeePass.Views.SettingsPageFrames.SettingsRecycleBinPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:ModernKeePass.Converters"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=SettingsDatabase}">
DataContext="{Binding Source={StaticResource Locator}, Path=RecycleBin}">
<Page.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<converters:NullToBooleanConverter x:Key="NullToBooleanConverter"/>
@@ -15,9 +15,6 @@
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<StackPanel.Resources>
<CollectionViewSource x:Name="RecycleBinGroups" Source="{Binding Groups}" />
<CollectionViewSource x:Name="Ciphers" Source="{Binding Ciphers}" />
<CollectionViewSource x:Name="Compressions" Source="{Binding Compressions}" />
<CollectionViewSource x:Name="KeyDerivations" Source="{Binding KeyDerivations}" />
</StackPanel.Resources>
<ToggleSwitch x:Uid="SettingsDatabaseRecycleBin" IsOn="{Binding HasRecycleBin, Mode=TwoWay}" />
<StackPanel Visibility="{Binding HasRecycleBin, Converter={StaticResource BooleanToVisibilityConverter}}">
@@ -25,11 +22,5 @@
<RadioButton x:Name="RadioButton" x:Uid="SettingsDatabaseRecycleBinExisting" GroupName="Recycle" IsChecked="{Binding SelectedRecycleBin, Converter={StaticResource NullToBooleanConverter}}" />
<ComboBox ItemsSource="{Binding Source={StaticResource RecycleBinGroups}}" SelectedItem="{Binding SelectedRecycleBin, Mode=TwoWay}" IsEnabled="{Binding IsChecked, ElementName=RadioButton}" />
</StackPanel>
<TextBlock x:Uid="SettingsDatabaseEncryption" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<ComboBox ItemsSource="{Binding Source={StaticResource Ciphers}}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCipher, Mode=TwoWay}" />
<TextBlock x:Uid="SettingsDatabaseCompression" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<ComboBox ItemsSource="{Binding Source={StaticResource Compressions}}" SelectedItem="{Binding SelectedCompression, Mode=TwoWay}" />
<TextBlock x:Uid="SettingsDatabaseKdf" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<ComboBox ItemsSource="{Binding Source={StaticResource KeyDerivations}}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedKeyDerivation, Mode=TwoWay}" />
</StackPanel>
</Page>

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views.SettingsPageFrames
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsRecycleBinPage : Page
{
public SettingsRecycleBinPage()
{
this.InitializeComponent();
}
}
}

View File

@@ -1,15 +0,0 @@
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class SettingsSavePage
{
public SettingsSavePage()
{
InitializeComponent();
}
}
}

View File

@@ -4,17 +4,20 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:userControls="using:ModernKeePass.Views.UserControls"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=SettingsSecurity}">
DataContext="{Binding Source={StaticResource Locator}, Path=Security}">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Uid="SettingsSecurityTitle" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,0" />
<TextBlock TextWrapping="WrapWholeWords" Margin="5,0,0,0">
<Run x:Uid="SettingsSecurityDesc1" />
<Run x:Uid="SettingsSecurityDesc2" FontWeight="SemiBold" />
<Run x:Uid="SettingsSecurityDesc3" />
</TextBlock>
<userControls:SetCredentialsUserControl Margin="0,20,0,0" x:Uid="SettingsSecurityUpdateButton"/>
<StackPanel.Resources>
<CollectionViewSource x:Name="Ciphers" Source="{Binding Ciphers}" />
<CollectionViewSource x:Name="Compressions" Source="{Binding Compressions}" />
<CollectionViewSource x:Name="KeyDerivations" Source="{Binding KeyDerivations}" />
</StackPanel.Resources>
<TextBlock x:Uid="SettingsDatabaseEncryption" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<ComboBox ItemsSource="{Binding Source={StaticResource Ciphers}}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedCipher, Mode=TwoWay}" />
<TextBlock x:Uid="SettingsDatabaseCompression" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<ComboBox ItemsSource="{Binding Source={StaticResource Compressions}}" SelectedItem="{Binding SelectedCompression, Mode=TwoWay}" />
<TextBlock x:Uid="SettingsDatabaseKdf" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
<ComboBox ItemsSource="{Binding Source={StaticResource KeyDerivations}}" DisplayMemberPath="Name" SelectedItem="{Binding SelectedKeyDerivation, Mode=TwoWay}" />
</StackPanel>
</Page>

View File

@@ -1,46 +0,0 @@
<UserControl x:Name="UserControl"
x:Class="ModernKeePass.Views.UserControls.BreadCrumbUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:templateSelectors="using:ModernKeePass.TemplateSelectors"
mc:Ignorable="d">
<ItemsControl ItemsSource="{Binding ItemsSource, ElementName=UserControl}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Resources>
<DataTemplate x:Name="FirstItemTemplate">
<HyperlinkButton FontWeight="Light" FontSize="12" Padding="0" Content="{Binding Title}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:NavigateToPageAction Parameter="{Binding Id}" TargetPage="ModernKeePass.Views.GroupDetailPage" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</HyperlinkButton>
</DataTemplate>
<DataTemplate x:Name="OtherItemsTemplate">
<StackPanel Orientation="Horizontal">
<Viewbox MaxHeight="10" Margin="0,2,0,0">
<SymbolIcon Symbol="Forward" />
</Viewbox>
<HyperlinkButton Content="{Binding Title}" FontWeight="Light" FontSize="12" Padding="0">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:NavigateToPageAction Parameter="{Binding Id}" TargetPage="ModernKeePass.Views.GroupDetailPage" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</HyperlinkButton>
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemTemplateSelector>
<templateSelectors:FirstItemDataTemplateSelector FirstItem="{StaticResource FirstItemTemplate}" OtherItem="{StaticResource OtherItemsTemplate}"/>
</ItemsControl.ItemTemplateSelector>
</ItemsControl>
</UserControl>

View File

@@ -1,29 +0,0 @@
using System.Collections.Generic;
using Windows.UI.Xaml;
using ModernKeePass.Application.Common.Interfaces;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace ModernKeePass.Views.UserControls
{
public sealed partial class BreadCrumbUserControl
{
public BreadCrumbUserControl()
{
InitializeComponent();
}
public IEnumerable<IEntityVm> ItemsSource
{
get { return (IEnumerable<IEntityVm>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register(
nameof(ItemsSource),
typeof(IEnumerable<IEntityVm>),
typeof(BreadCrumbUserControl),
new PropertyMetadata(new Stack<IEntityVm>(), (o, args) => { }));
}
}

View File

@@ -4,7 +4,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:templateSelectors="using:ModernKeePass.TemplateSelectors"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:converters="using:ModernKeePass.Converters"
@@ -13,6 +12,7 @@
mc:Ignorable="d">
<UserControl.Resources>
<converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</UserControl.Resources>
<Grid HorizontalAlignment="Left">
<VisualStateManager.VisualStateGroups>
@@ -80,6 +80,9 @@
<ListView
x:Name="ListView"
Grid.Row="1"
AllowDrop="True"
CanReorderItems="{Binding CanDragItems, ElementName=UserControl}"
CanDragItems="{Binding CanDragItems, ElementName=UserControl}"
ItemsSource="{Binding ItemsSource, ElementName=UserControl}"
SelectionChanged="Selector_OnSelectionChanged"
SelectedItem="{Binding SelectedItem, ElementName=UserControl, Mode=TwoWay}"
@@ -88,31 +91,18 @@
IsSynchronizedWithCurrentItem="False"
Background="{ThemeResource AppBarBackgroundThemeBrush}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<ListView.Resources>
<DataTemplate x:Name="IsSpecial">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=48}" Margin="7,15,0,15">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" />
</ToolTipService.ToolTip>
</SymbolIcon>
<TextBlock Text="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" x:Name="GroupTextBlock" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="30,0,20,0" FontStyle="Italic" />
</StackPanel>
</DataTemplate>
<ListView.ItemTemplate>
<DataTemplate x:Name="IsNormal">
<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,10,0,15">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" />
</ToolTipService.ToolTip>
</SymbolIcon>
<TextBlock Text="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" x:Name="GroupTextBlock" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="30,0,20,0" />
<TextBlock Text="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" x:Name="GroupTextBlock" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="20,0,10,0" />
</StackPanel>
</DataTemplate>
</ListView.Resources>
<ListView.ItemTemplateSelector>
<templateSelectors:SelectableDataTemplateSelector FalseItem="{StaticResource IsNormal}" TrueItem="{StaticResource IsSpecial}" />
</ListView.ItemTemplateSelector>
</ListView.ItemTemplate>
<ListView.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical" Visibility="{Binding IsButtonVisible, ElementName=UserControl}">
@@ -125,13 +115,13 @@
BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}"
HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<StackPanel Orientation="Horizontal" Margin="17,0,0,0">
<SymbolIcon Symbol="Add">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ButtonLabel, ElementName=UserControl}" />
</ToolTipService.ToolTip>
</SymbolIcon>
<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="20,0,0,0" />
</StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
@@ -146,13 +136,13 @@
x:Name="NewGroupTextBox"
Margin="0,5,0,5"
Visibility="Collapsed"
Width="280"
Width="230"
HorizontalAlignment="Center"
ButtonCommand="{Binding ActionButtonCommand, ElementName=UserControl}"
ButtonCommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
Style="{StaticResource TextBoxWithButtonStyle}"
KeyDown="NewGroupTextBox_OnKeyDown"
ButtonSymbol="&#xE111;">
ButtonSymbol="&#xE109;">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="LostFocus">
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Visible" />
@@ -162,6 +152,7 @@
</interactivity:Interaction.Behaviors>
</controls:TextBoxWithButton>
<Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
<TextBlock x:Uid="ReorderGroupsLabel" Margin="10,0,0,10" Visibility="{Binding CanDragItems, ElementName=UserControl, Converter={StaticResource BooleanToVisibilityConverter}}" TextWrapping="NoWrap" Style="{StaticResource BodyTextBlockStyle}" />
</StackPanel>
</DataTemplate>
</ListView.HeaderTemplate>
@@ -176,13 +167,13 @@
BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}"
HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<StackPanel Orientation="Horizontal" Margin="17,0,0,0">
<SymbolIcon Symbol="Home">
<ToolTipService.ToolTip>
<ToolTip x:Uid="HamburgerMenuHomeTooltip" />
</ToolTipService.ToolTip>
</SymbolIcon>
<TextBlock x:Uid="HamburgerMenuHomeLabel" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" />
<TextBlock x:Uid="HamburgerMenuHomeLabel" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="20,0,0,0" />
</StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
@@ -198,13 +189,13 @@
BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}"
HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<StackPanel Orientation="Horizontal" Margin="17,0,0,0">
<SymbolIcon Symbol="Setting">
<ToolTipService.ToolTip>
<ToolTip x:Uid="HamburgerMenuSettingsTooltip" />
</ToolTipService.ToolTip>
</SymbolIcon>
<TextBlock x:Uid="HamburgerMenuSettingsLabel" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" />
<TextBlock x:Uid="HamburgerMenuSettingsLabel" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="20,0,0,0" />
</StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">

View File

@@ -110,6 +110,18 @@ namespace ModernKeePass.Views.UserControls
typeof(bool),
typeof(HamburgerMenuUserControl),
new PropertyMetadata(false, (o, args) => { }));
public bool CanDragItems
{
get { return (bool)GetValue(CanDragItemsProperty); }
set { SetValue(CanDragItemsProperty, value); }
}
public static readonly DependencyProperty CanDragItemsProperty =
DependencyProperty.Register(
nameof(CanDragItems),
typeof(bool),
typeof(HamburgerMenuUserControl),
new PropertyMetadata(false, (o, args) => { }));
public ICommand ActionButtonCommand
{

View File

@@ -150,7 +150,6 @@
<Button.Flyout>
<MenuFlyout Opening="OverflowFlyout_OnOpening">
<MenuFlyoutItem x:Uid="TopMenuSaveFlyout" x:Name="SaveFlyout" />
<MenuFlyoutItem x:Uid="TopMenuMoveFlyout" x:Name="MoveFlyout" Visibility="{Binding MoveButtonVisibility, ElementName=UserControl}" />
<MenuFlyoutItem x:Uid="TopMenuRestoreFlyout" x:Name="RestoreFlyout" Visibility="{Binding RestoreButtonVisibility, ElementName=UserControl}" />
<ToggleMenuFlyoutItem x:Uid="TopMenuEditFlyout" x:Name="EditFlyout" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Click="EditButton_Click" />
<MenuFlyoutItem x:Uid="TopMenuDeleteFlyout" x:Name="DeleteFlyout" />

View File

@@ -169,8 +169,7 @@ namespace ModernKeePass.Views.UserControls
private void OverflowFlyout_OnOpening(object sender, object e)
{
EditFlyout.IsChecked = IsEditButtonChecked;
MoveFlyout.Visibility = MoveButtonVisibility;
RestoreFlyout.Visibility = RestoreButtonVisibility;
SortEntriesFlyout.Visibility = SortButtonVisibility;
SortGroupsFlyout.Visibility = SortButtonVisibility;
@@ -196,7 +195,7 @@ namespace ModernKeePass.Views.UserControls
private void GroupSearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png"));
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata:/Assets/ModernKeePass-SmallLogo.scale-80.png"));
var groups = Model.Groups.Where(g => g.Title.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5);
foreach (var group in groups)
{
@@ -218,11 +217,11 @@ namespace ModernKeePass.Views.UserControls
private async void EntrySearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png"));
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata:/Assets/ModernKeePass-SmallLogo.scale-80.png"));
var results = (await Model.Search(args.QueryText)).Take(5);
foreach (var result in results)
{
args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title, result.ParentGroupName, result.Id, imageUri, string.Empty);
args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title.Value, result.ParentGroupName, result.Id, imageUri, string.Empty);
}
}

View File

@@ -102,24 +102,27 @@
<Compile Include="Views\MainPageFrames\ImportExportPage.xaml.cs">
<DependentUpon>ImportExportPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsDatabasePage.xaml.cs">
<DependentUpon>SettingsDatabasePage.xaml</DependentUpon>
<Compile Include="Views\SettingsPageFrames\SettingsHistoryPage.xaml.cs">
<DependentUpon>SettingsHistoryPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsNewDatabasePage.xaml.cs">
<DependentUpon>SettingsNewDatabasePage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsSavePage.xaml.cs">
<DependentUpon>SettingsSavePage.xaml</DependentUpon>
<Compile Include="Views\SettingsPageFrames\SettingsRecycleBinPage.xaml.cs">
<DependentUpon>SettingsRecycleBinPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsSecurityPage.xaml.cs">
<DependentUpon>SettingsSecurityPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsNewDatabasePage.xaml.cs">
<DependentUpon>SettingsNewDatabasePage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsGeneralPage.xaml.cs">
<DependentUpon>SettingsGeneralPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsCredentialsPage.xaml.cs">
<DependentUpon>SettingsCredentialsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\SettingsPageFrames\SettingsWelcomePage.xaml.cs">
<DependentUpon>SettingsWelcomePage.xaml</DependentUpon>
</Compile>
<Compile Include="Views\UserControls\BreadCrumbUserControl.xaml.cs">
<DependentUpon>BreadCrumbUserControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\UserControls\ColorPickerUserControl.xaml.cs">
<DependentUpon>ColorPickerUserControl.xaml</DependentUpon>
</Compile>
@@ -204,11 +207,15 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPageFrames\SettingsSavePage.xaml">
<Page Include="Views\SettingsPageFrames\SettingsHistoryPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\UserControls\BreadCrumbUserControl.xaml">
<Page Include="Views\SettingsPageFrames\SettingsRecycleBinPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPageFrames\SettingsGeneralPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -260,7 +267,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPageFrames\SettingsDatabasePage.xaml">
<Page Include="Views\SettingsPageFrames\SettingsSecurityPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -268,7 +275,7 @@
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\SettingsPageFrames\SettingsSecurityPage.xaml">
<Page Include="Views\SettingsPageFrames\SettingsCredentialsPage.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
@@ -379,12 +386,8 @@
<HintPath>..\packages\HockeySDK.WINRT.4.1.6\lib\portable-win81\Microsoft.HockeyApp.Kit.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Toolkit.Uwp.Notifications, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Toolkit.Uwp.Notifications.2.0.0\lib\dotnet\Microsoft.Toolkit.Uwp.Notifications.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ModernKeePassLib, Version=2.44.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ModernKeePassLib.2.44.3\lib\netstandard1.2\ModernKeePassLib.dll</HintPath>
<Reference Include="ModernKeePassLib, Version=2.45.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ModernKeePassLib.2.45.1\lib\netstandard1.2\ModernKeePassLib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SixLabors.Core, Version=0.1.0.0, Culture=neutral, processorArchitecture=MSIL">

View File

@@ -1,5 +1,8 @@
Redesign of the UI (top menu and hamburger menu)
Resuming the app re-opens previously opened database
Notify user and show the Save As dialog when an error is encountered during saving
Save As actually works now
Creating a new group is now done inline
Support for additional fields
Support for attachments
Add an expiration timer for clipboard data
Ability to manually reorder groups
Ability to set max history count and size
Design changes
Update to KeePassLib version 2.45
Bug corrections

View File

@@ -1,5 +1,8 @@
Redesign de l'interface utilisateur (menu superieur et menu hamburger)
Le re-chargement de l'app re-ouvre la base de donnees ouverte precedemment
L'utlisateur est prevenu et un popup de sauvegarde est affiche quand il y a une erreur de sauvegarde
La fonctionnalite Sauvegarder Sous fonctionne correctement
La creation d'un nouveau groupe se fait directement dans le menu
Ajout des champs additionnels
Ajout des pi<70>ces-jointes
Ajout d'une expiration du presse papier
Possibilite de reorganiser les groupes manuellement
Possibilite de parametrer le nombre max et la taille de l'historique
Quelques changements de design
Mise a jour de la KeePassLib version 2.45
Correction de bugs

View File

@@ -14,8 +14,7 @@
<package id="Microsoft.NETCore.Platforms" version="2.1.1" targetFramework="win81" />
<package id="Microsoft.NETCore.Portable.Compatibility" version="1.0.1" targetFramework="win81" />
<package id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.1.7" targetFramework="win81" />
<package id="Microsoft.Toolkit.Uwp.Notifications" version="2.0.0" targetFramework="win81" />
<package id="ModernKeePassLib" version="2.44.3" targetFramework="win81" />
<package id="ModernKeePassLib" version="2.45.1" targetFramework="win81" />
<package id="MvvmLight" version="5.4.1.1" targetFramework="win81" />
<package id="MvvmLightLibs" version="5.4.1.1" targetFramework="win81" />
<package id="NETStandard.Library" version="2.0.3" targetFramework="win81" />

View File

@@ -21,12 +21,14 @@ You can get it [here](https://www.microsoft.com/en-us/store/p/modernkeepass/9mwq
- View, delete and restore from entry history
- Use Semantic Zoom to see your entries in a grouped mode
- List recently opened databases
- Open database from Windows Explorer
- Open a database from Windows Explorer
- Change database encryption
- Change database compression
- Change database key derivation
- Displays and change entry colors and icons
- Move entries from a group to another
- Move entries and groups from a group to another
- Entry custom fields (view, add, update, delete)
- Entry attachments (view, add, delete)
# Build and Test
1. Clone the repository
@@ -36,8 +38,6 @@ You can get it [here](https://www.microsoft.com/en-us/store/p/modernkeepass/9mwq
# Contribute
I'm not the best at creating nice assets, so if anyone would like to contribute some nice icons, it would be awesome :)
Otherwise, there are still many things left to implement:
- Entry custom fields
- Entry attachments
- Multi entry selection (for delete, or move)
- Import existing data from CSV, JSON, or XML
- Open database from URL (and maybe some clouds?)

View File

@@ -1,11 +1,17 @@
using Windows.ApplicationModel.DataTransfer;
using System;
using Windows.ApplicationModel.DataTransfer;
using Windows.UI.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Xaml.Interactivity;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Common;
namespace ModernKeePass.Actions
{
public class ClipboardAction : DependencyObject, IAction
{
private DispatcherTimer _dispatcher;
public string Text
{
get { return (string)GetValue(TextProperty); }
@@ -18,10 +24,24 @@ namespace ModernKeePass.Actions
public object Execute(object sender, object parameter)
{
if (string.IsNullOrEmpty(Text)) return null;
var settings = App.Services.GetRequiredService<ISettingsProxy>();
_dispatcher = new DispatcherTimer {Interval = TimeSpan.FromSeconds(settings.GetSetting(Constants.Settings.ClipboardTimeout, 10))};
_dispatcher.Tick += Dispatcher_Tick;
var dataPackage = new DataPackage { RequestedOperation = DataPackageOperation.Copy };
dataPackage.SetText(Text);
Clipboard.SetContent(dataPackage);
_dispatcher.Start();
return null;
}
private void Dispatcher_Tick(object sender, object e)
{
Clipboard.SetContent(null);
_dispatcher.Stop();
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using Windows.UI.Xaml;
using GalaSoft.MvvmLight.Views;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Xaml.Interactivity;
namespace ModernKeePass.Actions
@@ -24,10 +22,8 @@ namespace ModernKeePass.Actions
var uri = new Uri(Url);
return Windows.System.Launcher.LaunchUriAsync(uri).GetAwaiter().GetResult();
}
catch (Exception ex)
catch (Exception)
{
var dialogService = App.Services.GetRequiredService<IDialogService>();
dialogService.ShowError(ex, ex.Message, null, () => {}).Wait();
return false;
}
}

View File

@@ -19,6 +19,21 @@
public static string SaveSuspend => nameof(SaveSuspend);
public static string Sample => nameof(Sample);
public static string DefaultFileFormat => nameof(DefaultFileFormat);
public static string ClipboardTimeout => nameof(ClipboardTimeout);
public static string HistoryMaxCount => nameof(HistoryMaxCount);
public static class PasswordGenerationOptions
{
public static string UpperCasePattern => nameof(UpperCasePattern);
public static string LowerCasePattern => nameof(LowerCasePattern);
public static string DigitsPattern => nameof(DigitsPattern);
public static string MinusPattern => nameof(MinusPattern);
public static string UnderscorePattern => nameof(UnderscorePattern);
public static string SpacePattern => nameof(SpacePattern);
public static string SpecialPattern => nameof(SpecialPattern);
public static string BracketsPattern => nameof(BracketsPattern);
public static string PasswordLength => nameof(PasswordLength);
}
}
}
}

View File

@@ -0,0 +1,140 @@
using System.Collections.Generic;
using System.Linq;
using Windows.Foundation.Collections;
namespace ModernKeePass.Common
{
/// <summary>
/// Implementation of IObservableMap that supports reentrancy for use as a default view
/// model.
/// </summary>
public class ObservableDictionary : IObservableMap<string, object>
{
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
{
public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
{
CollectionChange = change;
Key = key;
}
public CollectionChange CollectionChange { get; }
public string Key { get; }
}
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
public event MapChangedEventHandler<string, object> MapChanged;
private void InvokeMapChanged(CollectionChange change, string key)
{
MapChanged?.Invoke(this, new ObservableDictionaryChangedEventArgs(change, key));
}
public void Add(string key, object value)
{
_dictionary.Add(key, value);
InvokeMapChanged(CollectionChange.ItemInserted, key);
}
public void Add(KeyValuePair<string, object> item)
{
Add(item.Key, item.Value);
}
public void AddRange(IEnumerable<KeyValuePair<string, object>> values)
{
foreach (var value in values)
{
Add(value);
}
}
public bool Remove(string key)
{
if (_dictionary.Remove(key))
{
InvokeMapChanged(CollectionChange.ItemRemoved, key);
return true;
}
return false;
}
public bool Remove(KeyValuePair<string, object> item)
{
object currentValue;
if (_dictionary.TryGetValue(item.Key, out currentValue) &&
Equals(item.Value, currentValue) && _dictionary.Remove(item.Key))
{
InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
return true;
}
return false;
}
public object this[string key]
{
get
{
return _dictionary[key];
}
set
{
_dictionary[key] = value;
InvokeMapChanged(CollectionChange.ItemChanged, key);
}
}
public void Clear()
{
var priorKeys = _dictionary.Keys.ToArray();
_dictionary.Clear();
foreach (var key in priorKeys)
{
InvokeMapChanged(CollectionChange.ItemRemoved, key);
}
}
public ICollection<string> Keys => _dictionary.Keys;
public bool ContainsKey(string key)
{
return _dictionary.ContainsKey(key);
}
public bool TryGetValue(string key, out object value)
{
return _dictionary.TryGetValue(key, out value);
}
public ICollection<object> Values => _dictionary.Values;
public bool Contains(KeyValuePair<string, object> item)
{
return _dictionary.Contains(item);
}
public int Count => _dictionary.Count;
public bool IsReadOnly => false;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
return _dictionary.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _dictionary.GetEnumerator();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
var arraySize = array.Length;
foreach (var pair in _dictionary)
{
if (arrayIndex >= arraySize) break;
array[arrayIndex++] = pair;
}
}
}
}

View File

@@ -4,7 +4,7 @@ using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.Controls
{
public class ListViewWithDisable: ListView
public class DisableListView: ListView
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{

View File

@@ -0,0 +1,51 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ModernKeePass.Controls
{
public class SelectableTemplateListView: ListView
{
public DataTemplate SelectedItemTemplate
{
get { return (DataTemplate)GetValue(SelectedItemTemplateProperty); }
set { SetValue(SelectedItemTemplateProperty, value); }
}
public static readonly DependencyProperty SelectedItemTemplateProperty =
DependencyProperty.Register(
nameof(SelectedItemTemplate),
typeof(DataTemplate),
typeof(PasswordBoxWithButton),
new PropertyMetadata(null, (o, args) => { }));
public SelectableTemplateListView()
{
ContainerContentChanging += SelectableTemplateListView_ContainerContentChanging;
SelectionChanged += SelectableTemplateListView_SelectionChanged;
}
private void SelectableTemplateListView_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
args.ItemContainer.ContentTemplate = args.ItemContainer.IsSelected
? SelectedItemTemplate
: ItemTemplate;
}
private void SelectableTemplateListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
if (listView == null) return;
foreach (var item in e.AddedItems)
{
var listViewItem = listView.ContainerFromItem(item) as ListViewItem;
if (listViewItem != null) listViewItem.ContentTemplate = SelectedItemTemplate;
}
//Remove DataTemplate for unselected items
foreach (var item in e.RemovedItems)
{
var listViewItem = listView.ContainerFromItem(item) as ListViewItem;
if (listViewItem != null) listViewItem.ContentTemplate = ItemTemplate;
}
}
}
}

View File

@@ -69,19 +69,6 @@ namespace ModernKeePass.Controls
typeof(TextBoxWithButton),
new PropertyMetadata(null, (o, args) => { }));
public bool IsButtonEnabled
{
get { return (bool)GetValue(IsButtonEnabledProperty); }
set { SetValue(IsButtonEnabledProperty, value); }
}
public static readonly DependencyProperty IsButtonEnabledProperty =
DependencyProperty.Register(
nameof(IsButtonEnabled),
typeof(bool),
typeof(TextBoxWithButton),
new PropertyMetadata(true, (o, args) => { }));
public TextBoxWithButton()
{
DefaultStyleKey = typeof(TextBoxWithButton);

View File

@@ -1,24 +0,0 @@
using System;
using Windows.UI.Xaml.Data;
namespace ModernKeePass.Converters
{
public class PluralizationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var pluralizationOptionString = parameter as string;
var pluralizationOptions = pluralizationOptionString?.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
if (pluralizationOptions == null || pluralizationOptions.Length != 2) return string.Empty;
var count = value is int ? (int) value : 0;
var text = count == 1 ? pluralizationOptions[0] : pluralizationOptions[1];
return $"{count} {text}";
}
// No need to implement this
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,20 +0,0 @@
using System;
using Windows.UI.Xaml.Data;
namespace ModernKeePass.Converters
{
public class TextToWidthConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var fontSize = double.Parse(parameter as string);
var text = value as string;
return text?.Length * fontSize ?? 0;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Messages
{
public class EntryFieldNameChangedMessage
{
public string OldName { get; set; }
public string NewName { get; set; }
public string Value { get; set; }
public bool IsProtected { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace Messages
{
public class EntryFieldValueChangedMessage
{
public string FieldName { get; set; }
public string FieldValue { get; set; }
public bool IsProtected { get; set; }
}
}

View File

@@ -1,18 +0,0 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ModernKeePass.TemplateSelectors
{
public class FirstItemDataTemplateSelector: DataTemplateSelector
{
public DataTemplate FirstItem { get; set; }
public DataTemplate OtherItem { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
var returnTemplate = itemsControl?.IndexFromContainer(container) == 0 ? FirstItem : OtherItem;
return returnTemplate;
}
}
}

View File

@@ -1,18 +0,0 @@
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.TemplateSelectors
{
public class SelectableDataTemplateSelector: DataTemplateSelector
{
public DataTemplate TrueItem { get; set; }
public DataTemplate FalseItem { get; set; }
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
var isSelectableItem = item as ISelectableModel;
return isSelectableItem != null && isSelectableItem.IsSelected ? TrueItem : FalseItem;
}
}
}

View File

@@ -0,0 +1,51 @@
using System.Linq;
using GalaSoft.MvvmLight;
using Messages;
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.ViewModels.ListItems
{
public class EntryFieldVm: ViewModelBase
{
private string _name;
private string _value;
private bool _isProtected;
public string Name
{
get { return _name; }
set
{
var newName = EntryFieldName.StandardFieldNames.Contains(value) ? $"{value}_1" : value;
MessengerInstance.Send(new EntryFieldNameChangedMessage { OldName = Name, NewName = newName, Value = Value, IsProtected = IsProtected});
Set(nameof(Name), ref _name, newName);
}
}
public string Value
{
get { return _value; }
set
{
MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = value, IsProtected = IsProtected });
Set(nameof(Value), ref _value, value);
}
}
public bool IsProtected
{
get { return _isProtected; }
set
{
MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = Value, IsProtected = value });
Set(nameof(IsProtected), ref _isProtected, value);
}
}
public EntryFieldVm(string fieldName, string fieldValue, bool isProtected)
{
_name = fieldName;
_value = fieldValue;
_isProtected = isProtected;
}
}
}

View File

@@ -0,0 +1,37 @@
using GalaSoft.MvvmLight;
using Messages;
namespace ModernKeePass.ViewModels.ListItems
{
public class FieldVm: ViewModelBase
{
private string _name;
private string _value;
public string Name
{
get { return _name; }
set
{
MessengerInstance.Send(new EntryFieldNameChangedMessage { OldName = Name, NewName = value, Value = Value });
Set(nameof(Name), ref _name, value);
}
}
public string Value
{
get { return _value; }
set
{
MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = value });
Set(nameof(Value), ref _value, value);
}
}
public FieldVm(string fieldName, string fieldValue)
{
_name = fieldName;
_value = fieldValue;
}
}
}

View File

@@ -1,12 +1,10 @@
using GalaSoft.MvvmLight;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.ViewModels.ListItems
{
public class RecentItemVm: ObservableObject, ISelectableModel
public class RecentItemVm: ObservableObject
{
private bool _isSelected;
private string _name;
private string _token;
private string _path;
@@ -29,12 +27,6 @@ namespace ModernKeePass.ViewModels.ListItems
set { Set(() => Path, ref _path, value); }
}
public bool IsSelected
{
get { return _isSelected; }
set { Set(() => IsSelected, ref _isSelected, value); }
}
public RecentItemVm(FileInfo file)
{
Token = file.Id;

View File

@@ -0,0 +1,37 @@
using System.Threading.Tasks;
using GalaSoft.MvvmLight;
using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.UpdateCredentials;
using ModernKeePass.Application.Database.Queries.GetDatabase;
namespace ModernKeePass.ViewModels.ListItems
{
public class SettingsCredentialsVm: ViewModelBase
{
private readonly IMediator _mediator;
private readonly IResourceProxy _resource;
private readonly INotificationService _notification;
public SettingsCredentialsVm(IMediator mediator, IResourceProxy resource, INotificationService notification)
{
_mediator = mediator;
_resource = resource;
_notification = notification;
MessengerInstance.Register<CredentialsSetMessage>(this, async message => await UpdateDatabaseCredentials(message));
}
public async Task UpdateDatabaseCredentials(CredentialsSetMessage message)
{
await _mediator.Send(new UpdateCredentialsCommand
{
KeyFilePath = message.KeyFilePath,
Password = message.Password
});
var database = await _mediator.Send(new GetDatabaseQuery());
_notification.Show(database.Name, _resource.GetResourceValue("CompositeKeyUpdated"));
}
}
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.ViewModels.ListItems
{
public class SettingsHistoryVm
{
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.ObjectModel;
using System.Linq;
using GalaSoft.MvvmLight;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Group.Queries.GetGroup;
using ModernKeePass.Application.Parameters.Commands.SetHasRecycleBin;
using ModernKeePass.Application.Parameters.Commands.SetRecycleBin;
namespace ModernKeePass.ViewModels.ListItems
{
public class SettingsRecycleBinVm: ObservableObject
{
private readonly IMediator _mediator;
private readonly DatabaseVm _database;
public bool HasRecycleBin
{
get { return _database.IsRecycleBinEnabled; }
set
{
_mediator.Send(new SetHasRecycleBinCommand { HasRecycleBin = value }).Wait();
RaisePropertyChanged(nameof(HasRecycleBin));
}
}
public bool IsNewRecycleBin
{
get { return string.IsNullOrEmpty(_database.RecycleBinId); }
set
{
if (value) _mediator.Send(new SetRecycleBinCommand { RecycleBinId = null }).Wait();
}
}
public ObservableCollection<IEntityVm> Groups { get; }
public IEntityVm SelectedRecycleBin
{
get { return Groups.FirstOrDefault(g => g.Id == _database.RecycleBinId); }
set
{
if (!IsNewRecycleBin) _mediator.Send(new SetRecycleBinCommand { RecycleBinId = value.Id }).Wait();
}
}
public SettingsRecycleBinVm(IMediator mediator)
{
_mediator = mediator;
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
var rootGroup = _mediator.Send(new GetGroupQuery { Id = _database.RootGroupId }).GetAwaiter().GetResult();
Groups = new ObservableCollection<IEntityVm>(rootGroup.SubGroups);
}
}
}

View File

@@ -1,37 +1,57 @@
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using GalaSoft.MvvmLight;
using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.UpdateCredentials;
using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Parameters.Commands.SetCipher;
using ModernKeePass.Application.Parameters.Commands.SetCompression;
using ModernKeePass.Application.Parameters.Commands.SetKeyDerivation;
using ModernKeePass.Application.Parameters.Models;
using ModernKeePass.Application.Parameters.Queries.GetCiphers;
using ModernKeePass.Application.Parameters.Queries.GetCompressions;
using ModernKeePass.Application.Parameters.Queries.GetKeyDerivations;
namespace ModernKeePass.ViewModels.ListItems
{
public class SettingsSecurityVm: ViewModelBase
// TODO: implement Kdf settings
public class SettingsSecurityVm: ObservableObject
{
private readonly IMediator _mediator;
private readonly IResourceProxy _resource;
private readonly INotificationService _notification;
private readonly DatabaseVm _database;
public SettingsSecurityVm(IMediator mediator, IResourceProxy resource, INotificationService notification)
public ObservableCollection<CipherVm> Ciphers { get; }
public IEnumerable<string> Compressions => _mediator.Send(new GetCompressionsQuery()).GetAwaiter().GetResult();
public ObservableCollection<KeyDerivationVm> KeyDerivations { get; }
public CipherVm SelectedCipher
{
get { return Ciphers.FirstOrDefault(c => c.Id == _database.CipherId); }
set { _mediator.Send(new SetCipherCommand {CipherId = value.Id}).Wait(); }
}
public string SelectedCompression
{
get { return Compressions.FirstOrDefault(c => c == _database.Compression); }
set { _mediator.Send(new SetCompressionCommand {Compression = value}).Wait(); }
}
public KeyDerivationVm SelectedKeyDerivation
{
get { return KeyDerivations.FirstOrDefault(c => c.Id == _database.KeyDerivationId); }
set { _mediator.Send(new SetKeyDerivationCommand {KeyDerivationId = value.Id}).Wait(); }
}
public SettingsSecurityVm(IMediator mediator)
{
_mediator = mediator;
_resource = resource;
_notification = notification;
MessengerInstance.Register<CredentialsSetMessage>(this, async message => await UpdateDatabaseCredentials(message));
}
public async Task UpdateDatabaseCredentials(CredentialsSetMessage message)
{
await _mediator.Send(new UpdateCredentialsCommand
{
KeyFilePath = message.KeyFilePath,
Password = message.Password
});
var database = await _mediator.Send(new GetDatabaseQuery());
_notification.Show(database.Name, _resource.GetResourceValue("CompositeKeyUpdated"));
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
var ciphers = _mediator.Send(new GetCiphersQuery()).GetAwaiter().GetResult();
Ciphers = new ObservableCollection<CipherVm>(ciphers);
var keyDerivations = _mediator.Send(new GetKeyDerivationsQuery()).GetAwaiter().GetResult();
KeyDerivations = new ObservableCollection<KeyDerivationVm>(keyDerivations);
}
}
}
}

View File

@@ -5,43 +5,30 @@ using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Interfaces;
using ModernKeePass.ViewModels.ListItems;
namespace ModernKeePass.ViewModels
{
public class RecentVm : ViewModelBase, IHasSelectableObject
public class RecentVm : ViewModelBase
{
private readonly IRecentProxy _recent;
private ISelectableModel _selectedItem;
private ObservableCollection<RecentItemVm> _recentItems;
private int _selectedIndex;
public ObservableCollection<RecentItemVm> RecentItems
{
get { return _recentItems; }
set { Set(() => RecentItems, ref _recentItems, value); }
}
public ISelectableModel SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value) return;
if (_selectedItem != null)
{
_selectedItem.IsSelected = false;
}
Set(() => SelectedItem, ref _selectedItem, value);
if (_selectedItem == null) return;
_selectedItem.IsSelected = true;
}
}
public ICommand ClearAllCommand { get; }
public int SelectedIndex
{
get { return _selectedIndex; }
set { Set(() => SelectedIndex, ref _selectedIndex, value); }
}
public RecentVm(IRecentProxy recent)
{
_recent = recent;
@@ -54,8 +41,7 @@ namespace ModernKeePass.ViewModels
{
var recentItems = _recent.GetAll().Select(r => new RecentItemVm(r));
RecentItems = new ObservableCollection<RecentItemVm>(recentItems);
if (RecentItems.Count > 0)
SelectedItem = RecentItems[0];
if (RecentItems.Any()) SelectedIndex = 0;
}
private void ClearAll()

View File

@@ -0,0 +1,37 @@
using System.Threading.Tasks;
using GalaSoft.MvvmLight;
using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.UpdateCredentials;
using ModernKeePass.Application.Database.Queries.GetDatabase;
namespace ModernKeePass.ViewModels.Settings
{
public class CredentialsVm: ViewModelBase
{
private readonly IMediator _mediator;
private readonly IResourceProxy _resource;
private readonly INotificationService _notification;
public CredentialsVm(IMediator mediator, IResourceProxy resource, INotificationService notification)
{
_mediator = mediator;
_resource = resource;
_notification = notification;
MessengerInstance.Register<CredentialsSetMessage>(this, async message => await UpdateDatabaseCredentials(message));
}
public async Task UpdateDatabaseCredentials(CredentialsSetMessage message)
{
await _mediator.Send(new UpdateCredentialsCommand
{
KeyFilePath = message.KeyFilePath,
Password = message.Password
});
var database = await _mediator.Send(new GetDatabaseQuery());
_notification.Show(database.Name, _resource.GetResourceValue("CompositeKeyUpdated"));
}
}
}

View File

@@ -0,0 +1,28 @@
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Common;
namespace ModernKeePass.ViewModels.Settings
{
public class GeneralVm
{
private readonly ISettingsProxy _settings;
public bool IsSaveSuspend
{
get { return _settings.GetSetting(Constants.Settings.SaveSuspend, true); }
set { _settings.PutSetting(Constants.Settings.SaveSuspend, value); }
}
public int CopyExpiration
{
get { return _settings.GetSetting(Constants.Settings.ClipboardTimeout, 10); }
set { _settings.PutSetting(Constants.Settings.ClipboardTimeout, value); }
}
public GeneralVm(ISettingsProxy settings)
{
_settings = settings;
}
}
}

Some files were not shown because too many files have changed in this diff Show More