37 Commits
V1.16 ... V1.19

Author SHA1 Message Date
Geoffroy BONNEVILLE
1c6fb0f2bb Removed useless collectionviewsource
Updated some packages
2020-06-02 15:57:14 +02:00
Geoffroy BONNEVILLE
e5b35dc6ab SemanticView zoomed out layout and design improvements 2020-06-02 13:16:36 +02:00
Geoffroy BONNEVILLE
ce48850566 Fix Sonar issues 2020-06-01 23:03:57 +02:00
Geoffroy BONNEVILLE
5d8d996f44 Working ColorPickerUserControl 2020-06-01 10:32:06 +02:00
Geoffroy BONNEVILLE
4000d51f70 Update PasswordLength property on password generation 2020-05-26 19:06:59 +02:00
Geoffroy BONNEVILLE
0c70b5146f Create entry history only if DB is open
Fix issues in entry field names
Entry field names cleanup and refactoring
2020-05-26 13:38:07 +02:00
Geoffroy BONNEVILLE
3ecee4a821 Fix ClipboardAction so that it only clears Clipboard when Window is active 2020-05-26 12:30:52 +02:00
Geoffroy BONNEVILLE
3d436c56fa Password generation button with display toggle and indicator is now a user control
SetCredentials user controls now uses PasswordGenerationBox user control
Some layout improvements in EntryDetailsPage
WIP Clipboard suspend issues
2020-05-25 19:23:32 +02:00
Geoffroy BONNEVILLE
0e05e3fbca Fix Sonar issues 2020-05-20 19:03:31 +02:00
Geoffroy BONNEVILLE
45b5ae5630 ColorPickerControl finally doesn't set database to dirty when there is an initial value 2020-05-20 17:40:06 +02:00
Geoffroy BONNEVILLE
643fb9a3f2 Working protected fields (warning: check performance) 2020-05-20 11:59:40 +02:00
Geoffroy BONNEVILLE
b7f8853ef2 WIP Protect/Unprotect Additional Field on selection 2020-05-18 22:20:31 +02:00
Geoffroy BONNEVILLE
9126307b4c Cryptography service now handles random byte generation
Protected strings are now protected in memory
2020-05-18 14:14:28 +02:00
Geoffroy BONNEVILLE
ceaf7dabd3 Fix Sonar issues 2020-05-14 17:06:39 +02:00
Geoffroy BONNEVILLE
7a2ce30512 Design improvements 2020-05-14 16:09:06 +02:00
Geoffroy BONNEVILLE
d497f69a5e Updated deleted text information
Improved dirty status detection (restored removed variable...)
This corrected history creation on navigation when entry deleted
2020-05-14 13:32:44 +02:00
Geoffroy BONNEVILLE
72e5bf4ee1 Added a cryptography service to encrypt protected information (unused atm)
Corrected a bug when deleting recycle bin
2020-05-14 12:05:05 +02:00
Geoffroy BONNEVILLE
2e01fa2212 Changed tooltip styles
Removed useless isdirty field in entry
When an app can't be saved on suspend, don't reopen it to avoid possible de-sync
2020-05-13 18:59:26 +02:00
Geoffroy BONNEVILLE
0adb44bc81 Some design changes
Again fixed open url bug
2020-05-13 15:14:58 +02:00
Geoffroy BONNEVILLE
d38d6461bd Updated Settings page
Added new settings (history and clipboard)
Renamed and moved Settings Vms
2020-05-13 13:50:33 +02:00
Geoffroy BONNEVILLE
7ac1595aaa Clipboard action now sets an expiration timer
WIP Max History count (back-end done, front-end todo)
2020-05-12 18:43:37 +02:00
Geoffroy BONNEVILLE
f8f7c19f65 Display a big database size warning
Auto rename additional field when it matches standard
Treated all fields as new Field class
Added the Is Protected property
2020-05-12 17:14:30 +02:00
Geoffroy BONNEVILLE
d6dc6a74a3 Groups can now also be manually reordered
Design improvements
2020-05-11 19:22:41 +02:00
Geoffroy BONNEVILLE
bb2b99ed66 Additional fields Add, Update and Delete work (too well)
SelectableListView works when programmatically setting selection
2020-05-11 10:53:14 +02:00
Geoffroy BONNEVILLE
71b6009a29 Remove some useless code (again)
Improve some visuals
2020-05-07 19:10:25 +02:00
Geoffroy BONNEVILLE
fbcc354809 Additional fields rendering done
Removed lots of unused classes
2020-05-07 16:01:59 +02:00
Geoffroy BONNEVILLE
e901afaf29 Attachment Add and Delete commands implemented 2020-05-07 12:11:12 +02:00
Geoffroy BONNEVILLE
ca04a6c8ee Entry page is now a Hub
EntryDetailVM and GroupDetailVM are now singleton
Read-only Additional fields and Attachments
2020-05-06 18:54:39 +02:00
Geoffroy BONNEVILLE
1488c3244f Better exception handling 2020-05-05 19:26:38 +02:00
Geoffroy BONNEVILLE
2e22a2bd92 Added some translations in file pickers
Corrected key file creation picker issue (any not allowed)
Fixed some Sonar issues
2020-05-05 17:32:07 +02:00
Geoffroy BONNEVILLE
8fb468358e Hamburger button state is now correct (no more double clicks) but it's a bit hacky
Changed Help tooltip location in New Database Settings page
Suggest Save As when opening DB when another is opened and there is a save error
2020-05-05 16:59:49 +02:00
Geoffroy BONNEVILLE
5ce0262318 TextBoxWithButton control correctly updates field value
Create Group now allows inline input of the group name
2020-05-05 15:27:34 +02:00
Geoffroy BONNEVILLE
2f30389f6c Big redesign (closer to Win10 UWP)
Replaced breadcrumb with Up button
Search button now integrated in top menu
Hamburger menu make better use of visual states
Better visual states changes when size changes
2020-05-04 20:56:19 +02:00
Geoffroy BONNEVILLE
b3c7683c12 Send a message on save to update commands can execute 2020-05-04 14:29:52 +02:00
Geoffroy BONNEVILLE
1e7662def7 Save error is now handled via Messenger instead of unhandled exception handler (which didn't work)
Save as actually works now
WIP Styles
Code cleanup
2020-05-04 12:48:27 +02:00
Geoffroy BONNEVILLE
97b10baedc Resuming correctly re-opens the previsouly opened database 2020-05-02 14:39:42 +02:00
Geoffroy BONNEVILLE
654bd6b4e5 Corrected some Sonar issues
Changed a little bit the Open and Set credentials User controls
2020-05-02 14:21:59 +02:00
174 changed files with 4002 additions and 2124 deletions

View File

@@ -77,23 +77,30 @@
<ItemGroup> <ItemGroup>
<Compile Include="Common\Behaviors\DirtyStatusBehavior.cs" /> <Compile Include="Common\Behaviors\DirtyStatusBehavior.cs" />
<Compile Include="Common\Interfaces\ICryptographyClient.cs" /> <Compile Include="Common\Interfaces\ICryptographyClient.cs" />
<Compile Include="Common\Interfaces\IDatabaseSettingsProxy.cs" />
<Compile Include="Common\Interfaces\IDatabaseProxy.cs" /> <Compile Include="Common\Interfaces\IDatabaseProxy.cs" />
<Compile Include="Common\Interfaces\IEntityVm.cs" /> <Compile Include="Common\Interfaces\IEntityVm.cs" />
<Compile Include="Common\Interfaces\IFileProxy.cs" /> <Compile Include="Common\Interfaces\IFileProxy.cs" />
<Compile Include="Common\Interfaces\IImportFormat.cs" /> <Compile Include="Common\Interfaces\IImportFormat.cs" />
<Compile Include="Common\Interfaces\ICredentialsProxy.cs" /> <Compile Include="Common\Interfaces\ICredentialsProxy.cs" />
<Compile Include="Common\Interfaces\ILogger.cs" />
<Compile Include="Common\Interfaces\INotificationService.cs" /> <Compile Include="Common\Interfaces\INotificationService.cs" />
<Compile Include="Common\Interfaces\IRecentProxy.cs" /> <Compile Include="Common\Interfaces\IRecentProxy.cs" />
<Compile Include="Common\Interfaces\IResourceProxy.cs" /> <Compile Include="Common\Interfaces\IResourceProxy.cs" />
<Compile Include="Common\Interfaces\ISettingsProxy.cs" /> <Compile Include="Common\Interfaces\ISettingsProxy.cs" />
<Compile Include="Common\Mappings\IMapFrom.cs" /> <Compile Include="Common\Mappings\IMapFrom.cs" />
<Compile Include="Common\Mappings\MappingProfile.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\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\DeleteHistory\DeleteHistoryCommand.cs" />
<Compile Include="Entry\Commands\RestoreHistory\RestoreHistoryCommand.cs" /> <Compile Include="Entry\Commands\RestoreHistory\RestoreHistoryCommand.cs" />
<Compile Include="Entry\Models\FieldVm.cs" />
<Compile Include="Entry\Queries\GetEntry\GetEntryQuery.cs" /> <Compile Include="Entry\Queries\GetEntry\GetEntryQuery.cs" />
<Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" /> <Compile Include="Group\Commands\DeleteEntry\DeleteEntryCommand.cs" />
<Compile Include="Group\Commands\DeleteGroup\DeleteGroupCommand.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\Commands\UpdateGroup\UpdateGroupCommand.cs" />
<Compile Include="Group\Queries\GetAllGroups\GetAllGroupsQuery.cs" /> <Compile Include="Group\Queries\GetAllGroups\GetAllGroupsQuery.cs" />
<Compile Include="Group\Queries\GetGroup\GetGroupQuery.cs" /> <Compile Include="Group\Queries\GetGroup\GetGroupQuery.cs" />
@@ -101,7 +108,9 @@
<Compile Include="Parameters\Commands\SetCipher\SetCipherCommand.cs" /> <Compile Include="Parameters\Commands\SetCipher\SetCipherCommand.cs" />
<Compile Include="Parameters\Commands\SetCompression\SetCompressionCommand.cs" /> <Compile Include="Parameters\Commands\SetCompression\SetCompressionCommand.cs" />
<Compile Include="Parameters\Commands\SetHasRecycleBin\SetHasRecycleBinCommand.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\SetKeyDerivation\SetKeyDerivationCommand.cs" />
<Compile Include="Parameters\Commands\SetMaxHistorySize\SetMaxHistorySizeCommand.cs" />
<Compile Include="Parameters\Commands\SetRecycleBin\SetRecycleBinCommand.cs" /> <Compile Include="Parameters\Commands\SetRecycleBin\SetRecycleBinCommand.cs" />
<Compile Include="Parameters\Models\CipherVm.cs" /> <Compile Include="Parameters\Models\CipherVm.cs" />
<Compile Include="Parameters\Models\KeyDerivationVm.cs" /> <Compile Include="Parameters\Models\KeyDerivationVm.cs" />
@@ -119,8 +128,8 @@
<Compile Include="Database\Queries\OpenDatabase\OpenDatabaseQueryValidator.cs" /> <Compile Include="Database\Queries\OpenDatabase\OpenDatabaseQueryValidator.cs" />
<Compile Include="Database\Queries\ReOpenDatabase\ReOpenDatabaseQuery.cs" /> <Compile Include="Database\Queries\ReOpenDatabase\ReOpenDatabaseQuery.cs" />
<Compile Include="DependencyInjection.cs" /> <Compile Include="DependencyInjection.cs" />
<Compile Include="Entry\Commands\SetFieldValue\SetFieldValueCommand.cs" /> <Compile Include="Entry\Commands\UpsertField\UpsertFieldCommand.cs" />
<Compile Include="Entry\Commands\SetFieldValue\SetFieldValueCommandValidator.cs" /> <Compile Include="Entry\Commands\UpsertField\UpsertFieldCommandValidator.cs" />
<Compile Include="Entry\Models\EntryVm.cs" /> <Compile Include="Entry\Models\EntryVm.cs" />
<Compile Include="Group\Commands\AddEntry\AddEntryCommand.cs" /> <Compile Include="Group\Commands\AddEntry\AddEntryCommand.cs" />
<Compile Include="Group\Commands\AddGroup\AddGroupCommand.cs" /> <Compile Include="Group\Commands\AddGroup\AddGroupCommand.cs" />

View File

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

View File

@@ -22,30 +22,37 @@ namespace ModernKeePass.Application.Common.Interfaces
string FileAccessToken { get; set; } string FileAccessToken { get; set; }
int Size { get; set; } int Size { get; set; }
bool IsDirty { get; set; } bool IsDirty { get; set; }
int MaxHistoryCount { get; set; }
long MaxHistorySize { get; set; }
Task Open(byte[] file, Credentials credentials); Task Open(byte[] file, Credentials credentials);
Task ReOpen(byte[] file); 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();
Task<byte[]> SaveDatabase(byte[] newFileContents);
void UpdateCredentials(Credentials credentials); void UpdateCredentials(Credentials credentials);
void CloseDatabase(); void CloseDatabase();
EntryEntity GetEntry(string id); EntryEntity GetEntry(string id);
GroupEntity GetGroup(string id);
Task AddEntry(string parentGroupId, string entryId); Task AddEntry(string parentGroupId, string entryId);
Task MoveEntry(string parentGroupId, string entryId, int index); Task MoveEntry(string parentGroupId, string entryId, int index);
Task AddGroup(string parentGroupId, string groupId); Task UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected);
void UpdateEntry(string entryId, string fieldName, object fieldValue); void DeleteField(string entryId, string fieldName);
void UpdateGroup(GroupEntity group);
Task RemoveEntry(string parentGroupId, string entryId); 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); Task RemoveGroup(string parentGroupId, string groupId);
void DeleteEntity(string entityId); void DeleteEntity(string entityId);
EntryEntity CreateEntry(string parentGroupId);
GroupEntity CreateGroup(string parentGroupId, string name, bool isRecycleBin = false); GroupEntity CreateGroup(string parentGroupId, string name, bool isRecycleBin = false);
void SortEntries(string groupId);
void SortSubGroups(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 AddHistory(string entryId);
EntryEntity RestoreFromHistory(string entryId, int historyIndex); EntryEntity RestoreFromHistory(string entryId, int historyIndex);
void DeleteHistory(string entryId, int historyIndex); void DeleteHistory(string entryId, int historyIndex);

View File

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

View File

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

View File

@@ -1,12 +1,16 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Application.Common.Interfaces namespace ModernKeePass.Application.Common.Interfaces
{ {
public interface IFileProxy public interface IFileProxy
{ {
Task<byte[]> OpenBinaryFile(string path); Task<FileInfo> OpenFile(string name, string extension, bool addToRecent);
Task<IList<string>> OpenTextFile(string path); Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent);
Task<byte[]> ReadBinaryFile(string path);
Task<IList<string>> ReadTextFile(string path);
Task WriteToLogFile(IEnumerable<string> data);
Task WriteBinaryContentsToFile(string path, byte[] contents); Task WriteBinaryContentsToFile(string path, byte[] contents);
void ReleaseFile(string path); void ReleaseFile(string path);
} }

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface ILogger
{
Task LogError(Exception exception);
void LogTrace(string message, Dictionary<string, string> values);
}
}

View File

@@ -17,11 +17,9 @@ namespace ModernKeePass.Application.Database.Commands.CloseDatabase
public void Handle(CloseDatabaseCommand message) public void Handle(CloseDatabaseCommand message)
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); 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(); _database.CloseDatabase();
// Cleanup
_database.FileAccessToken = null;
_database.Size = 0;
} }
} }
} }

View File

