mirror of
https://github.com/wismna/ModernKeePass.git
synced 2025-10-04 08:00:16 -04:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
7a2ce30512 | ||
![]() |
d497f69a5e | ||
![]() |
72e5bf4ee1 | ||
![]() |
2e01fa2212 | ||
![]() |
0adb44bc81 | ||
![]() |
d38d6461bd | ||
![]() |
7ac1595aaa | ||
![]() |
f8f7c19f65 | ||
![]() |
d6dc6a74a3 | ||
![]() |
bb2b99ed66 | ||
![]() |
71b6009a29 | ||
![]() |
fbcc354809 | ||
![]() |
e901afaf29 | ||
![]() |
ca04a6c8ee | ||
![]() |
1488c3244f | ||
![]() |
2e22a2bd92 | ||
![]() |
8fb468358e | ||
![]() |
5ce0262318 | ||
![]() |
2f30389f6c | ||
![]() |
b3c7683c12 | ||
![]() |
1e7662def7 | ||
![]() |
97b10baedc | ||
![]() |
654bd6b4e5 |
@@ -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" />
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -22,30 +22,37 @@ 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);
|
||||
Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V3);
|
||||
Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V4);
|
||||
Task<byte[]> SaveDatabase();
|
||||
Task<byte[]> SaveDatabase(byte[] newFileContents);
|
||||
void UpdateCredentials(Credentials credentials);
|
||||
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);
|
||||
void DeleteHistory(string entryId, int historyIndex);
|
||||
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
|
@@ -1,12 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using ModernKeePass.Domain.Dtos;
|
||||
|
||||
namespace ModernKeePass.Application.Common.Interfaces
|
||||
{
|
||||
public interface IFileProxy
|
||||
{
|
||||
Task<byte[]> OpenBinaryFile(string path);
|
||||
Task<IList<string>> OpenTextFile(string path);
|
||||
Task<FileInfo> OpenFile(string name, string extension, bool addToRecent);
|
||||
Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent);
|
||||
Task<byte[]> ReadBinaryFile(string path);
|
||||
Task<IList<string>> ReadTextFile(string path);
|
||||
Task WriteBinaryContentsToFile(string path, byte[] contents);
|
||||
void ReleaseFile(string path);
|
||||
}
|
||||
|
@@ -17,11 +17,9 @@ 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();
|
||||
|
||||
// Cleanup
|
||||
_database.FileAccessToken = null;
|
||||
_database.Size = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -43,10 +43,10 @@ namespace ModernKeePass.Application.Database.Commands.CreateDatabase
|
||||
}
|
||||
|
||||
await _database.Create(new Credentials
|
||||
{
|
||||
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.OpenBinaryFile(message.KeyFilePath) : null,
|
||||
Password = message.Password
|
||||
}, message.Name, version);
|
||||
{
|
||||
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.ReadBinaryFile(message.KeyFilePath) : null,
|
||||
Password = message.Password
|
||||
}, message.Name, version);
|
||||
_database.FileAccessToken = message.FilePath;
|
||||
|
||||
if (message.CreateSampleData)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -27,26 +27,19 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
|
||||
|
||||
try
|
||||
{
|
||||
byte[] contents;
|
||||
if (string.IsNullOrEmpty(message.FilePath))
|
||||
if (!string.IsNullOrEmpty(message.FilePath))
|
||||
{
|
||||
contents = await _database.SaveDatabase();
|
||||
|
||||
// Test DB integrity before writing changes to file
|
||||
_database.CloseDatabase();
|
||||
await _database.ReOpen(contents);
|
||||
|
||||
await _file.WriteBinaryContentsToFile(_database.FileAccessToken, contents);
|
||||
}
|
||||
else
|
||||
{
|
||||
var newFileContents = await _file.OpenBinaryFile(message.FilePath);
|
||||
contents = await _database.SaveDatabase(newFileContents);
|
||||
await _file.WriteBinaryContentsToFile(message.FilePath, contents);
|
||||
|
||||
_file.ReleaseFile(_database.FileAccessToken);
|
||||
_database.FileAccessToken = message.FilePath;
|
||||
}
|
||||
|
||||
var contents = await _database.SaveDatabase();
|
||||
|
||||
// Test DB integrity
|
||||
_database.CloseDatabase();
|
||||
await _database.ReOpen(contents);
|
||||
|
||||
// Transactional write to file
|
||||
await _file.WriteBinaryContentsToFile(_database.FileAccessToken, contents);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
|
@@ -27,7 +27,7 @@ namespace ModernKeePass.Application.Database.Commands.UpdateCredentials
|
||||
if (!_database.IsOpen) throw new DatabaseClosedException();
|
||||
_database.UpdateCredentials(new Credentials
|
||||
{
|
||||
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.OpenBinaryFile(message.KeyFilePath) : null,
|
||||
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.ReadBinaryFile(message.KeyFilePath) : null,
|
||||
Password = message.Password
|
||||
});
|
||||
}
|
||||
|
@@ -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; }
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -27,11 +27,11 @@ namespace ModernKeePass.Application.Database.Queries.OpenDatabase
|
||||
{
|
||||
if (_database.IsDirty) throw new DatabaseOpenException();
|
||||
|
||||
var file = await _file.OpenBinaryFile(message.FilePath);
|
||||
var file = await _file.ReadBinaryFile(message.FilePath);
|
||||
var hasKeyFile = !string.IsNullOrEmpty(message.KeyFilePath);
|
||||
await _database.Open(file, new Credentials
|
||||
{
|
||||
KeyFileContents = hasKeyFile ? await _file.OpenBinaryFile(message.KeyFilePath): null,
|
||||
KeyFileContents = hasKeyFile ? await _file.ReadBinaryFile(message.KeyFilePath): null,
|
||||
Password = message.Password
|
||||
});
|
||||
if (hasKeyFile) _file.ReleaseFile(message.KeyFilePath);
|
||||
|
@@ -20,9 +20,10 @@ namespace ModernKeePass.Application.Database.Queries.ReOpenDatabase
|
||||
|
||||
public async Task Handle(ReOpenDatabaseQuery message)
|
||||
{
|
||||
if (!_database.IsOpen) throw new DatabaseClosedException();
|
||||
if (_database.IsOpen) throw new DatabaseOpenException();
|
||||
if (string.IsNullOrEmpty(_database.FileAccessToken)) throw new DatabaseClosedException();
|
||||
|
||||
var file = await _file.OpenBinaryFile(_database.FileAccessToken);
|
||||
var file = await _file.ReadBinaryFile(_database.FileAccessToken);
|
||||
await _database.ReOpen(file);
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using FluentValidation;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModernKeePass.Application.Common.Behaviors;
|
||||
@@ -13,7 +12,6 @@ namespace ModernKeePass.Application
|
||||
var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly;
|
||||
services.AddMediatR(assembly);
|
||||
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DirtyStatusBehavior<,>));
|
||||
//services.AddValidatorsFromAssembly(assembly);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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()
|
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
20
ModernKeePass.Application/Entry/Models/FieldVm.cs
Normal file
20
ModernKeePass.Application/Entry/Models/FieldVm.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.AddEntry
|
||||
if (!_database.IsOpen) throw new DatabaseClosedException();
|
||||
|
||||
await _database.AddEntry(message.ParentGroupId, message.EntryId);
|
||||
//message.ParentGroup.Entries.Add(message.Entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.AddGroup
|
||||
if (!_database.IsOpen) throw new DatabaseClosedException();
|
||||
|
||||
await _database.AddGroup(message.ParentGroupId, message.GroupId);
|
||||
//message.ParentGroup.SubGroups.Add(message.Group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.RemoveEntry
|
||||
if (!_database.IsOpen) throw new DatabaseClosedException();
|
||||
|
||||
await _database.RemoveEntry(message.ParentGroupId, message.EntryId);
|
||||
//message.ParentGroup.Entries.Remove(message.Entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.RemoveGroup
|
||||
if (!_database.IsOpen) throw new DatabaseClosedException();
|
||||
|
||||
await _database.RemoveGroup(message.ParentGroupId, message.GroupId);
|
||||
//message.ParentGroup.SubGroups.Remove(message.Group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using MediatR;
|
||||
using ModernKeePass.Application.Common.Interfaces;
|
||||
using ModernKeePass.Application.Group.Models;
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -3,5 +3,12 @@
|
||||
public static class Constants
|
||||
{
|
||||
public static string EmptyId => "00000000000000000000000000000000";
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static string Any => "*";
|
||||
public static string Kdbx => ".kdbx";
|
||||
public static string Key => ".key";
|
||||
}
|
||||
}
|
||||
}
|
@@ -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" />
|
||||
|
8
ModernKeePass.Domain/Dtos/Attachment.cs
Normal file
8
ModernKeePass.Domain/Dtos/Attachment.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace ModernKeePass.Domain.Dtos
|
||||
{
|
||||
public class Attachment
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public byte[] Content { get; set; }
|
||||
}
|
||||
}
|
@@ -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; }
|
||||
|
9
ModernKeePass.Domain/Entities/FieldEntity.cs
Normal file
9
ModernKeePass.Domain/Entities/FieldEntity.cs
Normal 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; }
|
||||
}
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
@@ -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" />
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
{
|
||||
@@ -152,15 +164,6 @@ namespace ModernKeePass.Infrastructure.KeePass
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<byte[]> SaveDatabase(byte[] newFileContents)
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
_pwDatabase.SaveAs(IOConnectionInfo.FromByteArray(newFileContents), true, new NullStatusLogger());
|
||||
return _pwDatabase.IOConnectionInfo.Bytes;
|
||||
});
|
||||
}
|
||||
|
||||
public void CloseDatabase()
|
||||
{
|
||||
_pwDatabase?.Close();
|
||||
@@ -198,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(() =>
|
||||
@@ -223,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);
|
||||
|
||||
@@ -234,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;
|
||||
@@ -251,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);
|
||||
@@ -316,6 +342,18 @@ namespace ModernKeePass.Infrastructure.KeePass
|
||||
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)
|
||||
{
|
||||
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(id), true);
|
||||
|
@@ -9,7 +9,7 @@ using ModernKeePassLib.Cryptography.KeyDerivation;
|
||||
|
||||
namespace ModernKeePass.Infrastructure.KeePass
|
||||
{
|
||||
public class KeePassCryptographyClient: ICryptographyClient
|
||||
public class KeePassDatabaseSettingsProxy: IDatabaseSettingsProxy
|
||||
{
|
||||
public IEnumerable<BaseEntity> Ciphers
|
||||
{
|
45
ModernKeePass.Infrastructure/KeePass/MappingProfiles.cs
Normal file
45
ModernKeePass.Infrastructure/KeePass/MappingProfiles.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,20 +5,69 @@ using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.AccessCache;
|
||||
using Windows.Storage.Pickers;
|
||||
using ModernKeePass.Application.Common.Interfaces;
|
||||
using ModernKeePass.Domain.Dtos;
|
||||
|
||||
namespace ModernKeePass.Infrastructure.UWP
|
||||
{
|
||||
public class StorageFileClient: IFileProxy
|
||||
{
|
||||
public async Task<byte[]> OpenBinaryFile(string path)
|
||||
public async Task<FileInfo> OpenFile(string name, string extension, bool addToRecent)
|
||||
{
|
||||
var picker = new FileOpenPicker
|
||||
{
|
||||
ViewMode = PickerViewMode.List,
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
|
||||
};
|
||||
picker.FileTypeFilter.Add(extension);
|
||||
|
||||
// Application now has read/write access to the picked file
|
||||
var file = await picker.PickSingleFileAsync().AsTask();
|
||||
if (file == null) return null;
|
||||
|
||||
var token = addToRecent
|
||||
? StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path)
|
||||
: StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
|
||||
return new FileInfo
|
||||
{
|
||||
Id = token,
|
||||
Name = file.Name,
|
||||
Path = file.Path
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent)
|
||||
{
|
||||
var savePicker = new FileSavePicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
|
||||
SuggestedFileName = name
|
||||
};
|
||||
savePicker.FileTypeChoices.Add(description, new List<string> { extension });
|
||||
|
||||
var file = await savePicker.PickSaveFileAsync().AsTask();
|
||||
if (file == null) return null;
|
||||
|
||||
var token = addToRecent
|
||||
? StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path)
|
||||
: StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
|
||||
return new FileInfo
|
||||
{
|
||||
Id = token,
|
||||
Name = file.Name,
|
||||
Path = file.Path
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<byte[]> ReadBinaryFile(string path)
|
||||
{
|
||||
var file = await GetFile(path);
|
||||
var result = await FileIO.ReadBufferAsync(file).AsTask();
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public async Task<IList<string>> OpenTextFile(string path)
|
||||
public async Task<IList<string>> ReadTextFile(string path)
|
||||
{
|
||||
var file = await GetFile(path);
|
||||
var result = await FileIO.ReadLinesAsync(file).AsTask();
|
||||
|
47
ModernKeePass.Infrastructure/UWP/UwpCryptographyClient.cs
Normal file
47
ModernKeePass.Infrastructure/UWP/UwpCryptographyClient.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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": {
|
||||
|
@@ -1,17 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.AccessCache;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
using GalaSoft.MvvmLight.Views;
|
||||
using MediatR;
|
||||
using Messages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.HockeyApp;
|
||||
using ModernKeePass.Application;
|
||||
@@ -33,16 +32,16 @@ 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;
|
||||
private readonly INavigationService _navigation;
|
||||
private readonly IHockeyClient _hockey;
|
||||
private readonly IDialogService _dialog;
|
||||
private readonly INotificationService _notification;
|
||||
|
||||
public static IServiceProvider Services { get; private set; }
|
||||
private readonly IFileProxy _file;
|
||||
private readonly IMessenger _messenger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes the singleton application object. This is the first line of authored code
|
||||
@@ -63,60 +62,39 @@ namespace ModernKeePass
|
||||
_resource = Services.GetService<IResourceProxy>();
|
||||
_settings = Services.GetService<ISettingsProxy>();
|
||||
_navigation = Services.GetService<INavigationService>();
|
||||
_dialog = Services.GetService<IDialogService>();
|
||||
_notification = Services.GetService<INotificationService>();
|
||||
_hockey = Services.GetService<IHockeyClient>();
|
||||
_file = Services.GetService<IFileProxy>();
|
||||
_messenger = Services.GetService<IMessenger>();
|
||||
|
||||
InitializeComponent();
|
||||
Suspending += OnSuspending;
|
||||
Resuming += OnResuming;
|
||||
UnhandledException += OnUnhandledException;
|
||||
|
||||
_messenger.Register<SaveErrorMessage>(this, async message => await HandleSaveError(message));
|
||||
}
|
||||
|
||||
private async Task HandleSaveError(SaveErrorMessage message)
|
||||
{
|
||||
_notification.Show(_resource.GetResourceValue("MessageDialogSaveErrorTitle"), message?.Message);
|
||||
var database = await _mediator.Send(new GetDatabaseQuery());
|
||||
var file = await _file.CreateFile($"{database.Name} - copy",
|
||||
Domain.Common.Constants.Extensions.Kdbx,
|
||||
_resource.GetResourceValue("MessageDialogSaveErrorFileTypeDesc"), true);
|
||||
if (file != null)
|
||||
{
|
||||
await _mediator.Send(new SaveDatabaseCommand { FilePath = file.Id });
|
||||
_messenger.Send(new DatabaseSavedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
#region Event Handlers
|
||||
|
||||
private async void OnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// Save the argument exception because it's cleared on first access
|
||||
var exception = unhandledExceptionEventArgs.Exception;
|
||||
var realException =
|
||||
exception is TargetInvocationException &&
|
||||
exception.InnerException != null
|
||||
? exception.InnerException
|
||||
: exception;
|
||||
|
||||
_hockey.TrackException(realException);
|
||||
if (realException is SaveException)
|
||||
{
|
||||
// TODO: this is not working
|
||||
unhandledExceptionEventArgs.Handled = true;
|
||||
await _dialog.ShowMessage(realException.Message,
|
||||
_resource.GetResourceValue("MessageDialogSaveErrorTitle"),
|
||||
_resource.GetResourceValue("MessageDialogSaveErrorButtonSaveAs"),
|
||||
_resource.GetResourceValue("MessageDialogSaveErrorButtonDiscard"),
|
||||
async isOk =>
|
||||
{
|
||||
if (isOk)
|
||||
{
|
||||
var database = await _mediator.Send(new GetDatabaseQuery());
|
||||
var savePicker = new FileSavePicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
|
||||
SuggestedFileName = $"{database.Name} - copy"
|
||||
};
|
||||
savePicker.FileTypeChoices.Add(
|
||||
_resource.GetResourceValue("MessageDialogSaveErrorFileTypeDesc"),
|
||||
new List<string> {".kdbx"});
|
||||
|
||||
var file = await savePicker.PickSaveFileAsync();
|
||||
if (file != null)
|
||||
{
|
||||
var token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
|
||||
await _mediator.Send(new SaveDatabaseCommand {FilePath = token});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
_hockey.TrackException(e.Exception);
|
||||
_hockey.Flush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -153,9 +131,6 @@ namespace ModernKeePass
|
||||
{
|
||||
// Load state from previously terminated application
|
||||
await SuspensionManager.RestoreAsync();
|
||||
#if DEBUG
|
||||
await _dialog.ShowMessage("Windows or an error made the app terminate", "App terminated");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Place the frame in the current Window
|
||||
@@ -179,12 +154,18 @@ namespace ModernKeePass
|
||||
_notification.Show("App resumed", "Database reopened (changes were saved)");
|
||||
#endif
|
||||
}
|
||||
catch (Exception)
|
||||
catch (DatabaseOpenException)
|
||||
{
|
||||
#if DEBUG
|
||||
_notification.Show("App resumed", "Previous database still open because it couldn't be closed (probable save error)");
|
||||
#endif
|
||||
}
|
||||
catch (DatabaseClosedException)
|
||||
{
|
||||
_navigation.NavigateTo(Constants.Navigation.MainPage);
|
||||
#if DEBUG
|
||||
_notification.Show("App resumed", "Nothing to do, no previous database opened");
|
||||
#endif
|
||||
_navigation.NavigateTo(Constants.Navigation.MainPage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,9 +203,14 @@ namespace ModernKeePass
|
||||
await _mediator.Send(new CloseDatabaseCommand()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
catch (SaveException ex)
|
||||
{
|
||||
_notification.Show(exception.Source, exception.Message);
|
||||
_notification.Show(ex.Source, ex.Message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_hockey.TrackException(ex);
|
||||
_hockey.Flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@@ -1,5 +1,6 @@
|
||||
using System.Reflection;
|
||||
using AutoMapper;
|
||||
using GalaSoft.MvvmLight.Messaging;
|
||||
using GalaSoft.MvvmLight.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.HockeyApp;
|
||||
@@ -24,6 +25,7 @@ namespace ModernKeePass
|
||||
nav.Configure(Constants.Navigation.GroupPage, typeof(GroupDetailPage));
|
||||
return nav;
|
||||
});
|
||||
services.AddSingleton(provider => Messenger.Default);
|
||||
services.AddTransient(typeof(IDialogService), typeof(DialogService));
|
||||
|
||||
services.AddSingleton(provider =>
|
||||
|
@@ -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.16.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>
|
||||
|
@@ -13,14 +13,14 @@
|
||||
<VisualState x:Name="PointerOver">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
|
||||
Storyboard.TargetProperty="Opacity">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="0.8" />
|
||||
Storyboard.TargetProperty="Background">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonPointerOverBackgroundThemeBrush}" />
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid x:Name="Grid" Background="{StaticResource MainColor}" Margin="0" Width="{StaticResource MenuSize}" Height="{StaticResource MenuSize}">
|
||||
<Grid x:Name="Grid" Background="{StaticResource AppBarBackgroundThemeBrush}" Margin="0" Width="{StaticResource MenuWidth}" Height="{StaticResource MenuHeight}">
|
||||
<Canvas x:Name="HamburgerMenu" HorizontalAlignment="Center" Height="17" UseLayoutRounding="False" VerticalAlignment="Center" Width="28">
|
||||
<Canvas x:Name="Layer1" Height="17" Canvas.Left="0" Width="28" Margin="0" RenderTransformOrigin="0.5,0.5">
|
||||
<Canvas.RenderTransform>
|
||||
@@ -52,7 +52,7 @@
|
||||
<Setter Property="Margin" Value="0" />
|
||||
</Style>
|
||||
<Style x:Key="HeaderTextBoxStyle" TargetType="TextBox">
|
||||
<Setter Property="FontSize" Value="40"/>
|
||||
<Setter Property="FontSize" Value="30"/>
|
||||
<Setter Property="FontWeight" Value="Light"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
|
@@ -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>
|
||||
|
@@ -4,7 +4,7 @@
|
||||
<!-- Default style for Windows.UI.Xaml.Controls.Button -->
|
||||
<Style TargetType="Button" x:Key="NoBorderButtonStyle">
|
||||
<Setter Property="Background" Value="{ThemeResource ToggleButtonBackgroundThemeBrush}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextColorDark}"/>
|
||||
<Setter Property="Foreground" Value="{ThemeResource ButtonPointerOverForegroundThemeBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
|
||||
<Setter Property="Padding" Value="12,4,12,4" />
|
||||
|
@@ -3,39 +3,36 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<!-- Common theme values -->
|
||||
<x:Double x:Key="MenuSize">60</x:Double>
|
||||
<x:Double x:Key="ExpandedMenuSize">300</x:Double>
|
||||
<GridLength x:Key="MenuGridLength">60</GridLength>
|
||||
<GridLength x:Key="ExpandedMenuGridLength">300</GridLength>
|
||||
<!-- Only available for Windows 10 -->
|
||||
<!--<SolidColorBrush x:Key="MainColor" Color="{ThemeResource SystemAccentColor}" />
|
||||
<SolidColorBrush x:Key="TextColor" Color="{ThemeResource SystemColorHighlightTextColor}" />
|
||||
|
||||
<SolidColorBrush x:Key="CheckBoxForegroundChecked" Color="{ThemeResource MainColor}"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckGlyphForegroundChecked" Color="{ThemeResource TextColorLight}"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeChecked" Color="{ThemeResource MainColor}"/>
|
||||
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillChecked" Color="{ThemeResource MainColor}"/>-->
|
||||
<x:Double x:Key="MenuWidth">60</x:Double>
|
||||
<x:Double x:Key="MenuHeight">40</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">250</GridLength>
|
||||
|
||||
<Color x:Key="MainColor">SlateBlue</Color>
|
||||
<Color x:Key="MainColorLight">MediumPurple</Color>
|
||||
<Color x:Key="MainColorDark">Indigo</Color>
|
||||
<Color x:Key="TextColorLight">WhiteSmoke</Color>
|
||||
<Color x:Key="TextColorDark">DarkSlateGray</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="TextColorDarkBrush" Color="{ThemeResource TextColorDark}" />
|
||||
<SolidColorBrush x:Key="HubSectionBrush" Color="{ThemeResource HubSectionColor}" />
|
||||
|
||||
<Style TargetType="TextBlock" x:Key="TextBlockSettingsHeaderStyle" >
|
||||
<Setter Property="FontFamily" Value="Segoe UI" />
|
||||
<Setter Property="FontSize" Value="14.667" />
|
||||
<Setter Property="FontSize" Value="16" />
|
||||
<Setter Property="FontWeight" Value="SemiLight" />
|
||||
</Style>
|
||||
<Style TargetType="HyperlinkButton">
|
||||
<Setter Property="FontWeight" Value="SemiLight" />
|
||||
</Style>
|
||||
|
||||
<SolidColorBrush x:Key="TextBoxForegroundThemeBrush" Color="{ThemeResource TextColorDark}" />
|
||||
<SolidColorBrush x:Key="TextBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
|
||||
<SolidColorBrush x:Key="TextSelectionHighlightColorThemeBrush" Color="{ThemeResource MainColor}" />
|
||||
|
||||
@@ -50,9 +47,8 @@
|
||||
<SolidColorBrush x:Key="HyperlinkForegroundThemeBrush" Color="{ThemeResource MainColor}" />
|
||||
<SolidColorBrush x:Key="HyperlinkPointerOverForegroundThemeBrush" Color="{ThemeResource MainColorLight}" />
|
||||
|
||||
<SolidColorBrush x:Key="SearchBoxForegroundThemeBrush" Color="{ThemeResource TextColorDark}" />
|
||||
<!--<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}" />
|
||||
<SolidColorBrush x:Key="SearchBoxButtonBackgroundThemeBrush" Color="{ThemeResource MainColor}" />
|
||||
<SolidColorBrush x:Key="SearchBoxHitHighlightForegroundThemeBrush" Color="{ThemeResource MainColor}" />
|
||||
@@ -83,4 +79,14 @@
|
||||
<SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="{ThemeResource MainColor}" />
|
||||
<SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="{ThemeResource MainColorDark}" />
|
||||
<SolidColorBrush x:Key="SliderTrackDecreasePointerOverBackgroundThemeBrush" Color="{ThemeResource MainColorLight}" />
|
||||
|
||||
<Thickness x:Key="MenuFlyoutPresenterThemePadding">0</Thickness>
|
||||
<Thickness x:Key="FlyoutContentThemePadding">5</Thickness>
|
||||
<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>
|
||||
|
@@ -291,7 +291,7 @@
|
||||
TextWrapping="NoWrap"
|
||||
VerticalAlignment="Stretch"
|
||||
Margin="0"
|
||||
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=Text}" />
|
||||
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=Text, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<Button x:Name="ActionButton"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Background="Transparent"
|
||||
@@ -300,8 +300,8 @@
|
||||
Grid.Column="1"
|
||||
Style="{StaticResource ActionButtonStyle}"
|
||||
Content="{TemplateBinding ButtonSymbol}"
|
||||
IsEnabled="{TemplateBinding IsButtonEnabled}"
|
||||
Command="{TemplateBinding ButtonCommand}">
|
||||
Command="{TemplateBinding ButtonCommand}"
|
||||
CommandParameter="{TemplateBinding ButtonCommandParameter}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{TemplateBinding ButtonTooltip}" />
|
||||
</ToolTipService.ToolTip>
|
||||
|
@@ -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>
|
||||
@@ -267,4 +249,28 @@
|
||||
<data name="FileNotFoundTitle" xml:space="preserve">
|
||||
<value>File not found</value>
|
||||
</data>
|
||||
<data name="MessageDialogSaveNameSuggestion" xml:space="preserve">
|
||||
<value>KeePass</value>
|
||||
</data>
|
||||
<data name="CompositeKeyFileTypeDesc" xml:space="preserve">
|
||||
<value>Key file</value>
|
||||
</data>
|
||||
<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>
|
@@ -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>
|
||||
@@ -513,4 +510,58 @@
|
||||
<data name="SettingsSaveDatabaseSuspendDesc.Text" xml:space="preserve">
|
||||
<value>This settings is generally recommended. If you enable it, database will automatically be saved on application suspension and closing, provided your database is less than 1MB big. Saving bigger databases may take too long and Windows may then forcibly kill the app before saving is finished, resulting in your changes not being saved.</value>
|
||||
</data>
|
||||
<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>
|
@@ -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>
|
||||
@@ -267,4 +243,31 @@
|
||||
<data name="FileNotFoundTitle" xml:space="preserve">
|
||||
<value>Fichier manquant</value>
|
||||
</data>
|
||||
<data name="MessageDialogSaveNameSuggestion" xml:space="preserve">
|
||||
<value>KeePass</value>
|
||||
</data>
|
||||
<data name="CompositeKeyFileTypeDesc" xml:space="preserve">
|
||||
<value>Fichier de clé</value>
|
||||
</data>
|
||||
<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>
|
@@ -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>
|
||||
@@ -492,9 +489,6 @@
|
||||
<data name="NewImportFormat.Text" xml:space="preserve">
|
||||
<value>Format</value>
|
||||
</data>
|
||||
<data name="NewImportFormatHelp.Text" xml:space="preserve">
|
||||
<value>Le fichier CSV doit être formatté de la façon suivante: Nom du compte;Login;Mot de passe:URL;Commentaires</value>
|
||||
</data>
|
||||
<data name="CloseButton.Content" xml:space="preserve">
|
||||
<value>Fermer sans sauvegarder</value>
|
||||
</data>
|
||||
@@ -516,4 +510,58 @@
|
||||
<data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve">
|
||||
<value>Confirmer le mot de passe</value>
|
||||
</data>
|
||||
<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>
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,14 +8,18 @@ using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using GalaSoft.MvvmLight.Views;
|
||||
using MediatR;
|
||||
using Messages;
|
||||
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;
|
||||
@@ -28,24 +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 : ObservableObject
|
||||
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;
|
||||
@@ -61,8 +107,9 @@ namespace ModernKeePass.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -74,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; }
|
||||
@@ -85,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));
|
||||
}
|
||||
@@ -129,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +235,7 @@ namespace ModernKeePass.ViewModels
|
||||
if (!HasExpirationDate) return;
|
||||
|
||||
SelectedItem.ExpirationDate = value.Date;
|
||||
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait();
|
||||
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate, false).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +257,7 @@ namespace ModernKeePass.ViewModels
|
||||
set
|
||||
{
|
||||
SelectedItem.HasExpirationDate = value;
|
||||
SetFieldValue(nameof(HasExpirationDate), value).Wait();
|
||||
SetFieldValue(nameof(HasExpirationDate), value, false).Wait();
|
||||
RaisePropertyChanged(nameof(HasExpirationDate));
|
||||
}
|
||||
}
|
||||
@@ -199,7 +268,7 @@ namespace ModernKeePass.ViewModels
|
||||
set
|
||||
{
|
||||
SelectedItem.BackgroundColor = value.ToColor();
|
||||
SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor).Wait();
|
||||
SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor, false).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,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; }
|
||||
@@ -233,6 +301,11 @@ 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();
|
||||
|
||||
@@ -241,21 +314,25 @@ namespace ModernKeePass.ViewModels
|
||||
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());
|
||||
@@ -264,10 +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 };
|
||||
@@ -275,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()
|
||||
@@ -329,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()
|
||||
@@ -351,20 +391,100 @@ 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()
|
||||
{
|
||||
await AddHistory();
|
||||
await _mediator.Send(new SaveDatabaseCommand());
|
||||
SaveCommand.RaiseCanExecuteChanged();
|
||||
_isDirty = false;
|
||||
try
|
||||
{
|
||||
await _mediator.Send(new SaveDatabaseCommand());
|
||||
}
|
||||
catch (SaveException e)
|
||||
{
|
||||
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
|
||||
}
|
||||
UpdateDirtyStatus(false);
|
||||
}
|
||||
|
||||
private async Task Delete()
|
||||
@@ -375,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
@@ -9,6 +8,7 @@ using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.Command;
|
||||
using GalaSoft.MvvmLight.Views;
|
||||
using MediatR;
|
||||
using Messages;
|
||||
using ModernKeePass.Application.Common.Interfaces;
|
||||
using ModernKeePass.Application.Database.Commands.SaveDatabase;
|
||||
using ModernKeePass.Application.Database.Models;
|
||||
@@ -20,20 +20,21 @@ 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;
|
||||
using ModernKeePass.Application.Group.Commands.UpdateGroup;
|
||||
using ModernKeePass.Application.Group.Models;
|
||||
using ModernKeePass.Application.Group.Queries.GetGroup;
|
||||
using ModernKeePass.Application.Group.Queries.SearchEntries;
|
||||
using ModernKeePass.Common;
|
||||
using ModernKeePass.Domain.Enums;
|
||||
using ModernKeePass.Domain.Exceptions;
|
||||
using ModernKeePass.Models;
|
||||
|
||||
namespace ModernKeePass.ViewModels
|
||||
{
|
||||
public class GroupDetailVm : ObservableObject
|
||||
public class GroupDetailVm : ViewModelBase
|
||||
{
|
||||
public ObservableCollection<EntryVm> Entries { get; private set; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -90,7 +91,7 @@ namespace ModernKeePass.ViewModels
|
||||
public RelayCommand SortGroupsCommand { get; }
|
||||
public RelayCommand<string> MoveCommand { get; }
|
||||
public RelayCommand CreateEntryCommand { get; }
|
||||
public RelayCommand CreateGroupCommand { get; }
|
||||
public RelayCommand<string> CreateGroupCommand { get; }
|
||||
public RelayCommand DeleteCommand { get; set; }
|
||||
public RelayCommand GoBackCommand { get; set; }
|
||||
public RelayCommand GoToParentCommand { get; set; }
|
||||
@@ -106,6 +107,7 @@ 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)
|
||||
{
|
||||
@@ -120,10 +122,12 @@ namespace ModernKeePass.ViewModels
|
||||
SortGroupsCommand = new RelayCommand(async () => await SortGroupsAsync(), () => IsEditMode);
|
||||
MoveCommand = new RelayCommand<string>(async destination => await Move(destination), destination => IsNotRoot && !string.IsNullOrEmpty(destination) && destination != Id);
|
||||
CreateEntryCommand = new RelayCommand(async () => await AddNewEntry(), () => !IsInRecycleBin && Database.RecycleBinId != Id);
|
||||
CreateGroupCommand = new RelayCommand(async () => await AddNewGroup(), () => !IsInRecycleBin && Database.RecycleBinId != Id);
|
||||
CreateGroupCommand = new RelayCommand<string>(async newGroupName => await AddNewGroup(newGroupName), _ => !IsInRecycleBin && Database.RecycleBinId != Id);
|
||||
DeleteCommand = new RelayCommand(async () => await AskForDelete(),() => IsNotRoot);
|
||||
GoBackCommand = new RelayCommand(() => _navigation.GoBack());
|
||||
GoToParentCommand= new RelayCommand(() => GoToGroup(_parent.Id), () => _parent != null);
|
||||
|
||||
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
|
||||
}
|
||||
|
||||
public async Task Initialize(string groupId)
|
||||
@@ -137,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)
|
||||
@@ -159,7 +164,7 @@ namespace ModernKeePass.ViewModels
|
||||
public async Task AddNewGroup(string name = "")
|
||||
{
|
||||
var group = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group});
|
||||
GoToGroup(group.Id, true);
|
||||
Groups.Add(group);
|
||||
}
|
||||
|
||||
public async Task AddNewEntry()
|
||||
@@ -175,14 +180,16 @@ namespace ModernKeePass.ViewModels
|
||||
GoToGroup(destinationId);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EntryVm>> Search(string queryText)
|
||||
{
|
||||
return await _mediator.Send(new SearchEntriesQuery {GroupId = Id, SearchText = queryText});
|
||||
}
|
||||
|
||||
private async Task SaveChanges()
|
||||
{
|
||||
await _mediator.Send(new SaveDatabaseCommand());
|
||||
try
|
||||
{
|
||||
await _mediator.Send(new SaveDatabaseCommand());
|
||||
}
|
||||
catch (SaveException e)
|
||||
{
|
||||
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
|
||||
}
|
||||
SaveCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
|
||||
@@ -209,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});
|
||||
@@ -231,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 =>
|
||||
|
@@ -184,6 +184,7 @@ namespace ModernKeePass.ViewModels
|
||||
catch (SaveException exception)
|
||||
{
|
||||
_notification.Show(exception.Source, exception.Message);
|
||||
MessengerInstance.Send(new SaveErrorMessage { Message = exception.Message });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,12 @@ using System.Linq;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using GalaSoft.MvvmLight;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModernKeePass.Application.Common.Interfaces;
|
||||
using ModernKeePass.Application.Database.Queries.GetDatabase;
|
||||
using ModernKeePass.Domain.Interfaces;
|
||||
using ModernKeePass.ViewModels.ListItems;
|
||||
using ModernKeePass.Views;
|
||||
using ModernKeePass.Views.SettingsPageFrames;
|
||||
|
||||
namespace ModernKeePass.ViewModels
|
||||
{
|
||||
@@ -58,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
|
||||
{
|
||||
@@ -79,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);
|
||||
|
@@ -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"
|
||||
@@ -365,153 +365,257 @@
|
||||
</TransitionCollection>
|
||||
</Grid.ChildrenTransitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{StaticResource MenuGridLength}"/>
|
||||
<RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource MenuGridLength}" x:Name="LeftListViewColumn" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView"
|
||||
ItemsSource="{Binding History}"
|
||||
ResizeTarget="{Binding ElementName=LeftListViewColumn}"
|
||||
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
|
||||
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
|
||||
<userControls:HamburgerMenuUserControl
|
||||
x:Name="HamburgerMenu"
|
||||
x:Uid="HistoryLeftListView"
|
||||
ItemsSource="{Binding History}"
|
||||
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="" 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="" />
|
||||
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="" 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="" 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="" 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="" />
|
||||
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="" 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="" 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 MenuGridLength}"/>
|
||||
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
|
||||
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0"
|
||||
Command="{Binding GoBackCommand}"
|
||||
Height="{StaticResource MenuSize}"
|
||||
Width="{StaticResource MenuSize}"
|
||||
Height="{StaticResource MenuHeight}"
|
||||
Width="{StaticResource MenuWidth}"
|
||||
AutomationProperties.Name="Back"
|
||||
AutomationProperties.AutomationId="BackButton"
|
||||
AutomationProperties.ItemType="Navigation Button"
|
||||
Style="{StaticResource NoBorderButtonStyle}">
|
||||
<SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="60" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40" />
|
||||
<RowDefinition Height="20" />
|
||||
</Grid.RowDefinitions>
|
||||
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
|
||||
</Viewbox>
|
||||
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
|
||||
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
|
||||
</Viewbox>
|
||||
<TextBox Grid.Column="1" Grid.Row="0"
|
||||
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>
|
||||
<HyperlinkButton Grid.Column="1" Grid.Row="1"
|
||||
FontWeight="Light" FontSize="12" Padding="0" Margin="5,-5,0,0"
|
||||
Content="{Binding ParentGroupName}"
|
||||
Command="{Binding GoToParentCommand}"/>
|
||||
</Grid>
|
||||
<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>
|
@@ -42,8 +42,24 @@ namespace ModernKeePass.Views
|
||||
|
||||
private void EntryDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, e.NewSize.Width < 700 ? "Small" : "Large", true);
|
||||
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true);
|
||||
if (e.NewSize.Width <= 640)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Small", true);
|
||||
VisualStateManager.GoToState(TopMenu, "Collapsed", true);
|
||||
VisualStateManager.GoToState(HamburgerMenu, "Hidden", true);
|
||||
}
|
||||
else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Medium", true);
|
||||
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
|
||||
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Large", true);
|
||||
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
|
||||
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -36,21 +36,21 @@
|
||||
</TransitionCollection>
|
||||
</Grid.ChildrenTransitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{StaticResource MenuGridLength}"/>
|
||||
<RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource ExpandedMenuGridLength}" x:Name="LeftListViewColumn" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<userControls:HamburgerMenuUserControl
|
||||
x:Name="HamburgerMenu"
|
||||
x:Uid="GroupsLeftListView"
|
||||
ItemsSource="{Binding Groups}"
|
||||
CanDragItems="{Binding IsEditMode}"
|
||||
SelectionChanged="groups_SelectionChanged"
|
||||
ActionButtonCommand="{Binding CreateGroupCommand}"
|
||||
ResizeTarget="{Binding ElementName=LeftListViewColumn}"
|
||||
IsOpen="True"
|
||||
IsButtonVisible="Visible" />
|
||||
<Grid Grid.Column="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -63,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">
|
||||
@@ -84,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>
|
||||
@@ -116,7 +113,7 @@
|
||||
<TextBlock Text="{Binding Username}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
|
||||
<TextBlock Text="{Binding Url}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="2" Style="{StaticResource NoBorderButtonStyle}" Background="{StaticResource AppBarBackgroundThemeBrush}" VerticalAlignment="Bottom" Foreground="{StaticResource TextColorDarkBrush}">
|
||||
<Button Grid.Column="2" Style="{StaticResource NoBorderButtonStyle}" Background="{StaticResource AppBarBackgroundThemeBrush}" VerticalAlignment="Bottom">
|
||||
<SymbolIcon Symbol="More" />
|
||||
<Button.Flyout>
|
||||
<MenuFlyout>
|
||||
@@ -136,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,37 +180,39 @@
|
||||
<!-- Back button and page title -->
|
||||
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{StaticResource MenuGridLength}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
|
||||
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Button Grid.Column="0"
|
||||
Command="{Binding GoBackCommand}"
|
||||
Height="{StaticResource MenuSize}"
|
||||
Width="{StaticResource MenuSize}"
|
||||
Height="{StaticResource MenuHeight}"
|
||||
Width="{StaticResource MenuWidth}"
|
||||
AutomationProperties.Name="Back"
|
||||
AutomationProperties.AutomationId="BackButton"
|
||||
AutomationProperties.ItemType="Navigation Button"
|
||||
Style="{StaticResource NoBorderButtonStyle}">
|
||||
<SymbolIcon Symbol="Back" />
|
||||
</Button>
|
||||
<Grid Grid.Column="1" >
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="60" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40" />
|
||||
<RowDefinition Height="20" />
|
||||
</Grid.RowDefinitions>
|
||||
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
|
||||
</Viewbox>
|
||||
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
|
||||
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
|
||||
</Viewbox>
|
||||
<TextBox Grid.Column="1" Grid.Row="0"
|
||||
<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}"
|
||||
@@ -223,22 +222,17 @@
|
||||
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>
|
||||
<HyperlinkButton Grid.Column="1" Grid.Row="1"
|
||||
FontWeight="Light" FontSize="12" Padding="0" Margin="5,-5,0,0"
|
||||
Content="{Binding ParentGroupName}"
|
||||
Command="{Binding GoToParentCommand}" />
|
||||
</Grid>
|
||||
<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,57 +246,24 @@
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</userControls:TopMenuUserControl>
|
||||
<Button Grid.Column="3" x:Name="SearchButton" Style="{StaticResource NoBorderButtonStyle}" Background="{ThemeResource ToggleButtonBackgroundThemeBrush}" Height="{StaticResource MenuSize}" Padding="25,0,25,0">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<SymbolIcon Symbol="Find" />
|
||||
<TextBlock x:Uid="SearchButtonLabel" x:Name="SearchButtonLabel" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="10,0,0,0" />
|
||||
</StackPanel>
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Uid="SearchButtonTooltip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Click">
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchBox}" PropertyName="Visibility" Value="Visible" />
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchButton}" PropertyName="Visibility" Value="Collapsed" />
|
||||
<!-- TODO: make this work -->
|
||||
<actions:SetupFocusAction TargetObject="{Binding ElementName=SearchBox}" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<SearchBox Grid.Column="3" x:Uid="EntriesSearch" x:Name="SearchBox" Padding="12" Width="350" Visibility="Collapsed" Margin="0,5,0,5" FontSize="15" SuggestionsRequested="SearchBox_OnSuggestionsRequested" SearchHistoryEnabled="False" ResultSuggestionChosen="SearchBox_OnResultSuggestionChosen">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="LostFocus">
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchBox}" PropertyName="Visibility" Value="Collapsed" />
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchButton}" PropertyName="Visibility" Value="Visible" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</SearchBox>
|
||||
|
||||
</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="TopMenuGroup">
|
||||
<VisualStateGroup x:Name="PageLayout">
|
||||
<VisualState x:Name="Small">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Medium">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
@@ -311,8 +272,8 @@
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
|
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using ModernKeePass.Application.Entry.Models;
|
||||
@@ -78,31 +74,26 @@ namespace ModernKeePass.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void GridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
|
||||
{
|
||||
e.Cancel = !Model.IsEditMode;
|
||||
e.Data.RequestedOperation = DataPackageOperation.Move;
|
||||
}
|
||||
|
||||
private async void SearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchBox_OnResultSuggestionChosen(SearchBox sender, SearchBoxResultSuggestionChosenEventArgs args)
|
||||
{
|
||||
Model.GoToEntry(args.Tag);
|
||||
}
|
||||
|
||||
private void GroupDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
VisualStateManager.GoToState(this, e.NewSize.Width < 800 ? "Small" : "Large", true);
|
||||
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true);
|
||||
if (e.NewSize.Width <= 640)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Small", true);
|
||||
VisualStateManager.GoToState(TopMenu, "Collapsed", true);
|
||||
VisualStateManager.GoToState(HamburgerMenu, "Hidden", true);
|
||||
}
|
||||
else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008)
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Medium", true);
|
||||
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
|
||||
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
VisualStateManager.GoToState(this, "Large", true);
|
||||
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
|
||||
VisualStateManager.GoToState(HamburgerMenu, "Expanded", true);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@@ -27,7 +27,7 @@
|
||||
</TransitionCollection>
|
||||
</Grid.ChildrenTransitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{StaticResource MenuGridLength}"/>
|
||||
<RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -44,7 +44,7 @@
|
||||
<Button x:Name="BackButton"
|
||||
Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}"
|
||||
Visibility="Collapsed"
|
||||
Height="{StaticResource MenuSize}"
|
||||
Height="{StaticResource MenuWidth}"
|
||||
AutomationProperties.Name="Back"
|
||||
AutomationProperties.AutomationId="BackButton"
|
||||
AutomationProperties.ItemType="Navigation Button"
|
||||
@@ -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>
|
||||
|
@@ -6,15 +6,14 @@
|
||||
xmlns:viewModels="using:ModernKeePass.ViewModels"
|
||||
x:Class="ModernKeePass.Views.AboutPage"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Page.DataContext>
|
||||
<viewModels:AboutVm/>
|
||||
</Page.DataContext>
|
||||
|
||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,0">
|
||||
<Run Text="{Binding Name}"/>
|
||||
<Run Text="version"/>
|
||||
<Run Text="{Binding Name}" />
|
||||
<Run Text="version" />
|
||||
<Run Text="{Binding Version}" />
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="30,0,0,0">
|
||||
@@ -23,7 +22,7 @@
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="30,0,0,0">
|
||||
<Run x:Uid="AboutHomepage" />
|
||||
<Hyperlink NavigateUri="https://wismna.github.io/ModernKeePass/">
|
||||
<Run Text="https://wismna.github.io/ModernKeePass/"/>
|
||||
<Run Text="https://wismna.github.io/ModernKeePass/" />
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,0">
|
||||
|
@@ -20,10 +20,10 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="Import" Style="{StaticResource SubheaderTextBlockStyle}" />
|
||||
<HyperlinkButton Grid.Column="0" Grid.Row="1" Content="Select file..." Style="{StaticResource MainColorHyperlinkButton}" Click="ImportFileButton_OnClick" />
|
||||
<HyperlinkButton Grid.Column="0" Grid.Row="1" Content="Select file..." />
|
||||
<StackPanel Grid.Column="1" Grid.Row="1" >
|
||||
<TextBlock Text="Format" Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,0,10" />
|
||||
<ComboBox Style="{StaticResource MainColorComboBox}">
|
||||
<ComboBox>
|
||||
<ComboBoxItem>CSV</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
@@ -32,6 +32,6 @@
|
||||
<RadioButton GroupName="ImportDestination" Content="New database" />
|
||||
<RadioButton GroupName="ImportDestination" Content="Currently opened database" />
|
||||
</StackPanel>
|
||||
<Button Grid.Column="3" Grid.Row="1" Content="Import" Style="{StaticResource MainColorButton}" />
|
||||
<Button Grid.Column="3" Grid.Row="1" Content="Import" />
|
||||
</Grid>
|
||||
</Page>
|
||||
|
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.UI.Xaml;
|
||||
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace ModernKeePass.Views
|
||||
{
|
||||
@@ -15,21 +11,5 @@ namespace ModernKeePass.Views
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void ImportFileButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker =
|
||||
new FileOpenPicker
|
||||
{
|
||||
ViewMode = PickerViewMode.List,
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
|
||||
};
|
||||
picker.FileTypeFilter.Add(".csv");
|
||||
|
||||
// Application now has read/write access to the picked file
|
||||
var file = await picker.PickSingleFileAsync().AsTask();
|
||||
if (file == null) return;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
</Page.Resources>
|
||||
|
||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<HyperlinkButton x:Uid="NewCreateButton" Click="CreateDatabaseButton_OnClick" />
|
||||
<HyperlinkButton x:Uid="NewCreateButton" Command="{Binding CreateDatabaseFileCommand}" />
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="NewCreateDesc" />
|
||||
<Border HorizontalAlignment="Left" BorderThickness="1" BorderBrush="AliceBlue" Width="550" Visibility="{Binding IsFileSelected, Converter={StaticResource BooleanToVisibilityConverter}}">
|
||||
<StackPanel Margin="25,0,25,0">
|
||||
|
@@ -1,11 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Storage.AccessCache;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.UI.Xaml;
|
||||
using ModernKeePass.ViewModels;
|
||||
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace ModernKeePass.Views
|
||||
{
|
||||
@@ -14,28 +7,9 @@ namespace ModernKeePass.Views
|
||||
/// </summary>
|
||||
public sealed partial class NewDatabasePage
|
||||
{
|
||||
private NewVm Model => (NewVm)DataContext;
|
||||
|
||||
public NewDatabasePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void CreateDatabaseButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var savePicker = new FileSavePicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
|
||||
SuggestedFileName = "New Database"
|
||||
};
|
||||
savePicker.FileTypeChoices.Add("KeePass 2.x database", new List<string> {".kdbx"});
|
||||
|
||||
var file = await savePicker.PickSaveFileAsync().AsTask();
|
||||
if (file == null) return;
|
||||
|
||||
Model.Token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
|
||||
Model.Name = file.Name;
|
||||
Model.Path = file.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,7 @@
|
||||
</Page.Resources>
|
||||
|
||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<HyperlinkButton x:Uid="OpenBrowseButton" Click="ButtonBase_OnClick" />
|
||||
<HyperlinkButton x:Uid="OpenBrowseButton" Command="{Binding OpenDatabaseFileCommand}" />
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="OpenBrowseDesc" />
|
||||
<!--<HyperlinkButton x:Uid="OpenUrlButton" IsEnabled="False" Foreground="{StaticResource MainColor}" Style="{StaticResource MainColorHyperlinkButton}" />
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="OpenUrlDesc" />-->
|
||||
|
@@ -1,8 +1,4 @@
|
||||
using System;
|
||||
using Windows.Storage.AccessCache;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
using ModernKeePass.Domain.Dtos;
|
||||
using ModernKeePass.ViewModels;
|
||||
|
||||
@@ -26,31 +22,7 @@ namespace ModernKeePass.Views
|
||||
{
|
||||
base.OnNavigatedTo(e);
|
||||
var file = e.Parameter as FileInfo;
|
||||
if (file != null)
|
||||
{
|
||||
Model.Path = file.Path;
|
||||
Model.Name = file.Name;
|
||||
Model.Token = file.Id;
|
||||
}
|
||||
}
|
||||
|
||||
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = new FileOpenPicker
|
||||
{
|
||||
ViewMode = PickerViewMode.List,
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
|
||||
};
|
||||
picker.FileTypeFilter.Add(".kdbx");
|
||||
|
||||
// Application now has read/write access to the picked file
|
||||
var file = await picker.PickSingleFileAsync().AsTask();
|
||||
if (file == null) return;
|
||||
|
||||
// TODO: use service
|
||||
Model.Token = StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path);
|
||||
Model.Path = file.Path;
|
||||
Model.Name = file.Name;
|
||||
Model.SetFileInformation(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,15 +3,12 @@
|
||||
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 x:Name="Grid" Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="40" />
|
||||
<RowDefinition Height="*" />
|
||||
@@ -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}}"
|
||||
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>
|
@@ -1,7 +1,5 @@
|
||||
// Pour en savoir plus sur le modèle d'élément Page vierge, consultez la page http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
using ModernKeePass.ViewModels;
|
||||
|
||||
namespace ModernKeePass.Views
|
||||
{
|
||||
/// <summary>
|
||||
@@ -9,8 +7,6 @@ namespace ModernKeePass.Views
|
||||
/// </summary>
|
||||
public sealed partial class RecentDatabasesPage
|
||||
{
|
||||
private RecentVm Model => (RecentVm)DataContext;
|
||||
|
||||
public RecentDatabasesPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
|
||||
<HyperlinkButton x:Uid="SaveButton" Command="{Binding SaveCommand}" />
|
||||
<TextBlock x:Uid="SaveDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" />
|
||||
<HyperlinkButton x:Uid="SaveAsButton" Click="SaveAsButton_OnClick" />
|
||||
<HyperlinkButton x:Uid="SaveAsButton" Command="{Binding SaveAsCommand}" />
|
||||
<TextBlock x:Uid="SaveAsDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" />
|
||||
<HyperlinkButton x:Uid="CloseButton" Command="{Binding CloseCommand}" />
|
||||
<TextBlock x:Uid="CloseDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" />
|
||||
|
@@ -1,10 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.Storage.Pickers;
|
||||
using Windows.UI.Xaml;
|
||||
using ModernKeePass.ViewModels;
|
||||
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
|
||||
|
||||
namespace ModernKeePass.Views
|
||||
{
|
||||
@@ -13,24 +7,9 @@ namespace ModernKeePass.Views
|
||||
/// </summary>
|
||||
public sealed partial class SaveDatabasePage
|
||||
{
|
||||
public SaveVm Model => (SaveVm)DataContext;
|
||||
public SaveDatabasePage()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void SaveAsButton_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var savePicker = new FileSavePicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
|
||||
SuggestedFileName = "New Database"
|
||||
};
|
||||
savePicker.FileTypeChoices.Add("KeePass 2.x database", new List<string> { ".kdbx" });
|
||||
|
||||
var file = await savePicker.PickSaveFileAsync().AsTask();
|
||||
if (file == null) return;
|
||||
await Model.Save(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>
|
||||
|
@@ -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>
|
@@ -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();
|
||||
}
|
@@ -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>
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -13,18 +13,12 @@
|
||||
</StackPanel.Resources>
|
||||
<TextBlock x:Uid="SettingsNewDatabaseDesc" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,10"/>
|
||||
<ToggleSwitch x:Uid="SettingsNewDatabaseSample" IsOn="{Binding IsCreateSample, Mode=TwoWay}" />
|
||||
<TextBlock x:Uid="SettingsNewDatabaseKdf" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" />
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<ComboBox Grid.Column="1" ItemsSource="{Binding Source={StaticResource KeyDerivations}}" SelectedItem="{Binding DatabaseFormatVersion, Mode=TwoWay}" DisplayMemberPath="DisplayText" />
|
||||
<Button Grid.Column="2" Style="{StaticResource TextBlockButtonStyle}">
|
||||
<SymbolIcon Symbol="Help" RenderTransformOrigin="0.5,0.5" >
|
||||
<StackPanel Orientation="Horizontal" Margin="5,20,0,10">
|
||||
<TextBlock x:Uid="SettingsNewDatabaseKdf" Style="{StaticResource TextBlockSettingsHeaderStyle}" />
|
||||
<Button Style="{StaticResource TextBlockButtonStyle}" Foreground="{StaticResource ButtonPointerOverForegroundThemeBrush}" Margin="0,-2,0,0">
|
||||
<SymbolIcon Symbol="Help" RenderTransformOrigin="0.5,0.5">
|
||||
<SymbolIcon.RenderTransform>
|
||||
<CompositeTransform ScaleX="0.7" ScaleY="0.7"/>
|
||||
<CompositeTransform ScaleX="0.6" ScaleY="0.6"/>
|
||||
</SymbolIcon.RenderTransform>
|
||||
</SymbolIcon>
|
||||
<Button.Flyout>
|
||||
@@ -33,6 +27,7 @@
|
||||
</Flyout>
|
||||
</Button.Flyout>
|
||||
</Button>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<ComboBox ItemsSource="{Binding Source={StaticResource KeyDerivations}}" SelectedItem="{Binding DatabaseFormatVersion, Mode=TwoWay}" DisplayMemberPath="DisplayText" />
|
||||
</StackPanel>
|
||||
</Page>
|
||||
|
@@ -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>
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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>
|
||||
|
@@ -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>
|
@@ -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) => { }));
|
||||
}
|
||||
}
|
@@ -4,140 +4,208 @@
|
||||
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"
|
||||
xmlns:actions="using:ModernKeePass.Actions"
|
||||
xmlns:controls="using:ModernKeePass.Controls"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/>
|
||||
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
|
||||
</UserControl.Resources>
|
||||
<ListView
|
||||
ItemsSource="{Binding ItemsSource, ElementName=UserControl}"
|
||||
SelectionChanged="Selector_OnSelectionChanged"
|
||||
SelectedItem="{Binding SelectedItem, ElementName=UserControl, Mode=TwoWay}"
|
||||
SelectedIndex="{Binding SelectedIndex, ElementName=UserControl, Mode=TwoWay}"
|
||||
IsSwipeEnabled="false"
|
||||
IsSynchronizedWithCurrentItem="False"
|
||||
RequestedTheme="Dark"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
Foreground="{ThemeResource TextColorLightBrush}"
|
||||
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>
|
||||
<DataTemplate x:Name="IsNormal">
|
||||
<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" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.Resources>
|
||||
<ListView.ItemTemplateSelector>
|
||||
<templateSelectors:SelectableDataTemplateSelector FalseItem="{StaticResource IsNormal}" TrueItem="{StaticResource IsSpecial}" />
|
||||
</ListView.ItemTemplateSelector>
|
||||
<ListView.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip Content="{Binding HeaderLabel, ElementName=UserControl}" />
|
||||
</ToolTipService.ToolTip>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Checked">
|
||||
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource ExpandedMenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
|
||||
</core:EventTriggerBehavior>
|
||||
<core:EventTriggerBehavior EventName="Unchecked">
|
||||
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource MenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ToggleButton>
|
||||
<TextBlock Text="{Binding HeaderLabel, ElementName=UserControl}" FontWeight="Bold" FontSize="18" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="30,0,20,0" HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.HeaderTemplate>
|
||||
<ListView.FooterTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="White" BorderThickness="0,0,0,1" />
|
||||
<Button Padding="0" Margin="0"
|
||||
Height="{StaticResource MenuSize}"
|
||||
<Grid HorizontalAlignment="Left">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="VisibilityStates">
|
||||
<VisualState x:Name="Hidden">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderTextBlock" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Collapsed">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Width">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource MenuWidth}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderTextBlock" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Expanded">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Width">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ExpandedMenuSize}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderTextBlock" Storyboard.TargetProperty="Visibility">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{StaticResource MenuHeightGridLength}" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}" Unchecked="ToggleButton_OnUnchecked">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Checked">
|
||||
<core:GoToStateAction StateName="Expanded" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ToggleButton>
|
||||
<TextBlock
|
||||
x:Name="HeaderTextBlock"
|
||||
Text="{Binding HeaderLabel, ElementName=UserControl}"
|
||||
FontWeight="Bold"
|
||||
FontSize="18"
|
||||
TextWrapping="NoWrap"
|
||||
VerticalAlignment="Center"
|
||||
Margin="30,0,20,0"
|
||||
HorizontalAlignment="Center" />
|
||||
</StackPanel>
|
||||
<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}"
|
||||
SelectedIndex="{Binding SelectedIndex, ElementName=UserControl, Mode=TwoWay}"
|
||||
IsSwipeEnabled="false"
|
||||
IsSynchronizedWithCurrentItem="False"
|
||||
Background="{ThemeResource AppBarBackgroundThemeBrush}"
|
||||
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:Name="IsNormal">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<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="20,0,10,0" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<ListView.HeaderTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Vertical" Visibility="{Binding IsButtonVisible, ElementName=UserControl}">
|
||||
<Button x:Name="NewGroupButton"
|
||||
Padding="0" Margin="0"
|
||||
Height="{StaticResource MenuWidth}"
|
||||
Visibility="{Binding IsButtonVisible, ElementName=UserControl}"
|
||||
Style="{StaticResource NoBorderButtonStyle}"
|
||||
Background="Transparent"
|
||||
Foreground="{ThemeResource TextColorLightBrush}"
|
||||
BorderThickness="0"
|
||||
Width="{StaticResource ExpandedMenuSize}"
|
||||
HorizontalContentAlignment="Left"
|
||||
Command="{Binding ActionButtonCommand, ElementName=UserControl}">
|
||||
<StackPanel Orientation="Horizontal" Margin="17,0,5,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" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
<Button Padding="0" Margin="0"
|
||||
Height="{StaticResource MenuSize}"
|
||||
Style="{StaticResource NoBorderButtonStyle}"
|
||||
Foreground="{ThemeResource TextColorLightBrush}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Width="{StaticResource ExpandedMenuSize}"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Margin="17,0,5,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" />
|
||||
</StackPanel>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Click">
|
||||
<core:NavigateToPageAction TargetPage="ModernKeePass.Views.MainPage" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<Button
|
||||
Padding="0" Margin="0"
|
||||
Height="{StaticResource MenuSize}"
|
||||
Style="{StaticResource NoBorderButtonStyle}"
|
||||
Foreground="{ThemeResource TextColorLightBrush}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Width="{StaticResource ExpandedMenuSize}"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Margin="17,0,5,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" />
|
||||
</StackPanel>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Click">
|
||||
<core:NavigateToPageAction TargetPage="ModernKeePass.Views.SettingsPage" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.FooterTemplate>
|
||||
</ListView>
|
||||
<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="20,0,0,0" />
|
||||
</StackPanel>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Click">
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Visible" />
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Collapsed" />
|
||||
<actions:SetupFocusAction TargetObject="{Binding ElementName=NewGroupTextBox}" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<controls:TextBoxWithButton
|
||||
x:Uid="NewGroupTextBox"
|
||||
x:Name="NewGroupTextBox"
|
||||
Margin="0,5,0,5"
|
||||
Visibility="Collapsed"
|
||||
Width="230"
|
||||
HorizontalAlignment="Center"
|
||||
ButtonCommand="{Binding ActionButtonCommand, ElementName=UserControl}"
|
||||
ButtonCommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
|
||||
Style="{StaticResource TextBoxWithButtonStyle}"
|
||||
KeyDown="NewGroupTextBox_OnKeyDown"
|
||||
ButtonSymbol="">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="LostFocus">
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Visible" />
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Collapsed" />
|
||||
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Text" Value="" />
|
||||
</core:EventTriggerBehavior>
|
||||
</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>
|
||||
<ListView.FooterTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
|
||||
<Button Padding="0" Margin="0"
|
||||
Height="{StaticResource MenuWidth}"
|
||||
Style="{StaticResource NoBorderButtonStyle}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Width="{StaticResource ExpandedMenuSize}"
|
||||
HorizontalContentAlignment="Left">
|
||||
<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="20,0,0,0" />
|
||||
</StackPanel>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Click">
|
||||
<core:NavigateToPageAction TargetPage="ModernKeePass.Views.MainPage" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
<Button
|
||||
Padding="0" Margin="0"
|
||||
Height="{StaticResource MenuWidth}"
|
||||
Style="{StaticResource NoBorderButtonStyle}"
|
||||
Background="Transparent"
|
||||
BorderThickness="0"
|
||||
Width="{StaticResource ExpandedMenuSize}"
|
||||
HorizontalContentAlignment="Left">
|
||||
<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="20,0,0,0" />
|
||||
</StackPanel>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<core:EventTriggerBehavior EventName="Click">
|
||||
<core:NavigateToPageAction TargetPage="ModernKeePass.Views.SettingsPage" />
|
||||
</core:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.FooterTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows.Input;
|
||||
using Windows.System;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Input;
|
||||
using ModernKeePass.Application.Common.Interfaces;
|
||||
using ModernKeePass.Controls;
|
||||
|
||||
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
|
||||
|
||||
@@ -11,11 +14,6 @@ namespace ModernKeePass.Views.UserControls
|
||||
{
|
||||
public sealed partial class HamburgerMenuUserControl
|
||||
{
|
||||
public HamburgerMenuUserControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public string HeaderLabel
|
||||
{
|
||||
get { return (string)GetValue(HeaderLabelProperty); }
|
||||
@@ -52,18 +50,6 @@ namespace ModernKeePass.Views.UserControls
|
||||
typeof(HamburgerMenuUserControl),
|
||||
new PropertyMetadata("Title", (o, args) => { }));
|
||||
|
||||
public object ResizeTarget
|
||||
{
|
||||
get { return GetValue(ResizeTargetProperty); }
|
||||
set { SetValue(ResizeTargetProperty, value); }
|
||||
}
|
||||
public static readonly DependencyProperty ResizeTargetProperty =
|
||||
DependencyProperty.Register(
|
||||
nameof(ResizeTarget),
|
||||
typeof(object),
|
||||
typeof(HamburgerMenuUserControl),
|
||||
new PropertyMetadata(null, (o, args) => { }));
|
||||
|
||||
public Visibility IsButtonVisible
|
||||
{
|
||||
get { return (Visibility)GetValue(IsButtonVisibleProperty); }
|
||||
@@ -125,6 +111,18 @@ namespace ModernKeePass.Views.UserControls
|
||||
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
|
||||
{
|
||||
get { return (ICommand)GetValue(ActionButtonCommandProperty); }
|
||||
@@ -137,11 +135,32 @@ namespace ModernKeePass.Views.UserControls
|
||||
typeof(HamburgerMenuUserControl),
|
||||
new PropertyMetadata(null, (o, args) => { }));
|
||||
|
||||
|
||||
public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
|
||||
|
||||
public HamburgerMenuUserControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
SelectionChanged?.Invoke(sender, e);
|
||||
}
|
||||
|
||||
private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var parent = Parent as FrameworkElement;
|
||||
if (parent == null) return;
|
||||
VisualStateManager.GoToState(this, parent.ActualWidth <= 640 ? "Hidden" : "Collapsed", true);
|
||||
}
|
||||
|
||||
private void NewGroupTextBox_OnKeyDown(object sender, KeyRoutedEventArgs e)
|
||||
{
|
||||
if (e.Key != VirtualKey.Enter) return;
|
||||
var textBox = sender as TextBoxWithButton;
|
||||
ActionButtonCommand.Execute(textBox?.Text);
|
||||
// Stop the event from triggering twice
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -40,8 +40,7 @@
|
||||
<HyperlinkButton Grid.Row="1" Grid.Column="1" Margin="-15,0,0,0"
|
||||
x:Name="HyperlinkButton"
|
||||
Content="{Binding KeyFileText}"
|
||||
IsEnabled="{Binding HasKeyFile}"
|
||||
Click="KeyFileButton_Click" />
|
||||
Command="{Binding OpenKeyFileCommand}" />
|
||||
|
||||
<Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2"
|
||||
x:Uid="OpenDatabaseControlButton"
|
||||
@@ -79,6 +78,7 @@
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<!-- TODO: Correct the Runtime template binding error (even though it is actually working as intended) -->
|
||||
<core:DataTriggerBehavior Binding="{Binding IsError}" Value="True">
|
||||
<core:GoToStateAction StateName="Error"/>
|
||||
</core:DataTriggerBehavior>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user