@@ -43,10 +43,10 @@ namespace ModernKeePass.Application.Database.Commands.CreateDatabase
} }
await _database.Create(new Credentials await _database.Create(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 Password = message.Password
}, message.Name, version); }, message.Name, version);
_database.FileAccessToken = message.FilePath; _database.FileAccessToken = message.FilePath;
if (message.CreateSampleData) if (message.CreateSampleData)
@@ -64,17 +64,17 @@ namespace ModernKeePass.Application.Database.Commands.CreateDatabase
_database.UpdateGroup(internetGroup); _database.UpdateGroup(internetGroup);
var sample1 = _database.CreateEntry(_database.RootGroupId); var sample1 = _database.CreateEntry(_database.RootGroupId);
_database.UpdateEntry(sample1.Id, EntryFieldName.Title, "Sample Entry" ); await _database.UpdateEntry(sample1.Id, EntryFieldName.Title, "Sample Entry", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username" ); await _database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password" ); await _database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password", true);
_database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/" ); await _database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.Notes, "You may safely delete this sample" ); await _database.UpdateEntry(sample1.Id, EntryFieldName.Notes, "You may safely delete this sample", false);
var sample2 = _database.CreateEntry(_database.RootGroupId); var sample2 = _database.CreateEntry(_database.RootGroupId);
_database.UpdateEntry(sample2.Id, EntryFieldName.Title, "Sample Entry #2" ); await _database.UpdateEntry(sample2.Id, EntryFieldName.Title, "Sample Entry #2", false);
_database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321" ); await _database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321", false);
_database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345" ); await _database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345", true);
_database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html" ); await _database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html", false);
} }
} }
} }

View File

@@ -1,4 +1,6 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using MediatR; using MediatR;
using System.Threading.Tasks; using System.Threading.Tasks;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
@@ -14,11 +16,13 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
{ {
private readonly IDatabaseProxy _database; private readonly IDatabaseProxy _database;
private readonly IFileProxy _file; private readonly IFileProxy _file;
private readonly ILogger _logger;
public SaveDatabaseCommandHandler(IDatabaseProxy database, IFileProxy file) public SaveDatabaseCommandHandler(IDatabaseProxy database, IFileProxy file, ILogger logger)
{ {
_database = database; _database = database;
_file = file; _file = file;
_logger = logger;
} }
public async Task Handle(SaveDatabaseCommand message) public async Task Handle(SaveDatabaseCommand message)
@@ -27,26 +31,27 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
try try
{ {
byte[] contents; var timeToSave = Stopwatch.StartNew();
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; _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);
timeToSave.Stop();
_logger.LogTrace("SaveCommand", new Dictionary<string, string>
{
{ "duration", timeToSave.ElapsedMilliseconds.ToString()},
{ "size", _database.Size.ToString()}
});
} }
catch (Exception exception) catch (Exception exception)
{ {

View File

@@ -27,7 +27,7 @@ namespace ModernKeePass.Application.Database.Commands.UpdateCredentials
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
_database.UpdateCredentials(new Credentials _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 Password = message.Password
}); });
} }

View File

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

View File

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

View File

@@ -27,11 +27,11 @@ namespace ModernKeePass.Application.Database.Queries.OpenDatabase
{ {
if (_database.IsDirty) throw new DatabaseOpenException(); 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); var hasKeyFile = !string.IsNullOrEmpty(message.KeyFilePath);
await _database.Open(file, new Credentials 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 Password = message.Password
}); });
if (hasKeyFile) _file.ReleaseFile(message.KeyFilePath); if (hasKeyFile) _file.ReleaseFile(message.KeyFilePath);

View File

@@ -20,9 +20,10 @@ namespace ModernKeePass.Application.Database.Queries.ReOpenDatabase
public async Task Handle(ReOpenDatabaseQuery message) 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); await _database.ReOpen(file);
} }
} }

View File

@@ -1,5 +1,4 @@
using System.Reflection; using System.Reflection;
using FluentValidation;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Behaviors; using ModernKeePass.Application.Common.Behaviors;
@@ -13,7 +12,6 @@ namespace ModernKeePass.Application
var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly; var assembly = typeof(DependencyInjection).GetTypeInfo().Assembly;
services.AddMediatR(assembly); services.AddMediatR(assembly);
services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DirtyStatusBehavior<,>)); services.AddTransient(typeof(IPipelineBehavior<,>), typeof(DirtyStatusBehavior<,>));
//services.AddValidatorsFromAssembly(assembly);
return services; return services;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Entry.Commands.UpsertField
{
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 UpsertFieldCommandHandler : IAsyncRequestHandler<UpsertFieldCommand>
{
private readonly IDatabaseProxy _database;
public UpsertFieldCommandHandler(IDatabaseProxy database)
{
_database = database;
}
public async Task Handle(UpsertFieldCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue, message.IsProtected);
}
}
}
}

View File

@@ -1,10 +1,10 @@
using FluentValidation; 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) RuleFor(v => v.EntryId)
.NotNull() .NotNull()

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.AddEntry
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.AddEntry(message.ParentGroupId, message.EntryId); await _database.AddEntry(message.ParentGroupId, message.EntryId);
//message.ParentGroup.Entries.Add(message.Entry);
} }
} }
} }

View File

@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.AddGroup
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.AddGroup(message.ParentGroupId, message.GroupId); await _database.AddGroup(message.ParentGroupId, message.GroupId);
//message.ParentGroup.SubGroups.Add(message.Group);
} }
} }
} }

View File

@@ -29,7 +29,7 @@ namespace ModernKeePass.Application.Group.Commands.CreateGroup
var group = _database.CreateGroup(message.ParentGroup.Id, message.Name, message.IsRecycleBin); var group = _database.CreateGroup(message.ParentGroup.Id, message.Name, message.IsRecycleBin);
var groupVm = _mapper.Map<GroupVm>(group); var groupVm = _mapper.Map<GroupVm>(group);
message.ParentGroup.SubGroups.Add(groupVm); message.ParentGroup.Groups.Add(groupVm);
return groupVm; return groupVm;
} }
} }

View File

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

View File

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

View File

@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.RemoveEntry
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.RemoveEntry(message.ParentGroupId, message.EntryId); await _database.RemoveEntry(message.ParentGroupId, message.EntryId);
//message.ParentGroup.Entries.Remove(message.Entry);
} }
} }
} }

View File

@@ -24,7 +24,6 @@ namespace ModernKeePass.Application.Group.Commands.RemoveGroup
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.RemoveGroup(message.ParentGroupId, message.GroupId); await _database.RemoveGroup(message.ParentGroupId, message.GroupId);
//message.ParentGroup.SubGroups.Remove(message.Group);
} }
} }
} }

View File

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

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using MediatR; using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;
@@ -25,7 +24,7 @@ namespace ModernKeePass.Application.Group.Commands.SortGroups
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
_database.SortSubGroups(message.Group.Id); _database.SortSubGroups(message.Group.Id);
message.Group.SubGroups = message.Group.SubGroups.OrderBy(g => g.Title).ToList(); message.Group.Groups = message.Group.Groups.OrderBy(g => g.Title).ToList();
} }
} }
} }

View File

@@ -15,7 +15,7 @@ namespace ModernKeePass.Application.Group.Models
public string Id { get; set; } public string Id { get; set; }
public string Title { get; set; } public string Title { get; set; }
public Icon Icon { get; set; } public Icon Icon { get; set; }
public List<GroupVm> SubGroups { get; set; } public List<GroupVm> Groups { get; set; }
public List<EntryVm> Entries { get; set; } public List<EntryVm> Entries { get; set; }
public override string ToString() public override string ToString()
@@ -26,13 +26,7 @@ namespace ModernKeePass.Application.Group.Models
public void Mapping(Profile profile) public void Mapping(Profile profile)
{ {
profile.CreateMap<GroupEntity, GroupVm>() profile.CreateMap<GroupEntity, GroupVm>()
.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.Title, opts => opts.MapFrom(s => s.Name))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => s.Icon))
.ForMember(d => d.Entries, opts => opts.MapFrom(s => s.Entries))
.ForMember(d => d.SubGroups, opts => opts.MapFrom(s => s.SubGroups))
.MaxDepth(2); .MaxDepth(2);
} }
} }

View File

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

View File

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

View File

@@ -10,16 +10,16 @@ namespace ModernKeePass.Application.Parameters.Queries.GetCiphers
{ {
public class GetCiphersQueryHandler: IRequestHandler<GetCiphersQuery, IEnumerable<CipherVm>> 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) public IEnumerable<CipherVm> Handle(GetCiphersQuery message)
{ {
return _cryptography.Ciphers.Select(c => new CipherVm return _databaseSettings.Ciphers.Select(c => new CipherVm
{ {
Id = c.Id, Id = c.Id,
Name = c.Name Name = c.Name

View File

@@ -9,16 +9,16 @@ namespace ModernKeePass.Application.Parameters.Queries.GetCompressions
{ {
public class GetCompressionsQueryHandler : IRequestHandler<GetCompressionsQuery, IEnumerable<string>> 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) public IEnumerable<string> Handle(GetCompressionsQuery message)
{ {
return _cryptography.CompressionAlgorithms.OrderBy(c => c); return _databaseSettings.CompressionAlgorithms.OrderBy(c => c);
} }
} }
} }

View File

@@ -10,16 +10,16 @@ namespace ModernKeePass.Application.Parameters.Queries.GetKeyDerivations
{ {
public class GetKeyDerivationsQueryHandler : IRequestHandler<GetKeyDerivationsQuery, IEnumerable<KeyDerivationVm>> 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) public IEnumerable<KeyDerivationVm> Handle(GetKeyDerivationsQuery message)
{ {
return _cryptography.KeyDerivations.Select(c => new KeyDerivationVm return _databaseSettings.KeyDerivations.Select(c => new KeyDerivationVm
{ {
Id = c.Id, Id = c.Id,
Name = c.Name Name = c.Name

View File

@@ -1,5 +1,4 @@
using System; using System.Threading.Tasks;
using System.Threading.Tasks;
using MediatR; using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
@@ -14,11 +13,13 @@ namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile
{ {
private readonly ICredentialsProxy _security; private readonly ICredentialsProxy _security;
private readonly IFileProxy _file; private readonly IFileProxy _file;
private readonly ICryptographyClient _cryptography;
public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file) public GenerateKeyFileCommandHandler(ICredentialsProxy security, IFileProxy file, ICryptographyClient cryptography)
{ {
_security = security; _security = security;
_file = file; _file = file;
_cryptography = cryptography;
} }
public async Task Handle(GenerateKeyFileCommand message) public async Task Handle(GenerateKeyFileCommand message)
@@ -26,9 +27,7 @@ namespace ModernKeePass.Application.Security.Commands.GenerateKeyFile
byte[] entropy = null; byte[] entropy = null;
if (message.AddAdditionalEntropy) if (message.AddAdditionalEntropy)
{ {
entropy = new byte[10]; entropy = _cryptography.Random(10);
var random = new Random();
random.NextBytes(entropy);
} }
var keyFile = _security.GenerateKeyFile(entropy); var keyFile = _security.GenerateKeyFile(entropy);
await _file.WriteBinaryContentsToFile(message.KeyFilePath, keyFile); await _file.WriteBinaryContentsToFile(message.KeyFilePath, keyFile);

View File

@@ -6,7 +6,7 @@
"MediatR": "3.0.1", "MediatR": "3.0.1",
"MediatR.Extensions.Microsoft.DependencyInjection": "2.0.0", "MediatR.Extensions.Microsoft.DependencyInjection": "2.0.0",
"Microsoft.Extensions.DependencyInjection": "1.1.1", "Microsoft.Extensions.DependencyInjection": "1.1.1",
"Microsoft.NETCore.Portable.Compatibility": "1.0.1", "Microsoft.NETCore.Portable.Compatibility": "1.0.2",
"NETStandard.Library": "2.0.3", "NETStandard.Library": "2.0.3",
"Splat": "3.0.0" "Splat": "3.0.0"
}, },

View File

@@ -3,5 +3,12 @@
public static class Constants public static class Constants
{ {
public static string EmptyId => "00000000000000000000000000000000"; public static string EmptyId => "00000000000000000000000000000000";
public static class Extensions
{
public static string Any => "*";
public static string Kdbx => ".kdbx";
public static string Key => ".key";
}
} }
} }

View File

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

View File

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

View File

@@ -6,8 +6,8 @@ namespace ModernKeePass.Domain.Entities
{ {
public string Id { get; set; } public string Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string ParentId { get; set; } public string ParentGroupId { get; set; }
public string ParentName { get; set; } public string ParentGroupName { get; set; }
public DateTimeOffset LastModificationDate { get; set; } public DateTimeOffset ModificationDate { get; set; }
} }
} }

View File

@@ -7,13 +7,10 @@ namespace ModernKeePass.Domain.Entities
{ {
public class EntryEntity: BaseEntity public class EntryEntity: BaseEntity
{ {
public string UserName { get; set; } public IEnumerable<FieldEntity> Fields { get; set; } = new List<FieldEntity>();
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<EntryEntity> History { get; set; } = new List<EntryEntity>(); 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 Icon Icon { get; set; }
public Color ForegroundColor { get; set; } public Color ForegroundColor { get; set; }
public Color BackgroundColor { get; set; } public Color BackgroundColor { get; set; }

View File

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

View File

@@ -5,7 +5,7 @@ namespace ModernKeePass.Domain.Entities
{ {
public class GroupEntity : BaseEntity public class GroupEntity : BaseEntity
{ {
public List<GroupEntity> SubGroups { get; set; } = new List<GroupEntity>(); public List<GroupEntity> Groups { get; set; } = new List<GroupEntity>();
public List<EntryEntity> Entries { get; set; } = new List<EntryEntity>(); public List<EntryEntity> Entries { get; set; } = new List<EntryEntity>();
public Icon Icon { get; set; } public Icon Icon { get; set; }
} }

View File

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

View File

@@ -1,7 +1,7 @@
{ {
"supports": {}, "supports": {},
"dependencies": { "dependencies": {
"Microsoft.NETCore.Portable.Compatibility": "1.0.1", "Microsoft.NETCore.Portable.Compatibility": "1.0.2",
"NETStandard.Library": "2.0.3", "NETStandard.Library": "2.0.3",
"Splat": "3.0.0" "Splat": "3.0.0"
}, },

View File

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

View File

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

View File

@@ -1,33 +0,0 @@
using ModernKeePass.Domain.Enums;
using ModernKeePassLib;
namespace ModernKeePass.Infrastructure.KeePass
{
public static class EntryFieldMapper
{
public static string MapPwDefsToField(string value)
{
switch (value)
{
case PwDefs.TitleField: return EntryFieldName.Title;
case PwDefs.UserNameField: return EntryFieldName.UserName;
case PwDefs.PasswordField: return EntryFieldName.Password;
case PwDefs.NotesField: return EntryFieldName.Notes;
case PwDefs.UrlField: return EntryFieldName.Url;
default: return value;
}
}
public static string MapFieldToPwDef(string value)
{
switch (value)
{
case EntryFieldName.Title: return PwDefs.TitleField;
case EntryFieldName.UserName: return PwDefs.UserNameField;
case EntryFieldName.Password: return PwDefs.PasswordField;
case EntryFieldName.Notes: return PwDefs.NotesField;
case EntryFieldName.Url: return PwDefs.UrlField;
default: return value;
}
}
}
}

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ namespace ModernKeePass.Infrastructure.KeePass
{ {
private readonly IMapper _mapper; private readonly IMapper _mapper;
private readonly IDateTime _dateTime; private readonly IDateTime _dateTime;
private readonly ICryptographyClient _cryptography;
private readonly PwDatabase _pwDatabase = new PwDatabase(); private readonly PwDatabase _pwDatabase = new PwDatabase();
private Credentials _credentials; private Credentials _credentials;
// Flag: Has Dispose already been called? // Flag: Has Dispose already been called?
@@ -40,6 +41,18 @@ namespace ModernKeePass.Infrastructure.KeePass
public int Size { get; set; } public int Size { get; set; }
public bool IsDirty { 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 // Settings
public bool IsRecycleBinEnabled public bool IsRecycleBinEnabled
{ {
@@ -82,10 +95,11 @@ namespace ModernKeePass.Infrastructure.KeePass
set { _pwDatabase.Compression = (PwCompressionAlgorithm) Enum.Parse(typeof(PwCompressionAlgorithm), value); } set { _pwDatabase.Compression = (PwCompressionAlgorithm) Enum.Parse(typeof(PwCompressionAlgorithm), value); }
} }
public KeePassDatabaseClient(IMapper mapper, IDateTime dateTime) public KeePassDatabaseClient(IMapper mapper, IDateTime dateTime, ICryptographyClient cryptography)
{ {
_mapper = mapper; _mapper = mapper;
_dateTime = dateTime; _dateTime = dateTime;
_cryptography = cryptography;
} }
public async Task Open(byte[] file, Credentials credentials) public async Task Open(byte[] file, Credentials credentials)
@@ -152,15 +166,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() public void CloseDatabase()
{ {
_pwDatabase?.Close(); _pwDatabase?.Close();
@@ -198,6 +203,20 @@ namespace ModernKeePass.Infrastructure.KeePass
parentPwGroup.AddGroup(pwGroup, true); 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) public async Task RemoveEntry(string parentGroupId, string entryId)
{ {
await Task.Run(() => await Task.Run(() =>
@@ -223,19 +242,12 @@ namespace ModernKeePass.Infrastructure.KeePass
_pwDatabase.DeletedObjects.Add(new PwDeletedObject(BuildIdFromString(entityId), _dateTime.Now)); _pwDatabase.DeletedObjects.Add(new PwDeletedObject(BuildIdFromString(entityId), _dateTime.Now));
} }
public void UpdateEntry(string entryId, string fieldName, object fieldValue) public async Task UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected)
{ {
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true); var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
switch (fieldName) switch (fieldName)
{ {
case EntryFieldName.Title:
case EntryFieldName.UserName:
case EntryFieldName.Password:
case EntryFieldName.Notes:
case EntryFieldName.Url:
pwEntry.Strings.Set(EntryFieldMapper.MapFieldToPwDef(fieldName), new ProtectedString(true, fieldValue.ToString()));
break;
case EntryFieldName.HasExpirationDate: case EntryFieldName.HasExpirationDate:
pwEntry.Expires = (bool)fieldValue; pwEntry.Expires = (bool)fieldValue;
break; break;
@@ -251,9 +263,20 @@ namespace ModernKeePass.Infrastructure.KeePass
case EntryFieldName.ForegroundColor: case EntryFieldName.ForegroundColor:
pwEntry.ForegroundColor = (Color)fieldValue; pwEntry.ForegroundColor = (Color)fieldValue;
break; break;
default:
var stringValue = fieldValue == null ? string.Empty: fieldValue.ToString();
var unprotectedFieldValue = isProtected ? await _cryptography.UnProtect(stringValue) : stringValue;
pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, unprotectedFieldValue));
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) public EntryEntity AddHistory(string entryId)
{ {
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true); var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(entryId), true);
@@ -316,6 +339,18 @@ namespace ModernKeePass.Infrastructure.KeePass
pwGroup.SortSubGroups(false); 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) public EntryEntity GetEntry(string id)
{ {
var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(id), true); var pwEntry = _pwDatabase.RootGroup.FindEntry(BuildIdFromString(id), true);
@@ -356,7 +391,7 @@ namespace ModernKeePass.Infrastructure.KeePass
{ {
Id = g.Uuid.ToHexString(), Id = g.Uuid.ToHexString(),
Name = g.Name, Name = g.Name,
ParentName = g.ParentGroup?.Name ParentGroupName = g.ParentGroup?.Name
}); });
return groups; return groups;
} }

View File

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

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
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.ResolveUsing<ProtectedStringResolver>())
.ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected));
CreateMap<PwEntry, EntryEntity>()
.ForMember(dest => dest.ParentGroupId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString()))
.ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString()))
.ForMember(dest => dest.Fields, opt => opt.MapFrom(src => src.Strings))
.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.ModificationDate, 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.ParentGroupId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString()))
.ForMember(d => d.Id, opts => opts.MapFrom(s => s.Uuid.ToHexString()))
.ForMember(d => d.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId)))
.ForMember(d => d.ModificationDate, opts => opts.MapFrom(s => s.LastModificationTime))
.MaxDepth(2);
}
}
public class ProtectedStringResolver : IValueResolver<KeyValuePair<string, ProtectedString>, FieldEntity, string>
{
private readonly ICryptographyClient _cryptography;
public ProtectedStringResolver(ICryptographyClient cryptography)
{
_cryptography = cryptography;
}
public string Resolve(KeyValuePair<string, ProtectedString> source, FieldEntity destination, string destMember, ResolutionContext context)
{
// TODO: this variable will contain (temporarily) the decrypted string
var decryptedString = source.Value.ReadString();
return source.Value.IsProtected ? _cryptography.Protect(decryptedString).GetAwaiter().GetResult() : decryptedString;
}
}
}

View File

@@ -5,26 +5,93 @@ using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.AccessCache; using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Infrastructure.UWP namespace ModernKeePass.Infrastructure.UWP
{ {
public class StorageFileClient: IFileProxy 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 file = await GetFile(path);
var result = await FileIO.ReadBufferAsync(file).AsTask(); var result = await FileIO.ReadBufferAsync(file).AsTask();
return result.ToArray(); 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 file = await GetFile(path);
var result = await FileIO.ReadLinesAsync(file).AsTask(); var result = await FileIO.ReadLinesAsync(file).AsTask();
return result; return result;
} }
public async Task WriteToLogFile(IEnumerable<string> data)
{
var local = ApplicationData.Current.LocalFolder;
var logFile = await local.CreateFileAsync("LogFile.txt", CreationCollisionOption.OpenIfExists).AsTask();
if (logFile != null)
{
try
{
await FileIO.AppendLinesAsync(logFile, data);
}
catch (Exception)
{
// If another option is available to the app to log error(i.e. Azure Mobile Service, etc...) then try that here
}
}
}
public void ReleaseFile(string path) public void ReleaseFile(string path)
{ {
StorageApplicationPermissions.FutureAccessList.Remove(path); StorageApplicationPermissions.FutureAccessList.Remove(path);

View File

@@ -0,0 +1,57 @@
using System;
using System.Runtime.InteropServices.WindowsRuntime;
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)
{
if (string.IsNullOrEmpty(value)) return value;
// Create a DataProtectionProvider object for the specified descriptor.
var provider = new DataProtectionProvider("LOCAL=user");
// Encode the plaintext input message to a buffer.
var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8);
// Encrypt the message.
var buffProtected = await provider.ProtectAsync(buffMsg).AsTask().ConfigureAwait(false);
// Encode buffer to Base64
var protectedValue = CryptographicBuffer.EncodeToBase64String(buffProtected);
// Return the encrypted string.
return protectedValue;
}
public async Task<string> UnProtect(string value)
{
if (string.IsNullOrEmpty(value)) return 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).AsTask().ConfigureAwait(false);
// Convert the unprotected message from an IBuffer object to a string.
var clearText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffUnprotected);
// Return the plaintext string.
return clearText;
}
public byte[] Random(uint length)
{
return CryptographicBuffer.GenerateRandom(length).ToArray();
}
}
}

View File

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

View File

@@ -1,17 +1,16 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Activation;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.AccessCache; using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.HockeyApp; using Microsoft.HockeyApp;
using ModernKeePass.Application; using ModernKeePass.Application;
@@ -33,16 +32,17 @@ namespace ModernKeePass
/// Provides application-specific behavior to supplement the default Application class. /// Provides application-specific behavior to supplement the default Application class.
/// </summary> /// </summary>
sealed partial class App sealed partial class App
{ { public static IServiceProvider Services { get; private set; }
private readonly IResourceProxy _resource; private readonly IResourceProxy _resource;
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly ISettingsProxy _settings; private readonly ISettingsProxy _settings;
private readonly INavigationService _navigation; private readonly INavigationService _navigation;
private readonly IHockeyClient _hockey;
private readonly IDialogService _dialog;
private readonly INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file;
public static IServiceProvider Services { get; private set; } private readonly IMessenger _messenger;
private readonly ILogger _log;
private readonly IHockeyClient _hockey;
/// <summary> /// <summary>
/// Initializes the singleton application object. This is the first line of authored code /// Initializes the singleton application object. This is the first line of authored code
@@ -63,60 +63,39 @@ namespace ModernKeePass
_resource = Services.GetService<IResourceProxy>(); _resource = Services.GetService<IResourceProxy>();
_settings = Services.GetService<ISettingsProxy>(); _settings = Services.GetService<ISettingsProxy>();
_navigation = Services.GetService<INavigationService>(); _navigation = Services.GetService<INavigationService>();
_dialog = Services.GetService<IDialogService>();
_notification = Services.GetService<INotificationService>(); _notification = Services.GetService<INotificationService>();
_log = Services.GetService<ILogger>();
_hockey = Services.GetService<IHockeyClient>(); _hockey = Services.GetService<IHockeyClient>();
_file = Services.GetService<IFileProxy>();
_messenger = Services.GetService<IMessenger>();
InitializeComponent(); InitializeComponent();
Suspending += OnSuspending; Suspending += OnSuspending;
Resuming += OnResuming; Resuming += OnResuming;
UnhandledException += OnUnhandledException; 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 #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 _log.LogError(e.Exception);
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});
}
}
});
}
} }
/// <summary> /// <summary>
@@ -153,9 +132,6 @@ namespace ModernKeePass
{ {
// Load state from previously terminated application // Load state from previously terminated application
await SuspensionManager.RestoreAsync(); 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 // Place the frame in the current Window
@@ -179,12 +155,18 @@ namespace ModernKeePass
_notification.Show("App resumed", "Database reopened (changes were saved)"); _notification.Show("App resumed", "Database reopened (changes were saved)");
#endif #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 #if DEBUG
_notification.Show("App resumed", "Nothing to do, no previous database opened"); _notification.Show("App resumed", "Nothing to do, no previous database opened");
#endif #endif
_navigation.NavigateTo(Constants.Navigation.MainPage);
} }
} }
@@ -222,9 +204,13 @@ namespace ModernKeePass
await _mediator.Send(new CloseDatabaseCommand()).ConfigureAwait(false); 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)
{
await _log.LogError(ex);
} }
finally finally
{ {

View File

@@ -1,10 +1,13 @@
using System.Reflection; using System.Reflection;
using AutoMapper; using AutoMapper;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.HockeyApp; using Microsoft.HockeyApp;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Views; using ModernKeePass.Views;
using ModernKeePass.Log;
namespace ModernKeePass namespace ModernKeePass
{ {
@@ -24,6 +27,7 @@ namespace ModernKeePass
nav.Configure(Constants.Navigation.GroupPage, typeof(GroupDetailPage)); nav.Configure(Constants.Navigation.GroupPage, typeof(GroupDetailPage));
return nav; return nav;
}); });
services.AddSingleton(provider => Messenger.Default);
services.AddTransient(typeof(IDialogService), typeof(DialogService)); services.AddTransient(typeof(IDialogService), typeof(DialogService));
services.AddSingleton(provider => services.AddSingleton(provider =>
@@ -35,6 +39,7 @@ namespace ModernKeePass
#endif #endif
return HockeyClient.Current; return HockeyClient.Current;
}); });
services.AddSingleton(typeof(ILogger), typeof(HockeyAppLog));
return services; return services;
} }

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ModernKeePass.Application.Common.Interfaces;
using Microsoft.HockeyApp;
using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.Log
{
public class HockeyAppLog : ILogger
{
private readonly IHockeyClient _hockey;
private readonly IFileProxy _file;
private readonly IDateTime _dateTime;
public HockeyAppLog(IHockeyClient hockey, IFileProxy file, IDateTime dateTime)
{
_hockey = hockey;
_file = file;
_dateTime = dateTime;
}
public async Task LogError(Exception exception)
{
_hockey.TrackException(exception);
_hockey.Flush();
var time = _dateTime.Now.ToLocalTime();
var data = new List<string>
{
$"{time} - {exception.Message}",
$"{time} - {exception.StackTrace}"
};
await _file.WriteToLogFile(data);
}
public void LogTrace(string message, Dictionary<string, string> values)
{
_hockey.TrackTrace(message, values);
_hockey.Flush();
}
}
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?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"> <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.19.0.12" />
<Properties> <Properties>
<DisplayName>ModernKeePass</DisplayName> <DisplayName>ModernKeePass</DisplayName>
<PublisherDisplayName>wismna</PublisherDisplayName> <PublisherDisplayName>wismna</PublisherDisplayName>

View File

@@ -13,14 +13,14 @@
<VisualState x:Name="PointerOver"> <VisualState x:Name="PointerOver">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid" <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
Storyboard.TargetProperty="Opacity"> Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="0.8" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonPointerOverBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
</VisualStateManager.VisualStateGroups> </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="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 x:Name="Layer1" Height="17" Canvas.Left="0" Width="28" Margin="0" RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform> <Canvas.RenderTransform>
@@ -52,7 +52,7 @@
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
</Style> </Style>
<Style x:Key="HeaderTextBoxStyle" TargetType="TextBox"> <Style x:Key="HeaderTextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="40"/> <Setter Property="FontSize" Value="30"/>
<Setter Property="FontWeight" Value="Light"/> <Setter Property="FontWeight" Value="Light"/>
<Setter Property="BorderBrush" Value="Transparent" /> <Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />

View File

@@ -191,7 +191,7 @@
To="1" /> To="1" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter" <ObjectAnimationUsingKeyFrames Storyboard.TargetName="contentPresenter"
Storyboard.TargetProperty="Foreground"> Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource MainColor}" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ListViewItemSelectedForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
@@ -358,13 +358,13 @@
</Grid> </Grid>
</Border> </Border>
<Border x:Name="SelectedLeftIndicator" <Border x:Name="SelectedLeftIndicator"
BorderBrush="{StaticResource MainColor}" BorderBrush="{ThemeResource MainColorBrush}"
BorderThickness="5,0,0,0" BorderThickness="5,0,0,0"
Opacity="0"/> Opacity="0"/>
<Rectangle x:Name="SelectedBorder" <Rectangle x:Name="SelectedBorder"
IsHitTestVisible="False" IsHitTestVisible="False"
Opacity="0" Opacity="0"
Stroke="{StaticResource MainColor}" Stroke="{ThemeResource MainColorBrush}"
StrokeThickness="{ThemeResource ListViewItemSelectedBorderThemeThickness}" StrokeThickness="{ThemeResource ListViewItemSelectedBorderThemeThickness}"
Margin="0" /> Margin="0" />
<Border x:Name="SelectedCheckMarkOuter" <Border x:Name="SelectedCheckMarkOuter"
@@ -373,7 +373,7 @@
VerticalAlignment="Top" VerticalAlignment="Top"
Margin="4"> Margin="4">
<Grid x:Name="SelectedCheckMark" Opacity="0" Height="40" Width="40"> <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"/> <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> </Grid>
</Border> </Border>

View File

@@ -4,7 +4,7 @@
<!-- Default style for Windows.UI.Xaml.Controls.Button --> <!-- Default style for Windows.UI.Xaml.Controls.Button -->
<Style TargetType="Button" x:Key="NoBorderButtonStyle"> <Style TargetType="Button" x:Key="NoBorderButtonStyle">
<Setter Property="Background" Value="{ThemeResource ToggleButtonBackgroundThemeBrush}" /> <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="BorderBrush" Value="{ThemeResource ButtonBorderThemeBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" /> <Setter Property="BorderThickness" Value="{ThemeResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="12,4,12,4" /> <Setter Property="Padding" Value="12,4,12,4" />

View File

@@ -3,39 +3,36 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Common theme values --> <!-- Common theme values -->
<x:Double x:Key="MenuSize">60</x:Double> <x:Double x:Key="MenuWidth">60</x:Double>
<x:Double x:Key="ExpandedMenuSize">300</x:Double> <x:Double x:Key="MenuHeight">40</x:Double>
<GridLength x:Key="MenuGridLength">60</GridLength> <x:Double x:Key="ExpandedMenuSize">250</x:Double>
<GridLength x:Key="ExpandedMenuGridLength">300</GridLength> <GridLength x:Key="MenuHeightGridLength">40</GridLength>
<!-- Only available for Windows 10 --> <GridLength x:Key="MenuWidthGridLength">60</GridLength>
<!--<SolidColorBrush x:Key="MainColor" Color="{ThemeResource SystemAccentColor}" /> <GridLength x:Key="ExpandedMenuGridLength">250</GridLength>
<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}"/>-->
<Color x:Key="MainColor">SlateBlue</Color> <Color x:Key="MainColor">SlateBlue</Color>
<Color x:Key="MainColorLight">MediumPurple</Color> <Color x:Key="MainColorLight">MediumPurple</Color>
<Color x:Key="MainColorDark">Indigo</Color> <Color x:Key="MainColorDark">Indigo</Color>
<Color x:Key="TextColorLight">WhiteSmoke</Color> <Color x:Key="TextColorLight">WhiteSmoke</Color>
<Color x:Key="TextColorDark">DarkSlateGray</Color>
<Color x:Key="BorderColor">DarkGray</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="MainColorBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="MainColorLightBrush" Color="{ThemeResource MainColorLight}" /> <SolidColorBrush x:Key="MainColorLightBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="MainColorDarkBrush" Color="{ThemeResource MainColorDark}" /> <SolidColorBrush x:Key="MainColorDarkBrush" Color="{ThemeResource MainColorDark}" />
<SolidColorBrush x:Key="TextColorLightBrush" Color="{ThemeResource TextColorLight}" /> <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" > <Style TargetType="TextBlock" x:Key="TextBlockSettingsHeaderStyle" >
<Setter Property="FontFamily" Value="Segoe UI" /> <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" /> <Setter Property="FontWeight" Value="SemiLight" />
</Style> </Style>
<SolidColorBrush x:Key="TextBoxForegroundThemeBrush" Color="{ThemeResource TextColorDark}" />
<SolidColorBrush x:Key="TextBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" /> <SolidColorBrush x:Key="TextBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
<SolidColorBrush x:Key="TextSelectionHighlightColorThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="TextSelectionHighlightColorThemeBrush" Color="{ThemeResource MainColor}" />
@@ -50,9 +47,8 @@
<SolidColorBrush x:Key="HyperlinkForegroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="HyperlinkForegroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="HyperlinkPointerOverForegroundThemeBrush" Color="{ThemeResource MainColorLight}" /> <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="SearchBoxPointerOverTextThemeBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="SearchBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
<SolidColorBrush x:Key="SearchBoxFocusedBorderThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SearchBoxFocusedBorderThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="SearchBoxButtonBackgroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SearchBoxButtonBackgroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="SearchBoxHitHighlightForegroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SearchBoxHitHighlightForegroundThemeBrush" Color="{ThemeResource MainColor}" />
@@ -83,4 +79,14 @@
<SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="{ThemeResource MainColorDark}" /> <SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="{ThemeResource MainColorDark}" />
<SolidColorBrush x:Key="SliderTrackDecreasePointerOverBackgroundThemeBrush" Color="{ThemeResource MainColorLight}" /> <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> </ResourceDictionary>

View File

@@ -8,14 +8,11 @@
<Setter Property="BorderBrush" Value="{ThemeResource ComboBoxBorderThemeBrush}" /> <Setter Property="BorderBrush" Value="{ThemeResource ComboBoxBorderThemeBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}" /> <Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" /> <Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource SearchBoxContentThemeFontSize}" /> <Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="FontWeight" Value="{ThemeResource SearchBoxContentThemeFontWeight}"/> <Setter Property="FontWeight" Value="{ThemeResource SearchBoxContentThemeFontWeight}"/>
<Setter Property="Foreground" Value="{ThemeResource SearchBoxForegroundThemeBrush}" /> <Setter Property="Foreground" Value="{ThemeResource SearchBoxForegroundThemeBrush}" />
<Setter Property="Padding" Value="{ThemeResource SearchBoxThemePadding}"/> <Setter Property="Padding" Value="{ThemeResource SearchBoxThemePadding}"/>
<Setter Property="IsTabStop" Value="False" /> <Setter Property="IsTabStop" Value="False" />
<Setter Property="Width" Value="350" />
<Setter Property="Height" Value="32" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Typography.StylisticSet20" Value="True"/> <Setter Property="Typography.StylisticSet20" Value="True"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
@@ -291,7 +288,7 @@
TextWrapping="NoWrap" TextWrapping="NoWrap"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0" 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" <Button x:Name="ActionButton"
AutomationProperties.AccessibilityView="Raw" AutomationProperties.AccessibilityView="Raw"
Background="Transparent" Background="Transparent"
@@ -299,9 +296,9 @@
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"
Grid.Column="1" Grid.Column="1"
Style="{StaticResource ActionButtonStyle}" Style="{StaticResource ActionButtonStyle}"
Content="{TemplateBinding ButtonSymbol}" Content="{TemplateBinding ButtonContent}"
IsEnabled="{TemplateBinding IsButtonEnabled}" Command="{TemplateBinding ButtonCommand}"
Command="{TemplateBinding ButtonCommand}"> CommandParameter="{TemplateBinding ButtonCommandParameter}">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip Content="{TemplateBinding ButtonTooltip}" /> <ToolTip Content="{TemplateBinding ButtonTooltip}" />
</ToolTipService.ToolTip> </ToolTipService.ToolTip>

View File

@@ -147,35 +147,17 @@
<data name="EntityRestoredTitle" xml:space="preserve"> <data name="EntityRestoredTitle" xml:space="preserve">
<value>Restored</value> <value>Restored</value>
</data> </data>
<data name="EntryDeleted" xml:space="preserve">
<value>Entry permanently removed</value>
</data>
<data name="EntryDeletingConfirmation" xml:space="preserve"> <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>
<data name="EntryRecycled" xml:space="preserve"> <data name="EntryRecycled" xml:space="preserve">
<value>Entry moved to the Recycle bin</value> <value>{0} 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>
</data> </data>
<data name="EntryRestored" xml:space="preserve"> <data name="EntryRestored" xml:space="preserve">
<value>Entry returned to its original group</value> <value>Entry returned to its original group</value>
</data> </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"> <data name="GroupRecycled" xml:space="preserve">
<value>Group moved to the Recycle bin</value> <value>{0} 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>
</data> </data>
<data name="MainMenuItemAbout" xml:space="preserve"> <data name="MainMenuItemAbout" xml:space="preserve">
<value>About</value> <value>About</value>
@@ -216,8 +198,8 @@
<data name="CompositeKeyErrorUserAccount" xml:space="preserve"> <data name="CompositeKeyErrorUserAccount" xml:space="preserve">
<value>- user account</value> <value>- user account</value>
</data> </data>
<data name="SettingsMenuItemSave" xml:space="preserve"> <data name="SettingsMenuItemCredentials" xml:space="preserve">
<value>Saving</value> <value>Credentials</value>
</data> </data>
<data name="RecycleBinTitle" xml:space="preserve"> <data name="RecycleBinTitle" xml:space="preserve">
<value>Recycle Bin</value> <value>Recycle Bin</value>
@@ -267,4 +249,28 @@
<data name="FileNotFoundTitle" xml:space="preserve"> <data name="FileNotFoundTitle" xml:space="preserve">
<value>File not found</value> <value>File not found</value>
</data> </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> </root>

View File

@@ -121,7 +121,7 @@
<value>Dominik Reichl for the KeePass application and file format</value> <value>Dominik Reichl for the KeePass application and file format</value>
</data> </data>
<data name="AboutCredits2.Text" xml:space="preserve"> <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>
<data name="AboutCreditsLabel.Text" xml:space="preserve"> <data name="AboutCreditsLabel.Text" xml:space="preserve">
<value>Credits</value> <value>Credits</value>
@@ -204,8 +204,8 @@
<data name="EntryPassword.Text" xml:space="preserve"> <data name="EntryPassword.Text" xml:space="preserve">
<value>Password</value> <value>Password</value>
</data> </data>
<data name="EntryShowPassword.Content" xml:space="preserve"> <data name="EntryShowPassword.OnContent" xml:space="preserve">
<value>Show password</value> <value>Hide password</value>
</data> </data>
<data name="GroupCreateEntry.Text" xml:space="preserve"> <data name="GroupCreateEntry.Text" xml:space="preserve">
<value>Create new entry</value> <value>Create new entry</value>
@@ -297,9 +297,6 @@
<data name="SettingsDatabaseKdf.Text" xml:space="preserve"> <data name="SettingsDatabaseKdf.Text" xml:space="preserve">
<value>Key Derivation Algorithm</value> <value>Key Derivation Algorithm</value>
</data> </data>
<data name="SettingsDatabaseRecycleBin.Header" xml:space="preserve">
<value>Recycle bin</value>
</data>
<data name="SettingsDatabaseRecycleBin.OffContent" xml:space="preserve"> <data name="SettingsDatabaseRecycleBin.OffContent" xml:space="preserve">
<value>Disabled</value> <value>Disabled</value>
</data> </data>
@@ -513,4 +510,64 @@
<data name="SettingsSaveDatabaseSuspendDesc.Text" xml:space="preserve"> <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> <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>
<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>
<data name="EntryShowPassword.OffContent" xml:space="preserve">
<value>Show password</value>
</data>
<data name="PasswordGenerationButton.ButtonContent" xml:space="preserve">
<value>Random</value>
</data>
</root> </root>

View File

@@ -147,35 +147,17 @@
<data name="EntityRestoredTitle" xml:space="preserve"> <data name="EntityRestoredTitle" xml:space="preserve">
<value>Restauré</value> <value>Restauré</value>
</data> </data>
<data name="EntryDeleted" xml:space="preserve">
<value>Entrée supprimée définitivement</value>
</data>
<data name="EntryDeletingConfirmation" xml:space="preserve"> <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>
<data name="EntryRecycled" xml:space="preserve"> <data name="EntryRecycled" xml:space="preserve">
<value>Entrée placée dans la Corbeille</value> <value>{0} placé 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>
</data> </data>
<data name="GroupDeletingConfirmation" xml:space="preserve"> <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>
<data name="GroupRecycled" xml:space="preserve"> <data name="GroupRecycled" xml:space="preserve">
<value>Groupe placé dans la Corbeille</value> <value>{0} 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>
</data> </data>
<data name="MainMenuItemAbout" xml:space="preserve"> <data name="MainMenuItemAbout" xml:space="preserve">
<value>A propos</value> <value>A propos</value>
@@ -204,9 +186,6 @@
<data name="SettingsMenuGroupDatabase" xml:space="preserve"> <data name="SettingsMenuGroupDatabase" xml:space="preserve">
<value>Base de données</value> <value>Base de données</value>
</data> </data>
<data name="SettingsMenuItemGeneral" xml:space="preserve">
<value>Général</value>
</data>
<data name="SettingsMenuItemNew" xml:space="preserve"> <data name="SettingsMenuItemNew" xml:space="preserve">
<value>Nouveau</value> <value>Nouveau</value>
</data> </data>
@@ -216,9 +195,6 @@
<data name="CompositeKeyErrorUserAccount" xml:space="preserve"> <data name="CompositeKeyErrorUserAccount" xml:space="preserve">
<value>- compte utilisateur</value> <value>- compte utilisateur</value>
</data> </data>
<data name="SettingsMenuItemSave" xml:space="preserve">
<value>Sauvegardes</value>
</data>
<data name="RecycleBinTitle" xml:space="preserve"> <data name="RecycleBinTitle" xml:space="preserve">
<value>Corbeille</value> <value>Corbeille</value>
</data> </data>
@@ -267,4 +243,31 @@
<data name="FileNotFoundTitle" xml:space="preserve"> <data name="FileNotFoundTitle" xml:space="preserve">
<value>Fichier manquant</value> <value>Fichier manquant</value>
</data> </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> </root>

View File

@@ -204,9 +204,6 @@
<data name="EntryPassword.Text" xml:space="preserve"> <data name="EntryPassword.Text" xml:space="preserve">
<value>Mot de passe</value> <value>Mot de passe</value>
</data> </data>
<data name="EntryShowPassword.Content" xml:space="preserve">
<value>Afficher le mot de passe</value>
</data>
<data name="GroupCreateEntry.Text" xml:space="preserve"> <data name="GroupCreateEntry.Text" xml:space="preserve">
<value>Créer une nouvelle entrée</value> <value>Créer une nouvelle entrée</value>
</data> </data>
@@ -297,9 +294,6 @@
<data name="SettingsDatabaseKdf.Text" xml:space="preserve"> <data name="SettingsDatabaseKdf.Text" xml:space="preserve">
<value>Algorithme de dérivation de clé</value> <value>Algorithme de dérivation de clé</value>
</data> </data>
<data name="SettingsDatabaseRecycleBin.Header" xml:space="preserve">
<value>Corbeille</value>
</data>
<data name="SettingsDatabaseRecycleBin.OffContent" xml:space="preserve"> <data name="SettingsDatabaseRecycleBin.OffContent" xml:space="preserve">
<value>Désactivé</value> <value>Désactivé</value>
</data> </data>
@@ -492,9 +486,6 @@
<data name="NewImportFormat.Text" xml:space="preserve"> <data name="NewImportFormat.Text" xml:space="preserve">
<value>Format</value> <value>Format</value>
</data> </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"> <data name="CloseButton.Content" xml:space="preserve">
<value>Fermer sans sauvegarder</value> <value>Fermer sans sauvegarder</value>
</data> </data>
@@ -516,4 +507,67 @@
<data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve"> <data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve">
<value>Confirmer le mot de passe</value> <value>Confirmer le mot de passe</value>
</data> </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>
<data name="EntryShowPassword.OffContent" xml:space="preserve">
<value>Afficher le mot de passe</value>
</data>
<data name="EntryShowPassword.OnContent" xml:space="preserve">
<value>Cacher le mot de passe</value>
</data>
<data name="PasswordGenerationButton.ButtonContent" xml:space="preserve">
<value>Aléatoire</value>
</data>
</root> </root>

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -9,44 +8,38 @@ using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command; using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Models; using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Entry.Commands.AddAttachment;
using ModernKeePass.Application.Entry.Commands.AddHistory; 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.DeleteHistory;
using ModernKeePass.Application.Entry.Commands.RestoreHistory; 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.Models;
using ModernKeePass.Application.Entry.Queries.GetEntry; using ModernKeePass.Application.Entry.Queries.GetEntry;
using ModernKeePass.Application.Group.Commands.AddEntry; using ModernKeePass.Application.Group.Commands.AddEntry;
using ModernKeePass.Application.Group.Commands.DeleteEntry; using ModernKeePass.Application.Group.Commands.DeleteEntry;
using ModernKeePass.Application.Group.Commands.RemoveEntry; using ModernKeePass.Application.Group.Commands.RemoveEntry;
using ModernKeePass.Application.Group.Queries.GetGroup; using ModernKeePass.Application.Group.Queries.GetGroup;
using ModernKeePass.Application.Security.Commands.GeneratePassword;
using ModernKeePass.Application.Security.Queries.EstimatePasswordComplexity;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Domain.Dtos;
using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Extensions; using ModernKeePass.Extensions;
using ModernKeePass.Models; using ModernKeePass.Models;
using ModernKeePass.ViewModels.ListItems;
namespace ModernKeePass.ViewModels 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 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 string CustomChars { get; set; } = string.Empty;
public string Id => SelectedItem.Id; public string Id => SelectedItem.Id;
@@ -61,8 +54,9 @@ namespace ModernKeePass.ViewModels
} }
} }
public IEnumerable<GroupVm> BreadCrumb => new List<GroupVm> { _parent };
public ObservableCollection<EntryVm> History { get; private set; } public ObservableCollection<EntryVm> History { get; private set; }
public ObservableCollection<EntryFieldVm> AdditionalFields { get; private set; }
public ObservableCollection<Attachment> Attachments { get; private set; }
/// <summary> /// <summary>
/// Determines if the Entry is current or from history /// Determines if the Entry is current or from history
@@ -74,10 +68,32 @@ namespace ModernKeePass.ViewModels
get { return _selectedItem; } get { return _selectedItem; }
set set
{ {
Set(() => SelectedItem, ref _selectedItem, value); Set(() => SelectedItem, ref _selectedItem, value, true);
if (value != null) RaisePropertyChanged(); if (value != null)
{
AdditionalFields =
new ObservableCollection<EntryFieldVm>(
SelectedItem.AdditionalFields.Select(f =>
{
var field = new EntryFieldVm(_cryptography);
field.Initialize(f.Name, f.Value, f.IsProtected);
return field;
}));
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 public int SelectedIndex
{ {
get { return _selectedIndex; } get { return _selectedIndex; }
@@ -85,66 +101,72 @@ namespace ModernKeePass.ViewModels
{ {
Set(() => SelectedIndex, ref _selectedIndex, value); Set(() => SelectedIndex, ref _selectedIndex, value);
RaisePropertyChanged(nameof(IsCurrentEntry)); RaisePropertyChanged(nameof(IsCurrentEntry));
AddAttachmentCommand.RaiseCanExecuteChanged();
} }
} }
public double PasswordLength public int AdditionalFieldSelectedIndex
{ {
get { return _passwordLength; } get { return _additionalFieldSelectedIndex; }
set { Set(() => PasswordLength, ref _passwordLength, value); } set
{
Set(() => AdditionalFieldSelectedIndex, ref _additionalFieldSelectedIndex, value);
DeleteAdditionalField.RaiseCanExecuteChanged();
}
} }
public string Title public string Title
{ {
get { return SelectedItem.Title; } get { return SelectedItem.Title.Value; }
set set
{ {
SelectedItem.Title = value; SelectedItem.Title.Value = value;
SetFieldValue(nameof(Title), value).Wait(); SetFieldValue(SelectedItem.Title.Name, value, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public string UserName public string UserName
{ {
get { return SelectedItem.Username; } get { return SelectedItem.Username.Value; }
set set
{ {
SelectedItem.Username = value; SelectedItem.Username.Value = value;
SetFieldValue(nameof(UserName), value).Wait(); SetFieldValue(SelectedItem.Username.Name, value, false).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(UserName)); RaisePropertyChanged(nameof(UserName));
} }
} }
public string Password public string Password
{ {
get { return SelectedItem.Password; } get { return _cryptography.UnProtect(SelectedItem.Password.Value).GetAwaiter().GetResult(); }
set set
{ {
SelectedItem.Password = value; var protectedPassword = _cryptography.Protect(value).ConfigureAwait(false).GetAwaiter().GetResult();
SetFieldValue(nameof(Password), value).Wait(); SelectedItem.Password.Value = protectedPassword;
SetFieldValue(SelectedItem.Password.Name, protectedPassword, true).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(Password)); RaisePropertyChanged(nameof(Password));
RaisePropertyChanged(nameof(PasswordComplexityIndicator));
} }
} }
public string Url public string Url
{ {
get { return SelectedItem.Url; } get { return SelectedItem.Url.Value; }
set set
{ {
SelectedItem.Url = value; SelectedItem.Url.Value = value;
SetFieldValue(nameof(Url), value).Wait(); SetFieldValue(SelectedItem.Url.Name, value, false).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(Url)); RaisePropertyChanged(nameof(Url));
} }
} }
public string Notes public string Notes
{ {
get { return SelectedItem.Notes; } get { return SelectedItem.Notes.Value; }
set set
{ {
SelectedItem.Notes = value; SelectedItem.Notes.Value = value;
SetFieldValue(nameof(Notes), value).Wait(); SetFieldValue(SelectedItem.Notes.Name, value, false).ConfigureAwait(false).GetAwaiter();
} }
} }
@@ -154,7 +176,7 @@ namespace ModernKeePass.ViewModels
set set
{ {
SelectedItem.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString()); SelectedItem.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString());
SetFieldValue(nameof(Icon), SelectedItem.Icon).Wait(); SetFieldValue(EntryFieldName.Icon, SelectedItem.Icon, false).ConfigureAwait(false).GetAwaiter();
} }
} }
@@ -166,7 +188,7 @@ namespace ModernKeePass.ViewModels
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
SelectedItem.ExpirationDate = value.Date; SelectedItem.ExpirationDate = value.Date;
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait(); SetFieldValue(EntryFieldName.ExpirationDate, SelectedItem.ExpirationDate, false).ConfigureAwait(false).GetAwaiter();
} }
} }
@@ -178,7 +200,7 @@ namespace ModernKeePass.ViewModels
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
SelectedItem.ExpirationDate = SelectedItem.ExpirationDate.Date.Add(value); SelectedItem.ExpirationDate = SelectedItem.ExpirationDate.Date.Add(value);
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate).Wait(); SetFieldValue(EntryFieldName.ExpirationDate, SelectedItem.ExpirationDate, false).ConfigureAwait(false).GetAwaiter();
} }
} }
@@ -188,7 +210,7 @@ namespace ModernKeePass.ViewModels
set set
{ {
SelectedItem.HasExpirationDate = value; SelectedItem.HasExpirationDate = value;
SetFieldValue(nameof(HasExpirationDate), value).Wait(); SetFieldValue(EntryFieldName.HasExpirationDate, value, false).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(HasExpirationDate)); RaisePropertyChanged(nameof(HasExpirationDate));
} }
} }
@@ -199,7 +221,7 @@ namespace ModernKeePass.ViewModels
set set
{ {
SelectedItem.BackgroundColor = value.ToColor(); SelectedItem.BackgroundColor = value.ToColor();
SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor).Wait(); SetFieldValue(EntryFieldName.BackgroundColor, SelectedItem.BackgroundColor, false).ConfigureAwait(false).GetAwaiter();
} }
} }
@@ -209,30 +231,27 @@ namespace ModernKeePass.ViewModels
set set
{ {
SelectedItem.ForegroundColor = value.ToColor(); SelectedItem.ForegroundColor = value.ToColor();
SetFieldValue(nameof(ForegroundColor), SelectedItem.ForegroundColor).Wait(); SetFieldValue(EntryFieldName.ForegroundColor, SelectedItem.ForegroundColor, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public bool IsEditMode public bool IsEditMode
{ {
get { return IsCurrentEntry && _isEditMode; } get { return IsCurrentEntry && _isEditMode; }
set { Set(() => IsEditMode, ref _isEditMode, value); } set { Set(() => IsEditMode, ref _isEditMode, value); }
} }
public bool IsRevealPassword
{
get { return _isRevealPassword; }
set { Set(() => IsRevealPassword, ref _isRevealPassword, value); }
}
public RelayCommand SaveCommand { get; } public RelayCommand SaveCommand { get; }
public RelayCommand GeneratePasswordCommand { get; }
public RelayCommand<string> MoveCommand { get; } public RelayCommand<string> MoveCommand { get; }
public RelayCommand RestoreCommand { get; } public RelayCommand RestoreCommand { get; }
public RelayCommand DeleteCommand { get; } public RelayCommand DeleteCommand { get; }
public RelayCommand GoBackCommand { get; } public RelayCommand GoBackCommand { get; }
public RelayCommand GoToParentCommand { get; set; } public RelayCommand GoToParentCommand { get; set; }
public RelayCommand AddAdditionalField { get; set; }
public RelayCommand<EntryFieldVm> DeleteAdditionalField { get; set; }
public RelayCommand<Attachment> OpenAttachmentCommand { get; set; }
public RelayCommand AddAttachmentCommand { get; set; }
public RelayCommand<Attachment> DeleteAttachmentCommand { get; set; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
@@ -241,33 +260,46 @@ namespace ModernKeePass.ViewModels
private readonly IResourceProxy _resource; private readonly IResourceProxy _resource;
private readonly IDialogService _dialog; private readonly IDialogService _dialog;
private readonly INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file;
private readonly ICryptographyClient _cryptography;
private GroupVm _parent; private GroupVm _parent;
private EntryVm _selectedItem; private EntryVm _selectedItem;
private int _selectedIndex; private int _selectedIndex;
private int _additionalFieldSelectedIndex = -1;
private bool _isEditMode; private bool _isEditMode;
private bool _isRevealPassword;
private double _passwordLength = 25;
private bool _isDirty; 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, ICryptographyClient cryptography)
{ {
_mediator = mediator; _mediator = mediator;
_navigation = navigation; _navigation = navigation;
_resource = resource; _resource = resource;
_dialog = dialog; _dialog = dialog;
_notification = notification; _notification = notification;
_file = file;
_cryptography = cryptography;
SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty); SaveCommand = new RelayCommand(async () => await SaveChanges(), () => Database.IsDirty);
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
MoveCommand = new RelayCommand<string>(async destination => await Move(destination), destination => _parent != null && !string.IsNullOrEmpty(destination) && destination != _parent.Id); MoveCommand = new RelayCommand<string>(async destination => await Move(destination), destination => _parent != null && !string.IsNullOrEmpty(destination) && destination != _parent.Id);
RestoreCommand = new RelayCommand(async () => await RestoreHistory()); RestoreCommand = new RelayCommand(async () => await RestoreHistory());
DeleteCommand = new RelayCommand(async () => await AskForDelete()); DeleteCommand = new RelayCommand(async () => await AskForDelete());
GoBackCommand = new RelayCommand(() => _navigation.GoBack()); GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToParentCommand = new RelayCommand(() => GoToGroup(_parent.Id)); 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));
MessengerInstance.Register<PasswordGeneratedMessage>(this, message => Password = message.Password);
} }
public async Task Initialize(string entryId) public async Task Initialize(string entryId)
{ {
SelectedIndex = 0;
SelectedItem = await _mediator.Send(new GetEntryQuery { Id = entryId }); SelectedItem = await _mediator.Send(new GetEntryQuery { Id = entryId });
_parent = await _mediator.Send(new GetGroupQuery { Id = SelectedItem.ParentGroupId }); _parent = await _mediator.Send(new GetGroupQuery { Id = SelectedItem.ParentGroupId });
History = new ObservableCollection<EntryVm> { SelectedItem }; History = new ObservableCollection<EntryVm> { SelectedItem };
@@ -275,7 +307,56 @@ namespace ModernKeePass.ViewModels
{ {
History.Add(entry); History.Add(entry);
} }
SelectedIndex = 0; History.CollectionChanged += (sender, args) =>
{
SelectedIndex = 0;
SaveCommand.RaiseCanExecuteChanged();
};
}
public async Task AddHistory()
{
if (_isDirty && Database.IsOpen) 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(_cryptography));
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() private async Task AskForDelete()
@@ -285,12 +366,13 @@ namespace ModernKeePass.ViewModels
if (IsRecycleOnDelete) if (IsRecycleOnDelete)
{ {
await Delete(); await Delete();
_notification.Show(_resource.GetResourceValue("EntryRecyclingConfirmation"), _resource.GetResourceValue("EntryRecycled")); _notification.Show(_resource.GetResourceValue("EntityDeleting"), string.Format(_resource.GetResourceValue("EntryRecycled"), Title));
} }
else else
{ {
await _dialog.ShowMessage(_resource.GetResourceValue("EntryDeletingConfirmation"), await _dialog.ShowMessage(
_resource.GetResourceValue("EntityDeleteTitle"), string.Format(_resource.GetResourceValue("EntryDeletingConfirmation"), Title),
_resource.GetResourceValue("EntityDeleting"),
_resource.GetResourceValue("EntityDeleteActionButton"), _resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"), _resource.GetResourceValue("EntityDeleteCancelButton"),
async isOk => async isOk =>
@@ -301,70 +383,36 @@ namespace ModernKeePass.ViewModels
} }
else else
{ {
await _dialog.ShowMessage(_resource.GetResourceValue("HistoryDeleteDescription"), _resource.GetResourceValue("HistoryDeleteTitle"), await _dialog.ShowMessage(_resource.GetResourceValue("HistoryDeleteDescription"),
_resource.GetResourceValue("HistoryDeleteTitle"),
_resource.GetResourceValue("EntityDeleteActionButton"), _resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"), async isOk => _resource.GetResourceValue("EntityDeleteCancelButton"), async isOk =>
{ {
if (!isOk) return; if (!isOk) return;
await _mediator.Send(new DeleteHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 }); await _mediator.Send(new DeleteHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 });
History.RemoveAt(SelectedIndex); History.RemoveAt(SelectedIndex);
SelectedIndex = 0;
SaveCommand.RaiseCanExecuteChanged();
}); });
} }
} }
public async Task GeneratePassword()
{
Password = await _mediator.Send(new GeneratePasswordCommand
{
BracketsPatternSelected = BracketsPatternSelected,
CustomChars = CustomChars,
DigitsPatternSelected = DigitsPatternSelected,
LowerCasePatternSelected = LowerCasePatternSelected,
MinusPatternSelected = MinusPatternSelected,
PasswordLength = (int)PasswordLength,
SpacePatternSelected = SpacePatternSelected,
SpecialPatternSelected = SpecialPatternSelected,
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()
{
if (_isDirty) await _mediator.Send(new AddHistoryCommand { Entry = History[0] });
}
private async Task RestoreHistory() private async Task RestoreHistory()
{ {
await _mediator.Send(new RestoreHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 }); await _mediator.Send(new RestoreHistoryCommand { Entry = History[0], HistoryIndex = History.Count - SelectedIndex - 1 });
History.Insert(0, SelectedItem); History.Insert(0, SelectedItem);
SelectedIndex = 0;
SaveCommand.RaiseCanExecuteChanged();
} }
private async Task SaveChanges() private async Task SaveChanges()
{ {
await AddHistory(); await AddHistory();
await _mediator.Send(new SaveDatabaseCommand()); try
SaveCommand.RaiseCanExecuteChanged(); {
_isDirty = false; await _mediator.Send(new SaveDatabaseCommand());
}
catch (SaveException e)
{
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
}
UpdateDirtyStatus(false);
} }
private async Task Delete() private async Task Delete()
@@ -375,12 +423,40 @@ namespace ModernKeePass.ViewModels
ParentGroupId = SelectedItem.ParentGroupId, ParentGroupId = SelectedItem.ParentGroupId,
RecycleBinName = _resource.GetResourceValue("RecycleBinTitle") RecycleBinName = _resource.GetResourceValue("RecycleBinTitle")
}); });
_isDirty = false;
_navigation.GoBack(); _navigation.GoBack();
} }
public void GoToGroup(string groupId) private async Task OpenAttachment(Attachment attachment)
{ {
_navigation.NavigateTo(Constants.Navigation.GroupPage, new NavigationItem { Id = groupId }); var extensionIndex = attachment.Name.LastIndexOf('.');
var fileInfo = await _file.CreateFile(attachment.Name,
attachment.Name.Substring(extensionIndex, attachment.Name.Length - extensionIndex),
string.Empty,
false);
if (fileInfo == null) return;
await _file.WriteBinaryContentsToFile(fileInfo.Id, attachment.Content);
}
private async Task AddAttachment()
{
var fileInfo = await _file.OpenFile(string.Empty, Domain.Common.Constants.Extensions.Any, false);
if (fileInfo == null) return;
var contents = await _file.ReadBinaryFile(fileInfo.Id);
await _mediator.Send(new AddAttachmentCommand { Entry = SelectedItem, AttachmentName = fileInfo.Name, AttachmentContent = contents });
Attachments.Add(new Attachment { Name = fileInfo.Name, Content = contents });
}
private async Task DeleteAttachment(Attachment attachment)
{
await _mediator.Send(new DeleteAttachmentCommand { Entry = SelectedItem, AttachmentName = attachment.Name });
Attachments.Remove(attachment);
}
private void UpdateDirtyStatus(bool isDirty)
{
SaveCommand.RaiseCanExecuteChanged();
_isDirty = isDirty;
} }
} }
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
@@ -9,6 +8,7 @@ using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command; using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Models; 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.CreateGroup;
using ModernKeePass.Application.Group.Commands.DeleteGroup; using ModernKeePass.Application.Group.Commands.DeleteGroup;
using ModernKeePass.Application.Group.Commands.MoveEntry; using ModernKeePass.Application.Group.Commands.MoveEntry;
using ModernKeePass.Application.Group.Commands.MoveGroup;
using ModernKeePass.Application.Group.Commands.RemoveGroup; using ModernKeePass.Application.Group.Commands.RemoveGroup;
using ModernKeePass.Application.Group.Commands.SortEntries; using ModernKeePass.Application.Group.Commands.SortEntries;
using ModernKeePass.Application.Group.Commands.SortGroups; using ModernKeePass.Application.Group.Commands.SortGroups;
using ModernKeePass.Application.Group.Commands.UpdateGroup; using ModernKeePass.Application.Group.Commands.UpdateGroup;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;
using ModernKeePass.Application.Group.Queries.GetGroup; using ModernKeePass.Application.Group.Queries.GetGroup;
using ModernKeePass.Application.Group.Queries.SearchEntries;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Models; using ModernKeePass.Models;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class GroupDetailVm : ObservableObject public class GroupDetailVm : ViewModelBase
{ {
public ObservableCollection<EntryVm> Entries { get; private set; } public ObservableCollection<EntryVm> Entries { get; private set; }
@@ -42,7 +43,7 @@ namespace ModernKeePass.ViewModels
public bool IsNotRoot => Database.RootGroupId != _group.Id; public bool IsNotRoot => Database.RootGroupId != _group.Id;
public IOrderedEnumerable<IGrouping<char, EntryVm>> EntriesZoomedOut => from e in Entries public IOrderedEnumerable<IGrouping<char, EntryVm>> EntriesZoomedOut => from e in Entries
group e by e.Title.ToUpper().FirstOrDefault() into grp group e by (e.Title.Value ?? string.Empty).ToUpper().FirstOrDefault() into grp
orderby grp.Key orderby grp.Key
select grp; select grp;
@@ -90,7 +91,7 @@ namespace ModernKeePass.ViewModels
public RelayCommand SortGroupsCommand { get; } public RelayCommand SortGroupsCommand { get; }
public RelayCommand<string> MoveCommand { get; } public RelayCommand<string> MoveCommand { get; }
public RelayCommand CreateEntryCommand { get; } public RelayCommand CreateEntryCommand { get; }
public RelayCommand CreateGroupCommand { get; } public RelayCommand<string> CreateGroupCommand { get; }
public RelayCommand DeleteCommand { get; set; } public RelayCommand DeleteCommand { get; set; }
public RelayCommand GoBackCommand { get; set; } public RelayCommand GoBackCommand { get; set; }
public RelayCommand GoToParentCommand { get; set; } public RelayCommand GoToParentCommand { get; set; }
@@ -106,6 +107,7 @@ namespace ModernKeePass.ViewModels
private GroupVm _parent; private GroupVm _parent;
private bool _isEditMode; private bool _isEditMode;
private EntryVm _reorderedEntry; private EntryVm _reorderedEntry;
private GroupVm _reorderedGroup;
public GroupDetailVm(IMediator mediator, IResourceProxy resource, INavigationService navigation, IDialogService dialog, INotificationService notification) 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); SortGroupsCommand = new RelayCommand(async () => await SortGroupsAsync(), () => IsEditMode);
MoveCommand = new RelayCommand<string>(async destination => await Move(destination), destination => IsNotRoot && !string.IsNullOrEmpty(destination) && destination != Id); 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); 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); DeleteCommand = new RelayCommand(async () => await AskForDelete(),() => IsNotRoot);
GoBackCommand = new RelayCommand(() => _navigation.GoBack()); GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToParentCommand= new RelayCommand(() => GoToGroup(_parent.Id), () => _parent != null); GoToParentCommand= new RelayCommand(() => GoToGroup(_parent.Id), () => _parent != null);
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
} }
public async Task Initialize(string groupId) public async Task Initialize(string groupId)
@@ -136,7 +140,8 @@ namespace ModernKeePass.ViewModels
Entries = new ObservableCollection<EntryVm>(_group.Entries); Entries = new ObservableCollection<EntryVm>(_group.Entries);
Entries.CollectionChanged += Entries_CollectionChanged; Entries.CollectionChanged += Entries_CollectionChanged;
Groups = new ObservableCollection<GroupVm>(_group.SubGroups); Groups = new ObservableCollection<GroupVm>(_group.Groups);
Groups.CollectionChanged += Groups_CollectionChanged;
} }
public void GoToEntry(string entryId, bool isNew = false) public void GoToEntry(string entryId, bool isNew = false)
@@ -159,7 +164,7 @@ namespace ModernKeePass.ViewModels
public async Task AddNewGroup(string name = "") public async Task AddNewGroup(string name = "")
{ {
var group = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group}); var group = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group});
GoToGroup(group.Id, true); Groups.Add(group);
} }
public async Task AddNewEntry() public async Task AddNewEntry()
@@ -175,14 +180,16 @@ namespace ModernKeePass.ViewModels
GoToGroup(destinationId); GoToGroup(destinationId);
} }
public async Task<IEnumerable<EntryVm>> Search(string queryText)
{
return await _mediator.Send(new SearchEntriesQuery {GroupId = Id, SearchText = queryText});
}
private async Task SaveChanges() 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(); SaveCommand.RaiseCanExecuteChanged();
} }
@@ -209,6 +216,29 @@ namespace ModernKeePass.ViewModels
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
} }
private async void Groups_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Remove:
var oldIndex = e.OldStartingIndex;
_reorderedGroup = _group.Groups[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() private async Task SortEntriesAsync()
{ {
await _mediator.Send(new SortEntriesCommand {Group = _group}); await _mediator.Send(new SortEntriesCommand {Group = _group});
@@ -221,7 +251,7 @@ namespace ModernKeePass.ViewModels
private async Task SortGroupsAsync() private async Task SortGroupsAsync()
{ {
await _mediator.Send(new SortGroupsCommand {Group = _group}); await _mediator.Send(new SortGroupsCommand {Group = _group});
Groups = new ObservableCollection<GroupVm>(_group.SubGroups); Groups = new ObservableCollection<GroupVm>(_group.Groups);
RaisePropertyChanged(nameof(Groups)); RaisePropertyChanged(nameof(Groups));
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
} }
@@ -231,11 +261,13 @@ namespace ModernKeePass.ViewModels
if (IsRecycleOnDelete) if (IsRecycleOnDelete)
{ {
await Delete(); await Delete();
_notification.Show(_resource.GetResourceValue("GroupRecyclingConfirmation"), _resource.GetResourceValue("GroupRecycled")); _notification.Show(_resource.GetResourceValue("EntityDeleting"), string.Format(_resource.GetResourceValue("GroupRecycled"), Title));
} }
else 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("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"), _resource.GetResourceValue("EntityDeleteCancelButton"),
async isOk => async isOk =>

View File

@@ -184,6 +184,7 @@ namespace ModernKeePass.ViewModels
catch (SaveException exception) catch (SaveException exception)
{ {
_notification.Show(exception.Source, exception.Message); _notification.Show(exception.Source, exception.Message);
MessengerInstance.Send(new SaveErrorMessage { Message = exception.Message });
return; return;
} }
} }

View File

@@ -3,12 +3,12 @@ using System.Linq;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Domain.Interfaces; using ModernKeePass.Domain.Interfaces;
using ModernKeePass.ViewModels.ListItems; using ModernKeePass.ViewModels.ListItems;
using ModernKeePass.Views; using ModernKeePass.Views;
using ModernKeePass.Views.SettingsPageFrames;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
@@ -58,19 +58,11 @@ namespace ModernKeePass.ViewModels
IsSelected = true IsSelected = true
}, },
new ListMenuItemVm new ListMenuItemVm
{
Title = resource.GetResourceValue("SettingsMenuItemSave"),
Group = resource.GetResourceValue("SettingsMenuGroupApplication"),
SymbolIcon = Symbol.Save,
PageType = typeof(SettingsSavePage)
},
new ListMenuItemVm
{ {
Title = resource.GetResourceValue("SettingsMenuItemGeneral"), Title = resource.GetResourceValue("SettingsMenuItemGeneral"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"), Group = resource.GetResourceValue("SettingsMenuGroupApplication"),
SymbolIcon = Symbol.Setting, SymbolIcon = Symbol.Setting,
PageType = typeof(SettingsDatabasePage), PageType = typeof(SettingsGeneralPage)
IsEnabled = database.IsOpen
}, },
new ListMenuItemVm new ListMenuItemVm
{ {
@@ -79,6 +71,30 @@ namespace ModernKeePass.ViewModels
SymbolIcon = Symbol.Permissions, SymbolIcon = Symbol.Permissions,
PageType = typeof(SettingsSecurityPage), PageType = typeof(SettingsSecurityPage),
IsEnabled = database.IsOpen 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); SelectedItem = menuItems.FirstOrDefault(m => m.IsSelected);

View File

@@ -1,4 +1,4 @@
<Page <Page x:Name="Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -8,7 +8,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core" xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:actions="using:ModernKeePass.Actions" xmlns:actions="using:ModernKeePass.Actions"
xmlns:userControls="using:ModernKeePass.Views.UserControls" xmlns:controls="using:ModernKeePass.Views.UserControls"
x:Class="ModernKeePass.Views.EntryDetailPage" x:Class="ModernKeePass.Views.EntryDetailPage"
mc:Ignorable="d" mc:Ignorable="d"
SizeChanged="EntryDetailPage_OnSizeChanged" SizeChanged="EntryDetailPage_OnSizeChanged"
@@ -16,347 +16,6 @@
<Page.Resources> <Page.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" /> <converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
<converters:ProgressBarLegalValuesConverter x:Key="ProgressBarLegalValuesConverter" />
<converters:DoubleToSolidColorBrushConverter x:Key="DoubleToForegroundBrushComplexityConverter" />
<Style TargetType="local:PasswordBoxWithButton" x:Key="PasswordBoxWithButtonStyle">
<Setter Property="Background" Value="{ThemeResource SearchBoxBackgroundThemeBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource ComboBoxBorderThemeBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource SearchBoxContentThemeFontSize}" />
<Setter Property="FontWeight" Value="{ThemeResource SearchBoxContentThemeFontWeight}"/>
<Setter Property="Foreground" Value="{ThemeResource SearchBoxForegroundThemeBrush}" />
<Setter Property="Padding" Value="{ThemeResource SearchBoxThemePadding}"/>
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Width" Value="350" />
<Setter Property="Height" Value="32" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="Typography.StylisticSet20" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:PasswordBoxWithButton">
<Grid x:Name="SearchBoxGrid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxBorder" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxPointerOverBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxBorder" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxPointerOverBorderThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxPointerOverTextThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxDisabledBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxBorder" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxDisabledBorderThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxDisabledTextThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PasswordBox" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxBorder" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource MainColor}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxButtonForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButton" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedBorderThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="FocusedDropDown">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxGrid" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchBoxBorder" Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedBorderThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButton" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedTextThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.Resources>
<Style x:Key="ActionButtonStyle" TargetType="Button">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Background="Transparent">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchGlyph" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxButtonPointerOverForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButtonBackground" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxButtonPointerOverBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchGlyph" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedTextThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ActionButtonBackground" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxFocusedBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused" />
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="ActionButtonBackground" Background="{TemplateBinding Background}">
<TextBlock x:Name="SearchGlyph"
AutomationProperties.AccessibilityView="Raw"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
FontStyle="Normal"
Padding="4,0,4,0"
Text="{TemplateBinding Content}"
VerticalAlignment="Center" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
<Setter Property="MinWidth" Value="{ThemeResource TextControlThemeMinWidth}" />
<Setter Property="MinHeight" Value="{ThemeResource TextControlThemeMinHeight}" />
<Setter Property="Foreground" Value="{ThemeResource TextBoxForegroundThemeBrush}" />
<Setter Property="Background" Value="{ThemeResource TextBoxBackgroundThemeBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource TextBoxBorderThemeBrush}" />
<Setter Property="SelectionHighlightColor" Value="{ThemeResource TextSelectionHighlightColorThemeBrush}" />
<Setter Property="BorderThickness" Value="{ThemeResource TextControlBorderThemeThickness}" />
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden" />
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False" />
<Setter Property="Padding" Value="{ThemeResource TextControlThemePadding}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="PasswordBox">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxDisabledBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxDisabledBorderThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxDisabledForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextContentPresenter"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxDisabledForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="BackgroundElement"
Storyboard.TargetProperty="Opacity"
Duration="0"
To="{ThemeResource TextControlBackgroundThemeOpacity}" />
<DoubleAnimation Storyboard.TargetName="BorderElement"
Storyboard.TargetProperty="Opacity"
Duration="0"
To="{ThemeResource TextControlBorderThemeOpacity}" />
</Storyboard>
</VisualState>
<VisualState x:Name="PointerOver">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="BackgroundElement"
Storyboard.TargetProperty="Opacity"
Duration="0"
To="{ThemeResource TextControlPointerOverBackgroundThemeOpacity}" />
<DoubleAnimation Storyboard.TargetName="BorderElement"
Storyboard.TargetProperty="Opacity"
Duration="0"
To="{ThemeResource TextControlPointerOverBorderThemeOpacity}" />
</Storyboard>
</VisualState>
<VisualState x:Name="Focused" />
</VisualStateGroup>
<VisualStateGroup x:Name="ButtonStates" />
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="BackgroundElement"
Grid.Row="1"
Background="{TemplateBinding Background}"
Margin="{TemplateBinding BorderThickness}" />
<Border x:Name="BorderElement"
Grid.Row="1"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" />
<ContentPresenter x:Name="HeaderContentPresenter"
Grid.Row="0"
Foreground="{ThemeResource TextBoxForegroundHeaderThemeBrush}"
Margin="0,4,0,4"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
FontWeight="Semilight" />
<ScrollViewer x:Name="ContentElement"
Grid.Row="1"
HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}"
Margin="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
IsTabStop="False"
AutomationProperties.AccessibilityView="Raw"
ZoomMode="Disabled" />
<ContentControl x:Name="PlaceholderTextContentPresenter"
Grid.Row="1"
Foreground="{ThemeResource TextBoxPlaceholderTextThemeBrush}"
Margin="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
IsTabStop="False"
Grid.ColumnSpan="2"
Content="{TemplateBinding PlaceholderText}"
IsHitTestVisible="False" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<Border x:Name="SearchBoxBorder"
Background="Transparent"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<PasswordBox x:Name="PasswordBox"
BorderThickness="0"
Background="Transparent"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
MaxLength="2048"
MinHeight="{ThemeResource SearchBoxTextBoxThemeMinHeight}"
Padding="{TemplateBinding Padding}"
PlaceholderText="{TemplateBinding PlaceholderText}"
Style="{StaticResource PasswordBoxStyle}"
VerticalAlignment="Stretch"
Margin="0"
Password="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=Password}"
IsPasswordRevealButtonEnabled="{TemplateBinding IsPasswordRevealEnabled}"/>
<Button x:Name="ActionButton"
AutomationProperties.AccessibilityView="Raw"
Background="Transparent"
FontWeight="{ThemeResource SearchBoxButtonThemeFontWeight}"
FontSize="{TemplateBinding FontSize}"
Grid.Column="1"
Style="{StaticResource ActionButtonStyle}"
Content="{TemplateBinding ButtonSymbol}"
IsEnabled="{TemplateBinding IsButtonEnabled}">
<Button.Flyout>
<Flyout>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding Password}" ComparisonCondition="NotEqual" Value="" >
<!--<actions:CloseFlyoutAction />-->
<core:CallMethodAction MethodName="Hide" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<StackPanel>
<TextBlock>
<Run x:Uid="PasswordGeneratorLength" />
<Run Text="{Binding PasswordLength}" />
</TextBlock>
<Slider Value="{Binding PasswordLength, Mode=TwoWay}" Margin="0,-10,0,-20" />
<CheckBox IsChecked="{Binding UpperCasePatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorUpper" />
<CheckBox IsChecked="{Binding LowerCasePatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorLower" />
<CheckBox IsChecked="{Binding DigitsPatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorDigits" />
<CheckBox IsChecked="{Binding MinusPatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorMinus" />
<CheckBox IsChecked="{Binding UnderscorePatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorUnderscore" />
<CheckBox IsChecked="{Binding SpacePatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorSpace" />
<CheckBox IsChecked="{Binding SpecialPatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorSpecial" />
<CheckBox IsChecked="{Binding BracketsPatternSelected, Mode=TwoWay}" x:Uid="PasswordGeneratorBrackets" />
<TextBlock x:Uid="PasswordGeneratorAlso" Margin="0,5,0,0"/>
<TextBox Text="{Binding CustomChars, Mode=TwoWay}" />
<Button x:Uid="PasswordGeneratorButton" Command="{Binding GeneratePasswordCommand}" />
</StackPanel>
</Flyout>
</Button.Flyout>
<ToolTipService.ToolTip>
<ToolTip x:Uid="PasswordGeneratorTooltip" />
</ToolTipService.ToolTip>
</Button>
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources> </Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.ChildrenTransitions> <Grid.ChildrenTransitions>
@@ -365,153 +24,246 @@
</TransitionCollection> </TransitionCollection>
</Grid.ChildrenTransitions> </Grid.ChildrenTransitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuGridLength}"/> <RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}" x:Name="LeftListViewColumn" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView" <controls:HamburgerMenuUserControl
ItemsSource="{Binding History}" x:Name="HamburgerMenu"
ResizeTarget="{Binding ElementName=LeftListViewColumn}" x:Uid="HistoryLeftListView"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" ItemsSource="{Binding History}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" /> SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<ScrollViewer VerticalScrollBarVisibility="Auto"> <Hub Padding="0">
<StackPanel Margin="20,0,0,20"> <Hub.Resources>
<StackPanel.Resources> <Style TargetType="TextBlock" x:Key="EntryTextBlockStyle">
<Style TargetType="TextBlock"> <Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="Margin" Value="0,20,0,0"/> <Setter Property="FontSize" Value="16"/>
<Setter Property="FontSize" Value="18"/> <Setter Property="TextWrapping" Value="Wrap"/>
<Setter Property="TextWrapping" Value="Wrap"/> </Style>
</Style> </Hub.Resources>
<Style TargetType="CheckBox"> <HubSection x:Uid="EntryHubMain">
<Setter Property="Margin" Value="0,20,0,0"/> <DataTemplate>
<Setter Property="FontSize" Value="18"/> <ScrollViewer VerticalScrollBarVisibility="Auto">
</Style> <StackPanel Width="350" Margin="0,0,25,0">
</StackPanel.Resources> <StackPanel.Resources>
<TextBlock x:Uid="EntryLogin" /> <Style TargetType="CheckBox">
<local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}"> <Setter Property="Margin" Value="0,20,0,0"/>
<interactivity:Interaction.Behaviors> <Setter Property="FontSize" Value="18"/>
<core:EventTriggerBehavior EventName="ButtonClick"> </Style>
<actions:ClipboardAction Text="{Binding UserName}" /> </StackPanel.Resources>
<actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Title}" /> <TextBlock x:Uid="EntryLogin" Style="{StaticResource EntryTextBlockStyle}" />
</core:EventTriggerBehavior> <local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonContent="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
</interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
</local:TextBoxWithButton> <core:EventTriggerBehavior EventName="ButtonClick">
<TextBlock x:Uid="EntryPassword" /> <actions:ClipboardAction Text="{Binding UserName}" />
<local:PasswordBoxWithButton Password="{Binding Password, Mode=TwoWay}" IsPasswordRevealEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsCurrentEntry}" ButtonSymbol="&#xE15E;" /> <actions:ToastAction x:Uid="ToastCopyLogin" Title="{Binding Title}" />
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}"> </core:EventTriggerBehavior>
<interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick"> </local:TextBoxWithButton>
<actions:ClipboardAction Text="{Binding Password}" /> <TextBlock x:Uid="EntryPassword" Style="{StaticResource EntryTextBlockStyle}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" /> <controls:PasswordGenerationBox Password="{Binding Password, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</core:EventTriggerBehavior> <TextBlock Text="URL" Style="{StaticResource EntryTextBlockStyle}"/>
</interactivity:Interaction.Behaviors> <local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonContent="&#xE111;" IsEnabled="{Binding IsCurrentEntry}">
</local:TextBoxWithButton> <interactivity:Interaction.Behaviors>
<ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" /> <core:EventTriggerBehavior EventName="ButtonClick">
<CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" IsEnabled="{Binding IsRevealPasswordEnabled}" /> <actions:NavigateToUrlAction Url="{Binding Url}" />
<TextBlock TextWrapping="Wrap" Text="URL" FontSize="18"/> </core:EventTriggerBehavior>
<local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsCurrentEntry}"> </interactivity:Interaction.Behaviors>
<interactivity:Interaction.Behaviors> </local:TextBoxWithButton>
<core:EventTriggerBehavior EventName="ButtonClick"> <TextBlock x:Uid="EntryNotes" Style="{StaticResource EntryTextBlockStyle}" />
<actions:NavigateToUrlAction Url="{Binding Url}" /> <TextBox TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" />
</core:EventTriggerBehavior> <CheckBox x:Uid="EntryExpirationDate" Margin="-3,0,0,0" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
</interactivity:Interaction.Behaviors> <Grid>
</local:TextBoxWithButton> <Grid.ColumnDefinitions>
<TextBlock x:Uid="EntryNotes" /> <ColumnDefinition Width="Auto" />
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" /> <ColumnDefinition Width="*" />
<CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" /> </Grid.ColumnDefinitions>
<Grid> <SymbolIcon Grid.Column="0" Symbol="Important" Foreground="DarkRed" Visibility="{Binding HasExpired, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid.ColumnDefinitions> <ToolTipService.ToolTip>
<ColumnDefinition Width="Auto" /> <ToolTip x:Uid="EntryExpirationTooltip" />
<ColumnDefinition Width="*" /> </ToolTipService.ToolTip>
</Grid.ColumnDefinitions> </SymbolIcon>
<SymbolIcon Grid.Column="0" Symbol="Important" Foreground="DarkRed" Visibility="{Binding HasExpired, Converter={StaticResource BooleanToVisibilityConverter}}"> <StackPanel Grid.Column="1" x:Name="ExpirationDatePanel" Orientation="Vertical" Visibility="{Binding HasExpirationDate, Converter={StaticResource BooleanToVisibilityConverter}}">
<ToolTipService.ToolTip> <DatePicker Margin="10,5,20,0" Date="{Binding ExpiryDate, Mode=TwoWay}" />
<ToolTip x:Uid="EntryExpirationTooltip" /> <TimePicker Margin="10,10,0,0" Time="{Binding ExpiryTime, Mode=TwoWay}" />
</ToolTipService.ToolTip> </StackPanel>
</SymbolIcon> </Grid>
<StackPanel Grid.Column="1" x:Name="ExpirationDatePanel" Orientation="Horizontal" Visibility="{Binding HasExpirationDate, Converter={StaticResource BooleanToVisibilityConverter}}"> </StackPanel>
<DatePicker Margin="0,0,20,0" Date="{Binding ExpiryDate, Mode=TwoWay}" /> <VisualStateManager.VisualStateGroups>
<TimePicker Time="{Binding ExpiryTime, Mode=TwoWay}" /> <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}" />
<controls:SymbolPickerUserControl SelectedSymbol="{Binding Icon, Mode=TwoWay}" HorizontalAlignment="Left" IsEnabled="{Binding IsCurrentEntry}" />
<TextBlock x:Uid="EntryBackgroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<controls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" />
<TextBlock x:Uid="EntryForegroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<controls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" />
</StackPanel> </StackPanel>
</Grid> </DataTemplate>
<StackPanel x:Name="EditDesign" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Orientation="Horizontal"> </HubSection>
<StackPanel Width="250" HorizontalAlignment="Left"> <HubSection x:Uid="EntryHubAdditional">
<TextBlock x:Uid="EntryBackgroundColor" /> <DataTemplate>
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" /> <local:SelectableTemplateListView
</StackPanel> ItemsSource="{Binding AdditionalFields}"
<StackPanel Width="250" HorizontalAlignment="Left"> SelectedIndex="{Binding AdditionalFieldSelectedIndex, Mode=TwoWay}"
<TextBlock x:Uid="EntryForegroundColor" /> ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" /> <local:SelectableTemplateListView.HeaderTemplate>
</StackPanel> <DataTemplate>
</StackPanel> <StackPanel Orientation="Vertical">
</StackPanel> <HyperlinkButton Command="{Binding Path=DataContext.AddAdditionalField, ElementName=Page}">
</ScrollViewer> <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="{Binding DisplayValue}" 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>
</Grid> </Grid>
<!-- Bouton Précédent et titre de la page --> <!-- Bouton Précédent et titre de la page -->
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}"> <Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}"/> <ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" <Button Grid.Column="0"
Command="{Binding GoBackCommand}" Command="{Binding GoBackCommand}"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuSize}" Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back" AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton" AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}"> Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" /> <SymbolIcon Symbol="Back" />
</Button> </Button>
<Grid Grid.Column="1"> <Button Grid.Column="1"
<Grid.ColumnDefinitions> Height="{StaticResource MenuHeight}"
<ColumnDefinition Width="60" /> Width="{StaticResource MenuWidth}"
<ColumnDefinition Width="*" /> Command="{Binding GoToParentCommand}"
</Grid.ColumnDefinitions> Style="{StaticResource NoBorderButtonStyle}">
<Grid.RowDefinitions> <SymbolIcon Symbol="Up" />
<RowDefinition Height="40" /> <ToolTipService.ToolTip>
<RowDefinition Height="20" /> <ToolTip Content="{Binding ParentGroupName}" />
</Grid.RowDefinitions> </ToolTipService.ToolTip>
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"> </Button>
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" /> <Viewbox Grid.Column="2" MaxHeight="200">
</Viewbox> <SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}"> </Viewbox>
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" /> <TextBox Grid.Column="3"
</Viewbox> x:Uid="EntryTitle"
<TextBox Grid.Column="1" Grid.Row="0" x:Name="TitleTextBox"
x:Uid="EntryTitle" Text="{Binding Title, Mode=TwoWay}"
x:Name="TitleTextBox" Background="Transparent"
Text="{Binding Title, Mode=TwoWay}" IsHitTestVisible="{Binding IsEditMode}"
Background="Transparent" FontSize="20"
IsHitTestVisible="{Binding IsEditMode}" FontWeight="Light"
FontSize="20" TextWrapping="NoWrap"
FontWeight="Light" VerticalAlignment="Center">
TextWrapping="NoWrap" <interactivity:Interaction.Behaviors>
VerticalAlignment="Center"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<interactivity:Interaction.Behaviors> <actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True"> <core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" /> </core:DataTriggerBehavior>
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" /> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
</core:DataTriggerBehavior> <core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False"> </core:DataTriggerBehavior>
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" /> </interactivity:Interaction.Behaviors>
</core:DataTriggerBehavior> </TextBox>
</interactivity:Interaction.Behaviors> <controls:TopMenuUserControl
</TextBox> x:Name="TopMenu" Grid.Column="4"
<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"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
MoveButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource BooleanToVisibilityConverter}}" MoveButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource BooleanToVisibilityConverter}}"
RestoreButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource InverseBooleanToVisibilityConverter}}" RestoreButtonVisibility="{Binding IsCurrentEntry, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
@@ -524,31 +276,7 @@
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" /> <actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl> </controls:TopMenuUserControl>
</Grid> </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> </Grid>
</Page> </Page>

View File

@@ -29,7 +29,6 @@ namespace ModernKeePass.Views
{ {
await Model.Initialize(args.Id); await Model.Initialize(args.Id);
Model.IsEditMode = args.IsNew; Model.IsEditMode = args.IsNew;
if (args.IsNew) await Model.GeneratePassword();
} }
} }
@@ -42,8 +41,24 @@ namespace ModernKeePass.Views
private void EntryDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e) private void EntryDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
{ {
VisualStateManager.GoToState(this, e.NewSize.Width < 700 ? "Small" : "Large", true); if (e.NewSize.Width <= 640)
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true); {
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);
}
} }
} }
} }

View File

@@ -20,9 +20,6 @@
</Page.Resources> </Page.Resources>
<Grid> <Grid>
<Grid.Resources> <Grid.Resources>
<CollectionViewSource
x:Name="EntriesViewSource"
Source="{Binding Entries}" />
<CollectionViewSource <CollectionViewSource
x:Name="EntriesZoomedOutViewSource" x:Name="EntriesZoomedOutViewSource"
Source="{Binding EntriesZoomedOut}" IsSourceGrouped="True" /> Source="{Binding EntriesZoomedOut}" IsSourceGrouped="True" />
@@ -36,21 +33,21 @@
</TransitionCollection> </TransitionCollection>
</Grid.ChildrenTransitions> </Grid.ChildrenTransitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuGridLength}"/> <RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ExpandedMenuGridLength}" x:Name="LeftListViewColumn" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl <userControls:HamburgerMenuUserControl
x:Name="HamburgerMenu"
x:Uid="GroupsLeftListView" x:Uid="GroupsLeftListView"
ItemsSource="{Binding Groups}" ItemsSource="{Binding Groups}"
CanDragItems="{Binding IsEditMode}"
SelectionChanged="groups_SelectionChanged" SelectionChanged="groups_SelectionChanged"
ActionButtonCommand="{Binding CreateGroupCommand}" ActionButtonCommand="{Binding CreateGroupCommand}"
ResizeTarget="{Binding ElementName=LeftListViewColumn}"
IsOpen="True"
IsButtonVisible="Visible" /> IsButtonVisible="Visible" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -63,7 +60,6 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" x:Uid="ReorderEntriesLabel" Margin="10,10,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" /> <TextBlock Grid.Column="0" Grid.Row="0" x:Uid="ReorderEntriesLabel" Margin="10,10,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" />
<!--<TextBlock Grid.Column="1" Grid.Row="0" x:Uid="EntrySymbol" Margin="40,20,0,0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource BodyTextBlockStyle}" />-->
<HyperlinkButton Grid.Column="2" Grid.Row="0" VerticalAlignment="Top" Command="{Binding CreateEntryCommand}" HorizontalAlignment="Right"> <HyperlinkButton Grid.Column="2" Grid.Row="0" VerticalAlignment="Top" Command="{Binding CreateEntryCommand}" HorizontalAlignment="Right">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Add"> <SymbolIcon Symbol="Add">
@@ -80,21 +76,19 @@
<!-- Horizontal scrolling grid --> <!-- Horizontal scrolling grid -->
<GridView <GridView
x:Name="GridView" x:Name="GridView"
ItemsSource="{Binding Source={StaticResource EntriesViewSource}}" ItemsSource="{Binding Entries}"
AutomationProperties.AutomationId="ItemGridView" AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Entries" AutomationProperties.Name="Entries"
TabIndex="1" TabIndex="1"
Margin="10,0,0,0"
SelectionChanged="entries_SelectionChanged" SelectionChanged="entries_SelectionChanged"
IsSynchronizedWithCurrentItem="False" IsSynchronizedWithCurrentItem="False"
BorderBrush="{StaticResource ListViewItemSelectedBackgroundThemeBrush}" BorderBrush="{StaticResource ListViewItemSelectedBackgroundThemeBrush}"
AllowDrop="True" AllowDrop="True"
CanReorderItems="True" CanReorderItems="{Binding IsEditMode}"
CanDragItems="True" CanDragItems="{Binding IsEditMode}">
DragItemsStarting="GridView_DragItemsStarting">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<actions:SetupFocusAction TargetObject="{Binding ElementName=GridView}" /> <actions:SetupFocusAction TargetObject="{Binding}" />
</core:DataTriggerBehavior> </core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
<GridView.ItemTemplate> <GridView.ItemTemplate>
@@ -116,7 +110,7 @@
<TextBlock Text="{Binding Username}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" /> <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" /> <TextBlock Text="{Binding Url}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
</StackPanel> </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" /> <SymbolIcon Symbol="More" />
<Button.Flyout> <Button.Flyout>
<MenuFlyout> <MenuFlyout>
@@ -131,12 +125,12 @@
<MenuFlyoutItem x:Uid="EntryItemCopyPassword"> <MenuFlyoutItem x:Uid="EntryItemCopyPassword">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click"> <core:EventTriggerBehavior EventName="Click">
<actions:ClipboardAction Text="{Binding Password}" /> <actions:ClipboardAction Text="{Binding Password}" IsProtected="True" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" /> <actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</MenuFlyoutItem> </MenuFlyoutItem>
<MenuFlyoutItem x:Uid="EntryItemCopyUrl" IsEnabled="{Binding HasUrl}"> <MenuFlyoutItem x:Uid="EntryItemCopyUrl" IsEnabled="{Binding IsValidUrl}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click"> <core:EventTriggerBehavior EventName="Click">
<actions:NavigateToUrlAction Url="{Binding Url}" /> <actions:NavigateToUrlAction Url="{Binding Url}" />
@@ -154,23 +148,26 @@
<SemanticZoom.ZoomedOutView> <SemanticZoom.ZoomedOutView>
<GridView <GridView
ItemsSource="{Binding Source={StaticResource EntriesZoomedOutViewSource}}" ItemsSource="{Binding Source={StaticResource EntriesZoomedOutViewSource}}"
SelectionChanged="groups_SelectionChanged"
SelectionMode="None" SelectionMode="None"
IsSynchronizedWithCurrentItem="False"> IsSynchronizedWithCurrentItem="False">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" ItemWidth="140" />
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.ItemTemplate> <GridView.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Vertical"> <TextBlock Width="100" Margin="5,0,0,0" Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
<TextBlock Width="100" Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</DataTemplate> </DataTemplate>
</GridView.ItemTemplate> </GridView.ItemTemplate>
<GridView.GroupStyle> <GridView.GroupStyle>
<GroupStyle HidesIfEmpty="True"> <GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate> <GroupStyle.HeaderTemplate>
<DataTemplate> <DataTemplate>
<Grid Background="LightGray" HorizontalAlignment="Left"> <StackPanel Orientation="Vertical" HorizontalAlignment="Left">
<TextBlock Text="{Binding Key}" Width="50" Margin="30" Foreground="{StaticResource MainColorBrush}" Style="{StaticResource HeaderTextBlockStyle}" TextAlignment="Center" /> <TextBlock Text="{Binding Key}" Width="50" Margin="20" Foreground="{StaticResource MainColorBrush}" Style="{StaticResource SubheaderTextBlockStyle}" TextAlignment="Center" />
</Grid> <Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
</StackPanel>
</DataTemplate> </DataTemplate>
</GroupStyle.HeaderTemplate> </GroupStyle.HeaderTemplate>
</GroupStyle> </GroupStyle>
@@ -183,37 +180,39 @@
<!-- Back button and page title --> <!-- Back button and page title -->
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}"> <Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}"/> <ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" <Button Grid.Column="0"
Command="{Binding GoBackCommand}" Command="{Binding GoBackCommand}"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuSize}" Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back" AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton" AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}"> Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" /> <SymbolIcon Symbol="Back" />
</Button> </Button>
<Grid Grid.Column="1" > <Button Grid.Column="1"
<Grid.ColumnDefinitions> Height="{StaticResource MenuHeight}"
<ColumnDefinition Width="60" /> Width="{StaticResource MenuWidth}"
<ColumnDefinition Width="*" /> Command="{Binding GoToParentCommand}"
</Grid.ColumnDefinitions> Style="{StaticResource NoBorderButtonStyle}">
<Grid.RowDefinitions> <SymbolIcon Symbol="Up" />
<RowDefinition Height="40" /> <ToolTipService.ToolTip>
<RowDefinition Height="20" /> <ToolTip Content="{Binding ParentGroupName}" />
</Grid.RowDefinitions> </ToolTipService.ToolTip>
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"> </Button>
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" /> <Viewbox Grid.Column="2" MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
</Viewbox> <userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}"> </Viewbox>
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" /> <Viewbox Grid.Column="2" MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
</Viewbox> <SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
<TextBox Grid.Column="1" Grid.Row="0" </Viewbox>
<TextBox Grid.Column="3"
x:Uid="GroupTitle" x:Uid="GroupTitle"
x:Name="TitleTextBox" x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}" Text="{Binding Title, Mode=TwoWay}"
@@ -223,22 +222,17 @@
FontWeight="Light" FontWeight="Light"
TextWrapping="NoWrap" TextWrapping="NoWrap"
VerticalAlignment="Center"> VerticalAlignment="Center">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" /> <actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" /> <core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior> </core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" /> <core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior> </core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</TextBox> </TextBox>
<HyperlinkButton Grid.Column="1" Grid.Row="1" <userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="4"
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"
SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
SaveCommand="{Binding SaveCommand}" SaveCommand="{Binding SaveCommand}"
@@ -252,57 +246,24 @@
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl> </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> </Grid>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DragDropGroup"> <VisualStateGroup x:Name="PageLayout">
<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">
<VisualState x:Name="Small"> <VisualState x:Name="Small">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> <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> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
@@ -311,8 +272,8 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>

View File

@@ -1,8 +1,4 @@
using System; using Windows.UI.Xaml;
using System.Linq;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
using ModernKeePass.Application.Entry.Models; 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) private void GroupDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
{ {
VisualStateManager.GoToState(this, e.NewSize.Width < 800 ? "Small" : "Large", true); if (e.NewSize.Width <= 640)
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true); {
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 #endregion

View File

@@ -27,7 +27,7 @@
</TransitionCollection> </TransitionCollection>
</Grid.ChildrenTransitions> </Grid.ChildrenTransitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuGridLength}"/> <RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -44,7 +44,7 @@
<Button x:Name="BackButton" <Button x:Name="BackButton"
Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}"
Visibility="Collapsed" Visibility="Collapsed"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuWidth}"
AutomationProperties.Name="Back" AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton" AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" AutomationProperties.ItemType="Navigation Button"
@@ -53,7 +53,7 @@
</Button> </Button>
<TextBlock x:Name="TitleTextBox" Text="{Binding Name}" Grid.Column="1" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center" /> <TextBlock x:Name="TitleTextBox" Text="{Binding Name}" Grid.Column="1" FontSize="24" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid> </Grid>
<controls:ListViewWithDisable <controls:DisableListView
Grid.Column="0" Grid.Column="0"
Grid.Row="1" Grid.Row="1"
x:Name="MenuListView" x:Name="MenuListView"
@@ -62,15 +62,15 @@
ItemsSource="{Binding Source={StaticResource MenuItemsSource}}" ItemsSource="{Binding Source={StaticResource MenuItemsSource}}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}"> ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<controls:ListViewWithDisable.ItemTemplate> <controls:DisableListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding SymbolIcon}" /> <SymbolIcon Symbol="{Binding SymbolIcon}" />
<TextBlock Text="{Binding Title}" Margin="10,5,0,0" /> <TextBlock Text="{Binding Title}" Margin="10,5,0,0" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</controls:ListViewWithDisable.ItemTemplate> </controls:DisableListView.ItemTemplate>
<controls:ListViewWithDisable.GroupStyle> <controls:DisableListView.GroupStyle>
<GroupStyle HidesIfEmpty="True"> <GroupStyle HidesIfEmpty="True">
<GroupStyle.HeaderTemplate> <GroupStyle.HeaderTemplate>
<DataTemplate> <DataTemplate>
@@ -80,8 +80,8 @@
</DataTemplate> </DataTemplate>
</GroupStyle.HeaderTemplate> </GroupStyle.HeaderTemplate>
</GroupStyle> </GroupStyle>
</controls:ListViewWithDisable.GroupStyle> </controls:DisableListView.GroupStyle>
</controls:ListViewWithDisable> </controls:DisableListView>
<TextBlock x:Name="PageTitleTextBlock" Grid.Column="1" Grid.Row="0" FontSize="24" VerticalAlignment="Center" Margin="10,0,0,0" > <TextBlock x:Name="PageTitleTextBlock" Grid.Column="1" Grid.Row="0" FontSize="24" VerticalAlignment="Center" Margin="10,0,0,0" >
<Run Text="{Binding SelectedItem}" /> <Run Text="{Binding SelectedItem}" />
</TextBlock> </TextBlock>

View File

@@ -6,15 +6,14 @@
xmlns:viewModels="using:ModernKeePass.ViewModels" xmlns:viewModels="using:ModernKeePass.ViewModels"
x:Class="ModernKeePass.Views.AboutPage" x:Class="ModernKeePass.Views.AboutPage"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.DataContext> <Page.DataContext>
<viewModels:AboutVm/> <viewModels:AboutVm/>
</Page.DataContext> </Page.DataContext>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,0"> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,0">
<Run Text="{Binding Name}"/> <Run Text="{Binding Name}" />
<Run Text="version"/> <Run Text="version" />
<Run Text="{Binding Version}" /> <Run Text="{Binding Version}" />
</TextBlock> </TextBlock>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="30,0,0,0"> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="30,0,0,0">
@@ -23,7 +22,7 @@
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="30,0,0,0"> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="30,0,0,0">
<Run x:Uid="AboutHomepage" /> <Run x:Uid="AboutHomepage" />
<Hyperlink NavigateUri="https://wismna.github.io/ModernKeePass/"> <Hyperlink NavigateUri="https://wismna.github.io/ModernKeePass/">
<Run Text="https://wismna.github.io/ModernKeePass/"/> <Run Text="https://wismna.github.io/ModernKeePass/" />
</Hyperlink> </Hyperlink>
</TextBlock> </TextBlock>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,0"> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="10,0,0,0">

View File

@@ -20,10 +20,10 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Text="Import" Style="{StaticResource SubheaderTextBlockStyle}" /> <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" > <StackPanel Grid.Column="1" Grid.Row="1" >
<TextBlock Text="Format" Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,0,10" /> <TextBlock Text="Format" Style="{StaticResource BodyTextBlockStyle}" Margin="0,0,0,10" />
<ComboBox Style="{StaticResource MainColorComboBox}"> <ComboBox>
<ComboBoxItem>CSV</ComboBoxItem> <ComboBoxItem>CSV</ComboBoxItem>
</ComboBox> </ComboBox>
</StackPanel> </StackPanel>
@@ -32,6 +32,6 @@
<RadioButton GroupName="ImportDestination" Content="New database" /> <RadioButton GroupName="ImportDestination" Content="New database" />
<RadioButton GroupName="ImportDestination" Content="Currently opened database" /> <RadioButton GroupName="ImportDestination" Content="Currently opened database" />
</StackPanel> </StackPanel>
<Button Grid.Column="3" Grid.Row="1" Content="Import" Style="{StaticResource MainColorButton}" /> <Button Grid.Column="3" Grid.Row="1" Content="Import" />
</Grid> </Grid>
</Page> </Page>

View File

@@ -1,8 +1,4 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
@@ -15,21 +11,5 @@ namespace ModernKeePass.Views
{ {
InitializeComponent(); 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;
}
} }
} }

View File

@@ -13,7 +13,7 @@
</Page.Resources> </Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <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" /> <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}}"> <Border HorizontalAlignment="Left" BorderThickness="1" BorderBrush="AliceBlue" Width="550" Visibility="{Binding IsFileSelected, Converter={StaticResource BooleanToVisibilityConverter}}">
<StackPanel Margin="25,0,25,0"> <StackPanel Margin="25,0,25,0">

View File

@@ -1,11 +1,4 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
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
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
@@ -14,28 +7,9 @@ namespace ModernKeePass.Views
/// </summary> /// </summary>
public sealed partial class NewDatabasePage public sealed partial class NewDatabasePage
{ {
private NewVm Model => (NewVm)DataContext;
public NewDatabasePage() public NewDatabasePage()
{ {
InitializeComponent(); 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;
}
} }
} }

View File

@@ -13,7 +13,7 @@
</Page.Resources> </Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <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" /> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="OpenBrowseDesc" />
<!--<HyperlinkButton x:Uid="OpenUrlButton" IsEnabled="False" Foreground="{StaticResource MainColor}" Style="{StaticResource MainColorHyperlinkButton}" /> <!--<HyperlinkButton x:Uid="OpenUrlButton" IsEnabled="False" Foreground="{StaticResource MainColor}" Style="{StaticResource MainColorHyperlinkButton}" />
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="OpenUrlDesc" />--> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="OpenUrlDesc" />-->

View File

@@ -1,8 +1,4 @@
using System; using Windows.UI.Xaml.Navigation;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Navigation;
using ModernKeePass.Domain.Dtos; using ModernKeePass.Domain.Dtos;
using ModernKeePass.ViewModels; using ModernKeePass.ViewModels;
@@ -26,31 +22,7 @@ namespace ModernKeePass.Views
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
var file = e.Parameter as FileInfo; var file = e.Parameter as FileInfo;
if (file != null) Model.SetFileInformation(file);
{
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;
} }
} }
} }

View File

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

View File

@@ -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 // 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 namespace ModernKeePass.Views
{ {
/// <summary> /// <summary>
@@ -9,8 +7,6 @@ namespace ModernKeePass.Views
/// </summary> /// </summary>
public sealed partial class RecentDatabasesPage public sealed partial class RecentDatabasesPage
{ {
private RecentVm Model => (RecentVm)DataContext;
public RecentDatabasesPage() public RecentDatabasesPage()
{ {
InitializeComponent(); InitializeComponent();

View File

@@ -10,7 +10,7 @@
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<HyperlinkButton x:Uid="SaveButton" Command="{Binding SaveCommand}" /> <HyperlinkButton x:Uid="SaveButton" Command="{Binding SaveCommand}" />
<TextBlock x:Uid="SaveDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" /> <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" /> <TextBlock x:Uid="SaveAsDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" />
<HyperlinkButton x:Uid="CloseButton" Command="{Binding CloseCommand}" /> <HyperlinkButton x:Uid="CloseButton" Command="{Binding CloseCommand}" />
<TextBlock x:Uid="CloseDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" /> <TextBlock x:Uid="CloseDesc" Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" />

View File

@@ -1,10 +1,4 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
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
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
@@ -13,24 +7,9 @@ namespace ModernKeePass.Views
/// </summary> /// </summary>
public sealed partial class SaveDatabasePage public sealed partial class SaveDatabasePage
{ {
public SaveVm Model => (SaveVm)DataContext;
public SaveDatabasePage() public SaveDatabasePage()
{ {
InitializeComponent(); 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);
}
} }
} }

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,17 @@
<Page <Page
x:Class="ModernKeePass.Views.SettingsSavePage" x:Class="ModernKeePass.Views.SettingsGeneralPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=SettingsSave}"> DataContext="{Binding Source={StaticResource Locator}, Path=General}">
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock x:Uid="SettingsSaveDatabaseSuspendTitle" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,10"/> <TextBlock x:Uid="SettingsSaveDatabaseSuspendTitle" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,10"/>
<TextBlock x:Uid="SettingsSaveDatabaseSuspendDesc" TextWrapping="WrapWholeWords" 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}" /> <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> </StackPanel>
</Page> </Page>

View File

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

View File

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

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