24 Commits
V1.18 ... V1.20

Author SHA1 Message Date
Geoffroy BONNEVILLE
80a255aa6f Restored stackpanel in breadcrumb 2020-06-26 15:21:06 +02:00
Geoffroy BONNEVILLE
1f06bf3ba7 Root group up button disabled
Going back to home when opening a file from explorer no longer displays open file
Inverted Breadcrumb order to avoid reordering items
Update release notes
Code cleanup
2020-06-26 01:16:44 +02:00
Geoffroy BONNEVILLE
7dcd5a4a57 Removed Breadcrumb service
Breadcrumb control handles breadcrumb status
Layout improvements
Added the ability to delete an entry from the group menu
2020-06-10 13:38:04 +02:00
Geoffroy BONNEVILLE
c62ed584dc New Breadcrumb user control
New Breadcrumb service
WIP icons and back button behavior
2020-06-09 20:18:17 +02:00
d6b17fe696 WIP Return of the Breadcrumb
Entry Title field added as part of the entry page
Code cleanup
2020-06-08 19:17:11 +02:00
f477828628 Changed some observable collections types for HamburgerMenu binding (issue with WinRT 8.1)
Restored CollectionViewSource for recent (issue with WinRT 8.1)
Forbid horizontal scrolling in Main Menu
Fixed an incorrect SetupFocusAction target object binding
2020-06-08 14:16:54 +02:00
Geoffroy BONNEVILLE
4a0bc1cb86 Add ARM debug target 2020-06-08 11:52:12 +02:00
Geoffroy BONNEVILLE
4e7aca5517 CSV Import command created 2020-06-05 19:08:29 +02:00
Geoffroy BONNEVILLE
1f04f941c2 Corrected issue in color picker user control when changing history
Use of commands instead of events in code-behind
Some refactoring
2020-06-04 16:29:26 +02:00
Geoffroy BONNEVILLE
5c1dfa1b0e Update version to 1.20
Clicking new group button while collapsed will expand the menu
Use of space freed by hidden hamburger menu
2020-06-04 15:31:38 +02:00
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
89 changed files with 1771 additions and 1061 deletions

View File

@@ -36,6 +36,16 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|ARM' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>ARM</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
@@ -81,14 +91,15 @@
<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\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="Common\Models\BreadcrumbItem.cs" />
<Compile Include="Entry\Commands\AddAttachment\AddAttachmentCommand.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\DeleteAttachment\DeleteAttachmentCommand.cs" />
@@ -104,6 +115,8 @@
<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" />
<Compile Include="Group\Queries\SearchEntries\SearchEntriesQuery.cs" /> <Compile Include="Group\Queries\SearchEntries\SearchEntriesQuery.cs" />
<Compile Include="Import\Commands\ImportFromCsv\ImportFromCsvCommand.cs" />
<Compile Include="Import\Commands\ImportFromCsv\ImportFromCsvCommandValidator.cs" />
<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" />
@@ -118,7 +131,7 @@
<Compile Include="Parameters\Queries\GetKeyDerivations\GetKeyDerivationsQuery.cs" /> <Compile Include="Parameters\Queries\GetKeyDerivations\GetKeyDerivationsQuery.cs" />
<Compile Include="Database\Commands\CloseDatabase\CloseDatabaseCommand.cs" /> <Compile Include="Database\Commands\CloseDatabase\CloseDatabaseCommand.cs" />
<Compile Include="Database\Commands\CreateDatabase\CreateDatabaseCommand.cs" /> <Compile Include="Database\Commands\CreateDatabase\CreateDatabaseCommand.cs" />
<Compile Include="Database\Commands\CreateDatabase\CreateDatabaseQueryValidator.cs" /> <Compile Include="Database\Commands\CreateDatabase\CreateDatabaseCommandValidator.cs" />
<Compile Include="Database\Commands\SaveDatabase\SaveDatabaseCommand.cs" /> <Compile Include="Database\Commands\SaveDatabase\SaveDatabaseCommand.cs" />
<Compile Include="Database\Commands\UpdateCredentials\UpdateCredentialsCommand.cs" /> <Compile Include="Database\Commands\UpdateCredentials\UpdateCredentialsCommand.cs" />
<Compile Include="Database\Models\DatabaseVm.cs" /> <Compile Include="Database\Models\DatabaseVm.cs" />

View File

@@ -6,5 +6,6 @@ namespace ModernKeePass.Application.Common.Interfaces
{ {
Task<string> Protect(string value); Task<string> Protect(string value);
Task<string> UnProtect(string value); Task<string> UnProtect(string value);
byte[] Random(uint length);
} }
} }

View File

@@ -35,7 +35,7 @@ namespace ModernKeePass.Application.Common.Interfaces
EntryEntity GetEntry(string id); EntryEntity GetEntry(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);
void UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected); Task UpdateEntry(string entryId, string fieldName, object fieldValue, bool isProtected);
void DeleteField(string entryId, string fieldName); void DeleteField(string entryId, string fieldName);
Task RemoveEntry(string parentGroupId, string entryId); Task RemoveEntry(string parentGroupId, string entryId);
EntryEntity CreateEntry(string parentGroupId); EntryEntity CreateEntry(string parentGroupId);

View File

@@ -10,6 +10,7 @@ namespace ModernKeePass.Application.Common.Interfaces
Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent); Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent);
Task<byte[]> ReadBinaryFile(string path); Task<byte[]> ReadBinaryFile(string path);
Task<IList<string>> ReadTextFile(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

@@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace ModernKeePass.Application.Common.Interfaces
{
public interface IImportFormat
{
List<Dictionary<string, string>> Import(IList<string> fileContents);
}
}

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

@@ -0,0 +1,12 @@
using ModernKeePass.Domain.Enums;
namespace ModernKeePass.Application.Common.Models
{
public class BreadcrumbItem
{
public string Path { get; set; }
public string Name { get; set; }
public Icon Icon { get; set; }
}
}

View File

@@ -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", false); await _database.UpdateEntry(sample1.Id, EntryFieldName.Title, "Sample Entry", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username", false); await _database.UpdateEntry(sample1.Id, EntryFieldName.UserName, "Username", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password", true); await _database.UpdateEntry(sample1.Id, EntryFieldName.Password, "Password", true);
_database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/", false); await _database.UpdateEntry(sample1.Id, EntryFieldName.Url, "https://keepass.info/", false);
_database.UpdateEntry(sample1.Id, EntryFieldName.Notes, "You may safely delete this sample", false); 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", false); await _database.UpdateEntry(sample2.Id, EntryFieldName.Title, "Sample Entry #2", false);
_database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321", false); await _database.UpdateEntry(sample2.Id, EntryFieldName.UserName, "Michael321", false);
_database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345", true); await _database.UpdateEntry(sample2.Id, EntryFieldName.Password, "12345", true);
_database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html", false); await _database.UpdateEntry(sample2.Id, EntryFieldName.Url, "https://keepass.info/help/kb/testform.html", false);
} }
} }
} }

View File

@@ -2,9 +2,9 @@
namespace ModernKeePass.Application.Database.Commands.CreateDatabase namespace ModernKeePass.Application.Database.Commands.CreateDatabase
{ {
public class CreateDatabaseQueryValidator : AbstractValidator<CreateDatabaseCommand> public class CreateDatabaseCommandValidator : AbstractValidator<CreateDatabaseCommand>
{ {
public CreateDatabaseQueryValidator() public CreateDatabaseCommandValidator()
{ {
RuleFor(v => v.FilePath) RuleFor(v => v.FilePath)
.NotNull() .NotNull()

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,6 +31,7 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
try try
{ {
var timeToSave = Stopwatch.StartNew();
if (!string.IsNullOrEmpty(message.FilePath)) if (!string.IsNullOrEmpty(message.FilePath))
{ {
_database.FileAccessToken = message.FilePath; _database.FileAccessToken = message.FilePath;
@@ -40,6 +45,13 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
// Transactional write to file // Transactional write to file
await _file.WriteBinaryContentsToFile(_database.FileAccessToken, contents); 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

@@ -1,4 +1,5 @@
using MediatR; using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Exceptions; using ModernKeePass.Domain.Exceptions;
@@ -11,7 +12,7 @@ namespace ModernKeePass.Application.Entry.Commands.UpsertField
public object FieldValue { get; set; } public object FieldValue { get; set; }
public bool IsProtected { get; set; } = true; public bool IsProtected { get; set; } = true;
public class UpsertFieldCommandHandler : IRequestHandler<UpsertFieldCommand> public class UpsertFieldCommandHandler : IAsyncRequestHandler<UpsertFieldCommand>
{ {
private readonly IDatabaseProxy _database; private readonly IDatabaseProxy _database;
@@ -20,11 +21,11 @@ namespace ModernKeePass.Application.Entry.Commands.UpsertField
_database = database; _database = database;
} }
public void Handle(UpsertFieldCommand message) public async Task Handle(UpsertFieldCommand message)
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
_database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue, message.IsProtected); await _database.UpdateEntry(message.EntryId, message.FieldName, message.FieldValue, message.IsProtected);
} }
} }
} }

View File

@@ -39,29 +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.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.Fields.FirstOrDefault(f => .ForMember(d => d.Title, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Title, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Title, IsProtected = true } )) f.Name.Equals(EntryFieldName.Title, StringComparison.Ordinal)) ?? new FieldEntity { Name = EntryFieldName.Title, IsProtected = false } ))
.ForMember(d => d.Username, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => .ForMember(d => d.Username, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.UserName, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.UserName, IsProtected = true } )) f.Name.Equals(EntryFieldName.UserName, StringComparison.Ordinal)) ?? new FieldEntity { Name = EntryFieldName.UserName, IsProtected = false } ))
.ForMember(d => d.Password, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => .ForMember(d => d.Password, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Password, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Password, IsProtected = true } )) f.Name.Equals(EntryFieldName.Password, StringComparison.Ordinal)) ?? new FieldEntity { Name = EntryFieldName.Password, IsProtected = true } ))
.ForMember(d => d.Url, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => .ForMember(d => d.Url, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Url, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Url, IsProtected = true } )) f.Name.Equals(EntryFieldName.Url, StringComparison.Ordinal)) ?? new FieldEntity { Name = EntryFieldName.Url, IsProtected = false } ))
.ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f => .ForMember(d => d.Notes, opts => opts.MapFrom(s => s.Fields.FirstOrDefault(f =>
f.Name.Equals(EntryFieldName.Notes, StringComparison.OrdinalIgnoreCase)) ?? new FieldEntity { Name = EntryFieldName.Notes, IsProtected = true } )) f.Name.Equals(EntryFieldName.Notes, StringComparison.Ordinal)) ?? new FieldEntity { Name = EntryFieldName.Notes, IsProtected = false } ))
.ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s => .ForMember(d => d.AdditionalFields, opts => opts.MapFrom(s =>
s.Fields.Where(f => !EntryFieldName.StandardFieldNames.Contains(f.Name, StringComparer.OrdinalIgnoreCase)))) 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))
.ForMember(d => d.Attachments, opts => opts.MapFrom(s => s.Attachments));
} }
} }
} }

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

@@ -26,7 +26,7 @@ namespace ModernKeePass.Application.Group.Commands.MoveGroup
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
await _database.MoveGroup(message.ParentGroup.Id, message.Group.Id, message.Index); await _database.MoveGroup(message.ParentGroup.Id, message.Group.Id, message.Index);
message.ParentGroup.SubGroups.Insert(message.Index, message.Group); message.ParentGroup.Groups.Insert(message.Index, message.Group);
} }
} }
} }

View File

@@ -24,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,50 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MediatR;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.Application.Import.Commands.ImportFromCsv
{
public class ImportFromCsvCommand : IRequest
{
public string FilePath { get; set; }
public string DestinationGroupId { get; set; }
public bool HasHeaderRow { get; set; }
public char Delimiter { get; set; } = ';';
public Dictionary<int, string> FieldMappings { get; set; }
public class CreateDatabaseCommandHandler : IAsyncRequestHandler<ImportFromCsvCommand>
{
private readonly IDatabaseProxy _database;
private readonly IFileProxy _file;
public CreateDatabaseCommandHandler(IDatabaseProxy database, IFileProxy file)
{
_database = database;
_file = file;
}
public async Task Handle(ImportFromCsvCommand message)
{
if (!_database.IsOpen) throw new DatabaseClosedException();
var fileContents = await _file.ReadTextFile(message.FilePath);
for (var index = message.HasHeaderRow ? 1 : 0; index < fileContents.Count; index++)
{
var line = fileContents[index];
var fields = line.Split(message.Delimiter);
var entry = _database.CreateEntry(message.DestinationGroupId);
for (var i = 0; i < fields.Length; i++)
{
var fieldMapping = message.FieldMappings[i];
await _database.UpdateEntry(entry.Id, fieldMapping, fields[i], fieldMapping == EntryFieldName.Password);
}
}
}
}
}
}

View File

@@ -0,0 +1,17 @@
using FluentValidation;
namespace ModernKeePass.Application.Import.Commands.ImportFromCsv
{
public class ImportFromCsvCommandValidator : AbstractValidator<ImportFromCsvCommand>
{
public ImportFromCsvCommandValidator()
{
RuleFor(v => v.FilePath)
.NotNull()
.NotEmpty();
RuleFor(v => v.DestinationGroupId)
.NotNull()
.NotEmpty();
}
}
}

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

@@ -36,6 +36,16 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|ARM' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>ARM</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>

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

@@ -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

@@ -7,7 +7,7 @@ namespace ModernKeePass.Domain.Enums
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);
@@ -15,7 +15,7 @@ namespace ModernKeePass.Domain.Enums
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[] public static IEnumerable<string> StandardFieldNames => new[]
{ {
Title, Title,
UserName, UserName,

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

@@ -1,31 +0,0 @@
using System.Collections.Generic;
using ModernKeePass.Application.Common.Interfaces;
namespace ModernKeePass.Infrastructure.File
{
public class CsvImportFormat: IImportFormat
{
private const bool HasHeaderRow = true;
private const char Delimiter = ';';
private const char LineDelimiter = '\n';
public List<Dictionary<string, string>> Import(IList<string> fileContents)
{
var parsedResult = new List<Dictionary<string, string>>();
foreach (var line in fileContents)
{
var fields = line.Split(Delimiter);
var recordItem = new Dictionary<string, string>();
var i = 0;
foreach (var field in fields)
{
recordItem.Add(i.ToString(), field);
i++;
}
parsedResult.Add(recordItem);
}
return parsedResult;
}
}
}

View File

@@ -44,6 +44,16 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget> <PlatformTarget>x64</PlatformTarget>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|ARM' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\ARM\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<PlatformTarget>ARM</PlatformTarget>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x86' ">
<DebugType>pdbonly</DebugType> <DebugType>pdbonly</DebugType>
<Optimize>true</Optimize> <Optimize>true</Optimize>
@@ -78,8 +88,6 @@
<ItemGroup> <ItemGroup>
<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="KeePass\EntryFieldMapper.cs" />
<Compile Include="KeePass\MappingProfiles.cs" /> <Compile Include="KeePass\MappingProfiles.cs" />
<Compile Include="KeePass\IconMapper.cs" /> <Compile Include="KeePass\IconMapper.cs" />
<Compile Include="KeePass\KeePassDatabaseSettingsProxy.cs" /> <Compile Include="KeePass\KeePassDatabaseSettingsProxy.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

@@ -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?
@@ -94,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)
@@ -240,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, bool isProtected) 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(isProtected, fieldValue.ToString()));
break;
case EntryFieldName.HasExpirationDate: case EntryFieldName.HasExpirationDate:
pwEntry.Expires = (bool)fieldValue; pwEntry.Expires = (bool)fieldValue;
break; break;
@@ -269,7 +264,9 @@ namespace ModernKeePass.Infrastructure.KeePass
pwEntry.ForegroundColor = (Color)fieldValue; pwEntry.ForegroundColor = (Color)fieldValue;
break; break;
default: default:
pwEntry.Strings.Set(fieldName, new ProtectedString(isProtected, fieldValue.ToString())); 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; break;
} }
} }
@@ -394,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

@@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using AutoMapper; using AutoMapper;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Entities; using ModernKeePass.Domain.Entities;
using ModernKeePassLib; using ModernKeePassLib;
using ModernKeePassLib.Security; using ModernKeePassLib.Security;
@@ -14,32 +15,42 @@ namespace ModernKeePass.Infrastructure.KeePass
{ {
CreateMap<KeyValuePair<string, ProtectedString>, FieldEntity>() CreateMap<KeyValuePair<string, ProtectedString>, FieldEntity>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key)) .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.Key))
.ForMember(dest => dest.Value, opt => opt.MapFrom(src => src.Value.ReadString())) .ForMember(dest => dest.Value, opt => opt.ResolveUsing<ProtectedStringResolver>())
.ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected)); .ForMember(dest => dest.IsProtected, opt => opt.MapFrom(src => src.Value.IsProtected));
CreateMap<PwEntry, EntryEntity>() CreateMap<PwEntry, EntryEntity>()
.ForMember(dest => dest.ParentId, opt => opt.MapFrom(src => src.ParentGroup.Uuid.ToHexString())) .ForMember(dest => dest.ParentGroupId, 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.Id, opt => opt.MapFrom(src => src.Uuid.ToHexString()))
.ForMember(dest => dest.Fields, opt => opt.MapFrom(src => src.Strings)) .ForMember(dest => dest.Fields, opt => opt.MapFrom(src => src.Strings))
.ForMember(dest => dest.ForegroundColor, opt => opt.MapFrom(src => src.ForegroundColor))
.ForMember(dest => dest.BackgroundColor, opt => opt.MapFrom(src => src.BackgroundColor))
.ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime))) .ForMember(dest => dest.ExpirationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.ExpiryTime)))
.ForMember(dest => dest.HasExpirationDate, opt => opt.MapFrom(src => src.Expires)) .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.Icon, opt => opt.MapFrom(src => IconMapper.MapPwIconToIcon(src.IconId)))
.ForMember(dest => dest.LastModificationDate, opt => opt.MapFrom(src => new DateTimeOffset(src.LastModificationTime))) .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()) ))); .ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Binaries.Select(b => new KeyValuePair<string, byte[]> (b.Key, b.Value.ReadData()) )));
CreateMap<PwGroup, GroupEntity>() CreateMap<PwGroup, GroupEntity>()
.ForMember(d => d.ParentId, opts => opts.MapFrom(s => s.ParentGroup.Uuid.ToHexString())) .ForMember(d => d.ParentGroupId, 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.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.Icon, opts => opts.MapFrom(s => IconMapper.MapPwIconToIcon(s.IconId)))
.ForMember(d => d.LastModificationDate, opts => opts.MapFrom(s => s.LastModificationTime)) .ForMember(d => d.ModificationDate, 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); .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

@@ -74,6 +74,24 @@ namespace ModernKeePass.Infrastructure.UWP
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

@@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Security.Cryptography; using Windows.Security.Cryptography;
using Windows.Security.Cryptography.DataProtection; using Windows.Security.Cryptography.DataProtection;
@@ -10,24 +11,28 @@ namespace ModernKeePass.Infrastructure.UWP
{ {
public async Task<string> Protect(string value) public async Task<string> Protect(string value)
{ {
if (string.IsNullOrEmpty(value)) return value;
// Create a DataProtectionProvider object for the specified descriptor. // Create a DataProtectionProvider object for the specified descriptor.
var provider = new DataProtectionProvider(); var provider = new DataProtectionProvider("LOCAL=user");
// Encode the plaintext input message to a buffer. // Encode the plaintext input message to a buffer.
var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8); var buffMsg = CryptographicBuffer.ConvertStringToBinary(value, BinaryStringEncoding.Utf8);
// Encrypt the message. // Encrypt the message.
var buffProtected = await provider.ProtectAsync(buffMsg); var buffProtected = await provider.ProtectAsync(buffMsg).AsTask().ConfigureAwait(false);
// Encode buffer to Base64 // Encode buffer to Base64
var stringProtected = CryptographicBuffer.EncodeToBase64String(buffProtected); var protectedValue = CryptographicBuffer.EncodeToBase64String(buffProtected);
// Return the encrypted string. // Return the encrypted string.
return stringProtected; return protectedValue;
} }
public async Task<string> UnProtect(string value) public async Task<string> UnProtect(string value)
{ {
if (string.IsNullOrEmpty(value)) return value;
// Create a DataProtectionProvider object. // Create a DataProtectionProvider object.
var provider = new DataProtectionProvider(); var provider = new DataProtectionProvider();
@@ -35,13 +40,18 @@ namespace ModernKeePass.Infrastructure.UWP
var buffProtected = CryptographicBuffer.DecodeFromBase64String(value); var buffProtected = CryptographicBuffer.DecodeFromBase64String(value);
// Decrypt the protected message specified on input. // Decrypt the protected message specified on input.
var buffUnprotected = await provider.UnprotectAsync(buffProtected); var buffUnprotected = await provider.UnprotectAsync(buffProtected).AsTask().ConfigureAwait(false);
// Convert the unprotected message from an IBuffer object to a string. // Convert the unprotected message from an IBuffer object to a string.
var strClearText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffUnprotected); var clearText = CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, buffUnprotected);
// Return the plaintext string. // Return the plaintext string.
return strClearText; return clearText;
}
public byte[] Random(uint length)
{
return CryptographicBuffer.GenerateRandom(length).ToArray();
} }
} }
} }

View File

@@ -2,7 +2,7 @@
"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.45.1", "ModernKeePassLib": "2.45.1",
"NETStandard.Library": "2.0.3" "NETStandard.Library": "2.0.3"
}, },

View File

@@ -38,10 +38,11 @@ namespace ModernKeePass
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 INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file; private readonly IFileProxy _file;
private readonly IMessenger _messenger; 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,6 +64,7 @@ namespace ModernKeePass
_settings = Services.GetService<ISettingsProxy>(); _settings = Services.GetService<ISettingsProxy>();
_navigation = Services.GetService<INavigationService>(); _navigation = Services.GetService<INavigationService>();
_notification = Services.GetService<INotificationService>(); _notification = Services.GetService<INotificationService>();
_log = Services.GetService<ILogger>();
_hockey = Services.GetService<IHockeyClient>(); _hockey = Services.GetService<IHockeyClient>();
_file = Services.GetService<IFileProxy>(); _file = Services.GetService<IFileProxy>();
_messenger = Services.GetService<IMessenger>(); _messenger = Services.GetService<IMessenger>();
@@ -93,8 +95,7 @@ namespace ModernKeePass
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{ {
_hockey.TrackException(e.Exception); _log.LogError(e.Exception);
_hockey.Flush();
} }
/// <summary> /// <summary>
@@ -209,8 +210,7 @@ namespace ModernKeePass
} }
catch (Exception ex) catch (Exception ex)
{ {
_hockey.TrackException(ex); await _log.LogError(ex);
_hockey.Flush();
} }
finally finally
{ {

View File

@@ -4,8 +4,10 @@ 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
{ {
@@ -37,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.18.0.12" /> <Identity Name="wismna.ModernKeePass" Publisher="CN=0719A91A-C322-4EE0-A257-E60733EECF06" Version="1.20.0.12" />
<Properties> <Properties>
<DisplayName>ModernKeePass</DisplayName> <DisplayName>ModernKeePass</DisplayName>
<PublisherDisplayName>wismna</PublisherDisplayName> <PublisherDisplayName>wismna</PublisherDisplayName>

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>
@@ -299,7 +296,7 @@
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"
Grid.Column="1" Grid.Column="1"
Style="{StaticResource ActionButtonStyle}" Style="{StaticResource ActionButtonStyle}"
Content="{TemplateBinding ButtonSymbol}" Content="{TemplateBinding ButtonContent}"
Command="{TemplateBinding ButtonCommand}" Command="{TemplateBinding ButtonCommand}"
CommandParameter="{TemplateBinding ButtonCommandParameter}"> CommandParameter="{TemplateBinding ButtonCommandParameter}">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>

View File

@@ -26,13 +26,12 @@ 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.Dtos;
using ModernKeePass.Domain.Exceptions; using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Domain.Interfaces;
using ModernKeePass.Extensions; using ModernKeePass.Extensions;
using ModernKeePass.Models; using ModernKeePass.Models;
using ModernKeePass.ViewModels.ListItems; using ModernKeePass.ViewModels.ListItems;
@@ -41,73 +40,22 @@ namespace ModernKeePass.ViewModels
{ {
public class EntryDetailVm : ViewModelBase public class EntryDetailVm : ViewModelBase
{ {
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 double PasswordLength
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.PasswordLength, 25); }
set
{
_settings.PutSetting(Constants.Settings.PasswordGenerationOptions.PasswordLength, value);
RaisePropertyChanged(nameof(PasswordLength));
}
}
public bool UpperCasePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.UpperCasePattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.UpperCasePattern, value); }
}
public bool LowerCasePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.LowerCasePattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.LowerCasePattern, value); }
}
public bool DigitsPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.DigitsPattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.DigitsPattern, value); }
}
public bool MinusPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.MinusPattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.MinusPattern, value); }
}
public bool UnderscorePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.UnderscorePattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.UnderscorePattern, value); }
}
public bool SpacePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.SpacePattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.SpacePattern, value); }
}
public bool SpecialPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.SpecialPattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.SpecialPattern, value); }
}
public bool BracketsPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.BracketsPattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.BracketsPattern, value); }
}
public string CustomChars { get; set; } = string.Empty;
public string Id => SelectedItem.Id; public string Id => _current.Id;
public string ParentGroupName => _parent.Title; public GroupVm Parent { get; private set; }
public bool IsRecycleOnDelete public bool IsRecycleOnDelete
{ {
get get
{ {
var database = Database; var database = Database;
return database.IsRecycleBinEnabled && _parent.Id != database.RecycleBinId; return database.IsRecycleBinEnabled && Parent.Id != database.RecycleBinId;
} }
} }
public ObservableCollection<EntryVm> History { get; private set; } public ObservableCollection<IEntityVm> History { get; private set; }
public ObservableCollection<EntryFieldVm> AdditionalFields { get; private set; } public ObservableCollection<EntryFieldVm> AdditionalFields { get; private set; }
public ObservableCollection<Attachment> Attachments { get; private set; } public ObservableCollection<Attachment> Attachments { get; private set; }
@@ -116,31 +64,6 @@ namespace ModernKeePass.ViewModels
/// </summary> /// </summary>
public bool IsCurrentEntry => SelectedIndex == 0; public bool IsCurrentEntry => SelectedIndex == 0;
public EntryVm SelectedItem
{
get { return _selectedItem; }
set
{
Set(() => SelectedItem, ref _selectedItem, value, true);
if (value != null)
{
AdditionalFields =
new ObservableCollection<EntryFieldVm>(
SelectedItem.AdditionalFields.Select(f => new EntryFieldVm(f.Name, f.Value, f.IsProtected)));
Attachments = new ObservableCollection<Attachment>(SelectedItem.Attachments.Select(f => new Attachment
{
Name = f.Key,
Content = f.Value
}));
Attachments.CollectionChanged += (sender, args) =>
{
UpdateDirtyStatus(true);
};
RaisePropertyChanged(string.Empty);
}
}
}
public int SelectedIndex public int SelectedIndex
{ {
get { return _selectedIndex; } get { return _selectedIndex; }
@@ -162,150 +85,136 @@ namespace ModernKeePass.ViewModels
} }
} }
public string Title public string Title
{ {
get { return SelectedItem.Title.Value; } get { return _current.Title.Value; }
set set
{ {
SelectedItem.Title.Value = value; _current.Title.Value = value;
SetFieldValue(nameof(Title), value, false).Wait(); SetFieldValue(_current.Title.Name, value, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public string UserName public string UserName
{ {
get { return SelectedItem.Username.Value; } get { return _current.Username.Value; }
set set
{ {
SelectedItem.Username.Value = value; _current.Username.Value = value;
SetFieldValue(nameof(UserName), value, false).Wait(); SetFieldValue(_current.Username.Name, value, false).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(UserName)); RaisePropertyChanged(nameof(UserName));
} }
} }
public string Password public string Password
{ {
get { return SelectedItem.Password.Value; } get { return _cryptography.UnProtect(_current.Password.Value).GetAwaiter().GetResult(); }
set set
{ {
SelectedItem.Password.Value = value; var protectedPassword = _cryptography.Protect(value).ConfigureAwait(false).GetAwaiter().GetResult();
SetFieldValue(nameof(Password), value, true).Wait(); _current.Password.Value = protectedPassword;
SetFieldValue(_current.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.Value; } get { return _current.Url.Value; }
set set
{ {
SelectedItem.Url.Value = value; _current.Url.Value = value;
SetFieldValue(nameof(Url), value, false).Wait(); SetFieldValue(_current.Url.Name, value, false).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(Url)); RaisePropertyChanged(nameof(Url));
} }
} }
public string Notes public string Notes
{ {
get { return SelectedItem.Notes.Value; } get { return _current.Notes.Value; }
set set
{ {
SelectedItem.Notes.Value = value; _current.Notes.Value = value;
SetFieldValue(nameof(Notes), value, false).Wait(); SetFieldValue(_current.Notes.Name, value, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public Symbol Icon public Symbol Icon
{ {
get { return (Symbol)Enum.Parse(typeof(Symbol), SelectedItem.Icon.ToString()); } get { return (Symbol)Enum.Parse(typeof(Symbol), _current.Icon.ToString()); }
set set
{ {
SelectedItem.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString()); _current.Icon = (Icon)Enum.Parse(typeof(Icon), value.ToString());
SetFieldValue(nameof(Icon), SelectedItem.Icon, false).Wait(); SetFieldValue(EntryFieldName.Icon, _current.Icon, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public DateTimeOffset ExpiryDate public DateTimeOffset ExpiryDate
{ {
get { return SelectedItem.ExpirationDate; } get { return _current.ExpirationDate; }
set set
{ {
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
SelectedItem.ExpirationDate = value.Date; _current.ExpirationDate = value.Date;
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate, false).Wait(); SetFieldValue(EntryFieldName.ExpirationDate, _current.ExpirationDate, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public TimeSpan ExpiryTime public TimeSpan ExpiryTime
{ {
get { return SelectedItem.ExpirationDate.TimeOfDay; } get { return _current.ExpirationDate.TimeOfDay; }
set set
{ {
if (!HasExpirationDate) return; if (!HasExpirationDate) return;
SelectedItem.ExpirationDate = SelectedItem.ExpirationDate.Date.Add(value); _current.ExpirationDate = _current.ExpirationDate.Date.Add(value);
SetFieldValue("ExpirationDate", SelectedItem.ExpirationDate, false).Wait(); SetFieldValue(EntryFieldName.ExpirationDate, _current.ExpirationDate, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public bool HasExpirationDate public bool HasExpirationDate
{ {
get { return SelectedItem.HasExpirationDate; } get { return _current.HasExpirationDate; }
set set
{ {
SelectedItem.HasExpirationDate = value; _current.HasExpirationDate = value;
SetFieldValue(nameof(HasExpirationDate), value, false).Wait(); SetFieldValue(EntryFieldName.HasExpirationDate, value, false).ConfigureAwait(false).GetAwaiter();
RaisePropertyChanged(nameof(HasExpirationDate)); RaisePropertyChanged(nameof(HasExpirationDate));
} }
} }
public SolidColorBrush BackgroundColor public SolidColorBrush BackgroundColor
{ {
get { return SelectedItem?.BackgroundColor.ToSolidColorBrush(); } get { return _current?.BackgroundColor.ToSolidColorBrush(); }
set set
{ {
SelectedItem.BackgroundColor = value.ToColor(); _current.BackgroundColor = value.ToColor();
SetFieldValue(nameof(BackgroundColor), SelectedItem.BackgroundColor, false).Wait(); SetFieldValue(EntryFieldName.BackgroundColor, _current.BackgroundColor, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public SolidColorBrush ForegroundColor public SolidColorBrush ForegroundColor
{ {
get { return SelectedItem?.ForegroundColor.ToSolidColorBrush(); } get { return _current?.ForegroundColor.ToSolidColorBrush(); }
set set
{ {
SelectedItem.ForegroundColor = value.ToColor(); _current.ForegroundColor = value.ToColor();
SetFieldValue(nameof(ForegroundColor), SelectedItem.ForegroundColor, false).Wait(); SetFieldValue(EntryFieldName.ForegroundColor, _current.ForegroundColor, false).ConfigureAwait(false).GetAwaiter();
} }
} }
public bool IsEditMode
{
get { return IsCurrentEntry && _isEditMode; }
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 AddAdditionalField { get; }
public RelayCommand GoToParentCommand { get; set; } public RelayCommand<EntryFieldVm> DeleteAdditionalField { get; }
public RelayCommand AddAdditionalField { get; set; } public RelayCommand<Attachment> OpenAttachmentCommand { get; }
public RelayCommand<EntryFieldVm> DeleteAdditionalField { get; set; } public RelayCommand AddAttachmentCommand { get; }
public RelayCommand<Attachment> OpenAttachmentCommand { get; set; } public RelayCommand<Attachment> DeleteAttachmentCommand { get; }
public RelayCommand AddAttachmentCommand { get; set; } public RelayCommand<EntryVm> SetCurrentEntryCommand { get; }
public RelayCommand<Attachment> DeleteAttachmentCommand { get; set; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
@@ -315,16 +224,14 @@ namespace ModernKeePass.ViewModels
private readonly IDialogService _dialog; private readonly IDialogService _dialog;
private readonly INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file; private readonly IFileProxy _file;
private readonly ISettingsProxy _settings; private readonly ICryptographyClient _cryptography;
private GroupVm _parent; private readonly IDateTime _dateTime;
private EntryVm _selectedItem; private EntryVm _current;
private int _selectedIndex; private int _selectedIndex;
private int _additionalFieldSelectedIndex = -1; private int _additionalFieldSelectedIndex = -1;
private bool _isEditMode;
private bool _isRevealPassword;
private bool _isDirty; private bool _isDirty;
public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification, IFileProxy file, ISettingsProxy settings) public EntryDetailVm(IMediator mediator, INavigationService navigation, IResourceProxy resource, IDialogService dialog, INotificationService notification, IFileProxy file, ICryptographyClient cryptography, IDateTime dateTime)
{ {
_mediator = mediator; _mediator = mediator;
_navigation = navigation; _navigation = navigation;
@@ -332,33 +239,34 @@ namespace ModernKeePass.ViewModels
_dialog = dialog; _dialog = dialog;
_notification = notification; _notification = notification;
_file = file; _file = file;
_settings = settings; _cryptography = cryptography;
_dateTime = dateTime;
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());
GoToParentCommand = new RelayCommand(() => GoToGroup(_parent.Id));
AddAdditionalField = new RelayCommand(AddField, () => IsCurrentEntry); AddAdditionalField = new RelayCommand(AddField, () => IsCurrentEntry);
DeleteAdditionalField = new RelayCommand<EntryFieldVm>(async field => await DeleteField(field), field => field != null && IsCurrentEntry); DeleteAdditionalField = new RelayCommand<EntryFieldVm>(async field => await DeleteField(field), field => field != null && IsCurrentEntry);
OpenAttachmentCommand = new RelayCommand<Attachment>(async attachment => await OpenAttachment(attachment)); OpenAttachmentCommand = new RelayCommand<Attachment>(async attachment => await OpenAttachment(attachment));
AddAttachmentCommand = new RelayCommand(async () => await AddAttachment(), () => IsCurrentEntry); AddAttachmentCommand = new RelayCommand(async () => await AddAttachment(), () => IsCurrentEntry);
DeleteAttachmentCommand = new RelayCommand<Attachment>(async attachment => await DeleteAttachment(attachment), _ => IsCurrentEntry); DeleteAttachmentCommand = new RelayCommand<Attachment>(async attachment => await DeleteAttachment(attachment), _ => IsCurrentEntry);
SetCurrentEntryCommand = new RelayCommand<EntryVm>(SetCurrentEntry, entry => entry != null);
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged()); MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
MessengerInstance.Register<EntryFieldValueChangedMessage>(this, async message => await SetFieldValue(message.FieldName, message.FieldValue, message.IsProtected)); 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<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; SelectedIndex = 0;
SelectedItem = await _mediator.Send(new GetEntryQuery { Id = entryId }); _current = await _mediator.Send(new GetEntryQuery { Id = entryId });
_parent = await _mediator.Send(new GetGroupQuery { Id = SelectedItem.ParentGroupId }); SetCurrentEntry(_current);
History = new ObservableCollection<EntryVm> { SelectedItem }; Parent = await _mediator.Send(new GetGroupQuery { Id = _current.ParentGroupId });
foreach (var entry in SelectedItem.History.Skip(1)) History = new ObservableCollection<IEntityVm> { _current };
foreach (var entry in _current.History.Skip(1))
{ {
History.Add(entry); History.Add(entry);
} }
@@ -369,26 +277,9 @@ namespace ModernKeePass.ViewModels
}; };
} }
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
});
}
public async Task AddHistory() public async Task AddHistory()
{ {
if (_isDirty) await _mediator.Send(new AddHistoryCommand { Entry = History[0] }); if (_isDirty && Database.IsOpen) await _mediator.Send(new AddHistoryCommand { Entry = History[0] as EntryVm });
} }
public void GoToGroup(string groupId) public void GoToGroup(string groupId)
@@ -399,7 +290,7 @@ namespace ModernKeePass.ViewModels
private async Task Move(string destination) private async Task Move(string destination)
{ {
await _mediator.Send(new AddEntryCommand { ParentGroupId = destination, EntryId = Id }); await _mediator.Send(new AddEntryCommand { ParentGroupId = destination, EntryId = Id });
await _mediator.Send(new RemoveEntryCommand { ParentGroupId = _parent.Id, EntryId = Id }); await _mediator.Send(new RemoveEntryCommand { ParentGroupId = Parent.Id, EntryId = Id });
GoToGroup(destination); GoToGroup(destination);
} }
@@ -417,7 +308,7 @@ namespace ModernKeePass.ViewModels
private void AddField() private void AddField()
{ {
AdditionalFields.Add(new EntryFieldVm(string.Empty, string.Empty, false)); AdditionalFields.Add(new EntryFieldVm(_cryptography));
AdditionalFieldSelectedIndex = AdditionalFields.Count - 1; AdditionalFieldSelectedIndex = AdditionalFields.Count - 1;
} }
@@ -461,7 +352,7 @@ namespace ModernKeePass.ViewModels
_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] as EntryVm, HistoryIndex = History.Count - SelectedIndex - 1 });
History.RemoveAt(SelectedIndex); History.RemoveAt(SelectedIndex);
}); });
} }
@@ -469,8 +360,8 @@ namespace ModernKeePass.ViewModels
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] as EntryVm, HistoryIndex = History.Count - SelectedIndex - 1 });
History.Insert(0, SelectedItem); History.Insert(0, _current);
} }
private async Task SaveChanges() private async Task SaveChanges()
@@ -492,7 +383,7 @@ namespace ModernKeePass.ViewModels
await _mediator.Send(new DeleteEntryCommand await _mediator.Send(new DeleteEntryCommand
{ {
EntryId = Id, EntryId = Id,
ParentGroupId = SelectedItem.ParentGroupId, ParentGroupId = _current.ParentGroupId,
RecycleBinName = _resource.GetResourceValue("RecycleBinTitle") RecycleBinName = _resource.GetResourceValue("RecycleBinTitle")
}); });
_isDirty = false; _isDirty = false;
@@ -515,13 +406,13 @@ namespace ModernKeePass.ViewModels
var fileInfo = await _file.OpenFile(string.Empty, Domain.Common.Constants.Extensions.Any, false); var fileInfo = await _file.OpenFile(string.Empty, Domain.Common.Constants.Extensions.Any, false);
if (fileInfo == null) return; if (fileInfo == null) return;
var contents = await _file.ReadBinaryFile(fileInfo.Id); var contents = await _file.ReadBinaryFile(fileInfo.Id);
await _mediator.Send(new AddAttachmentCommand { Entry = SelectedItem, AttachmentName = fileInfo.Name, AttachmentContent = contents }); await _mediator.Send(new AddAttachmentCommand { Entry = _current, AttachmentName = fileInfo.Name, AttachmentContent = contents });
Attachments.Add(new Attachment { Name = fileInfo.Name, Content = contents }); Attachments.Add(new Attachment { Name = fileInfo.Name, Content = contents });
} }
private async Task DeleteAttachment(Attachment attachment) private async Task DeleteAttachment(Attachment attachment)
{ {
await _mediator.Send(new DeleteAttachmentCommand { Entry = SelectedItem, AttachmentName = attachment.Name }); await _mediator.Send(new DeleteAttachmentCommand { Entry = _current, AttachmentName = attachment.Name });
Attachments.Remove(attachment); Attachments.Remove(attachment);
} }
@@ -530,5 +421,29 @@ namespace ModernKeePass.ViewModels
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
_isDirty = isDirty; _isDirty = isDirty;
} }
private void SetCurrentEntry(EntryVm entry)
{
_current = entry;
AdditionalFields =
new ObservableCollection<EntryFieldVm>(
entry.AdditionalFields.Select(f =>
{
var field = new EntryFieldVm(_cryptography);
field.Initialize(f.Name, f.Value, f.IsProtected);
return field;
}));
Attachments = new ObservableCollection<Attachment>(entry.Attachments.Select(f => new Attachment
{
Name = f.Key,
Content = f.Value
}));
Attachments.CollectionChanged += (sender, args) =>
{
UpdateDirtyStatus(true);
};
RaisePropertyChanged(string.Empty);
}
} }
} }

View File

@@ -18,6 +18,7 @@ using ModernKeePass.Application.Group.Commands.AddEntry;
using ModernKeePass.Application.Group.Commands.AddGroup; using ModernKeePass.Application.Group.Commands.AddGroup;
using ModernKeePass.Application.Group.Commands.CreateEntry; using ModernKeePass.Application.Group.Commands.CreateEntry;
using ModernKeePass.Application.Group.Commands.CreateGroup; using ModernKeePass.Application.Group.Commands.CreateGroup;
using ModernKeePass.Application.Group.Commands.DeleteEntry;
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.MoveGroup;
@@ -38,10 +39,12 @@ namespace ModernKeePass.ViewModels
{ {
public ObservableCollection<EntryVm> Entries { get; private set; } public ObservableCollection<EntryVm> Entries { get; private set; }
public ObservableCollection<GroupVm> Groups { get; private set; } public ObservableCollection<IEntityVm> Groups { get; private set; }
public bool IsNotRoot => Database.RootGroupId != _group.Id; public bool IsNotRoot => Database.RootGroupId != _group.Id;
public GroupVm Parent { get; private set; }
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.Value ?? string.Empty).ToUpper().FirstOrDefault() into grp group e by (e.Title.Value ?? string.Empty).ToUpper().FirstOrDefault() into grp
orderby grp.Key orderby grp.Key
@@ -80,11 +83,11 @@ namespace ModernKeePass.ViewModels
} }
} }
public string ParentGroupName => _parent?.Title; public string ParentGroupName => Parent == null ? Database.Name : Parent.Title;
public bool IsRecycleOnDelete => Database.IsRecycleBinEnabled && !IsInRecycleBin; public bool IsRecycleOnDelete => Database.IsRecycleBinEnabled && !IsInRecycleBin;
public bool IsInRecycleBin => _parent != null && _parent.Id == Database.RecycleBinId; public bool IsInRecycleBin => Parent != null && Parent.Id == Database.RecycleBinId;
public RelayCommand SaveCommand { get; } public RelayCommand SaveCommand { get; }
public RelayCommand SortEntriesCommand { get; } public RelayCommand SortEntriesCommand { get; }
@@ -92,9 +95,12 @@ namespace ModernKeePass.ViewModels
public RelayCommand<string> MoveCommand { get; } public RelayCommand<string> MoveCommand { get; }
public RelayCommand CreateEntryCommand { get; } public RelayCommand CreateEntryCommand { get; }
public RelayCommand<string> CreateGroupCommand { get; } public RelayCommand<string> CreateGroupCommand { get; }
public RelayCommand DeleteCommand { get; set; } public RelayCommand DeleteCommand { get; }
public RelayCommand GoBackCommand { get; set; } public RelayCommand GoBackCommand { get; }
public RelayCommand GoToParentCommand { get; set; } public RelayCommand GoToParentCommand { get; }
public RelayCommand<GroupVm> GoToGroupCommand { get; }
public RelayCommand<EntryVm> GoToEntryCommand { get; }
public RelayCommand<EntryVm> DeleteEntryCommand { get; }
private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); private DatabaseVm Database => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
@@ -104,7 +110,6 @@ namespace ModernKeePass.ViewModels
private readonly IDialogService _dialog; private readonly IDialogService _dialog;
private readonly INotificationService _notification; private readonly INotificationService _notification;
private GroupVm _group; private GroupVm _group;
private GroupVm _parent;
private bool _isEditMode; private bool _isEditMode;
private EntryVm _reorderedEntry; private EntryVm _reorderedEntry;
private GroupVm _reorderedGroup; private GroupVm _reorderedGroup;
@@ -125,7 +130,10 @@ namespace ModernKeePass.ViewModels
CreateGroupCommand = new RelayCommand<string>(async newGroupName => await AddNewGroup(newGroupName), _ => !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);
GoToGroupCommand = new RelayCommand<GroupVm>(group => GoToGroup(group.Id), group => group != null);
GoToEntryCommand = new RelayCommand<EntryVm>(entry => GoToEntry(entry.Id), entry => entry != null);
DeleteEntryCommand = new RelayCommand<EntryVm>(async entry => await AskForDeleteEntry(entry), entry => entry != null);
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged()); MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
} }
@@ -135,12 +143,13 @@ namespace ModernKeePass.ViewModels
_group = await _mediator.Send(new GetGroupQuery { Id = groupId }); _group = await _mediator.Send(new GetGroupQuery { Id = groupId });
if (!string.IsNullOrEmpty(_group.ParentGroupId)) if (!string.IsNullOrEmpty(_group.ParentGroupId))
{ {
_parent = await _mediator.Send(new GetGroupQuery { Id = _group.ParentGroupId }); Parent = await _mediator.Send(new GetGroupQuery {Id = _group.ParentGroupId});
} }
else Parent = null;
Entries = new ObservableCollection<EntryVm>(_group.Entries); Entries = new ObservableCollection<EntryVm>(_group.Entries);
Entries.CollectionChanged += Entries_CollectionChanged; Entries.CollectionChanged += Entries_CollectionChanged;
Groups = new ObservableCollection<GroupVm>(_group.SubGroups); Groups = new ObservableCollection<IEntityVm>(_group.Groups);
Groups.CollectionChanged += Groups_CollectionChanged; Groups.CollectionChanged += Groups_CollectionChanged;
} }
@@ -176,7 +185,7 @@ namespace ModernKeePass.ViewModels
public async Task Move(string destinationId) public async Task Move(string destinationId)
{ {
await _mediator.Send(new AddGroupCommand {ParentGroupId = destinationId, GroupId = Id }); await _mediator.Send(new AddGroupCommand {ParentGroupId = destinationId, GroupId = Id });
await _mediator.Send(new RemoveGroupCommand {ParentGroupId = _parent.Id, GroupId = Id }); await _mediator.Send(new RemoveGroupCommand {ParentGroupId = Parent.Id, GroupId = Id });
GoToGroup(destinationId); GoToGroup(destinationId);
} }
@@ -222,7 +231,7 @@ namespace ModernKeePass.ViewModels
{ {
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
var oldIndex = e.OldStartingIndex; var oldIndex = e.OldStartingIndex;
_reorderedGroup = _group.SubGroups[oldIndex]; _reorderedGroup = _group.Groups[oldIndex];
break; break;
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
if (_reorderedGroup == null) if (_reorderedGroup == null)
@@ -251,7 +260,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<IEntityVm>(_group.Groups);
RaisePropertyChanged(nameof(Groups)); RaisePropertyChanged(nameof(Groups));
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
} }
@@ -285,7 +294,39 @@ namespace ModernKeePass.ViewModels
ParentGroupId = _group.ParentGroupId, ParentGroupId = _group.ParentGroupId,
RecycleBinName = _resource.GetResourceValue("RecycleBinTitle") RecycleBinName = _resource.GetResourceValue("RecycleBinTitle")
}); });
_navigation.GoBack(); _navigation.GoBack();
} }
private async Task AskForDeleteEntry(EntryVm entry)
{
if (IsRecycleOnDelete)
{
await DeleteEntry(entry);
_notification.Show(_resource.GetResourceValue("EntityDeleting"), string.Format(_resource.GetResourceValue("EntryRecycled"), entry.Title));
}
else
{
await _dialog.ShowMessage(
string.Format(_resource.GetResourceValue("EntryDeletingConfirmation"), entry.Title),
_resource.GetResourceValue("EntityDeleting"),
_resource.GetResourceValue("EntityDeleteActionButton"),
_resource.GetResourceValue("EntityDeleteCancelButton"),
async isOk =>
{
if (isOk) await DeleteEntry(entry);
});
}
}
private async Task DeleteEntry(EntryVm entry)
{
await _mediator.Send(new DeleteEntryCommand
{
EntryId = entry.Id,
ParentGroupId = Id,
RecycleBinName = _resource.GetResourceValue("RecycleBinTitle")
});
Entries.Remove(entry);
}
} }
} }

View File

@@ -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>
@@ -373,14 +32,8 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl
x:Name="HamburgerMenu"
x:Uid="HistoryLeftListView"
ItemsSource="{Binding History}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Hub Padding="0"> <Hub x:Name="Hub" Padding="0">
<Hub.Resources> <Hub.Resources>
<Style TargetType="TextBlock" x:Key="EntryTextBlockStyle"> <Style TargetType="TextBlock" x:Key="EntryTextBlockStyle">
<Setter Property="Margin" Value="0,20,0,0"/> <Setter Property="Margin" Value="0,20,0,0"/>
@@ -391,15 +44,24 @@
<HubSection x:Uid="EntryHubMain"> <HubSection x:Uid="EntryHubMain">
<DataTemplate> <DataTemplate>
<ScrollViewer VerticalScrollBarVisibility="Auto"> <ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel Margin="20,0,0,20" MinWidth="400"> <StackPanel Width="350" Margin="0,0,25,0">
<StackPanel.Resources> <StackPanel.Resources>
<Style TargetType="CheckBox"> <Style TargetType="CheckBox">
<Setter Property="Margin" Value="0,20,0,0"/> <Setter Property="Margin" Value="0,20,0,0"/>
<Setter Property="FontSize" Value="18"/> <Setter Property="FontSize" Value="18"/>
</Style> </Style>
</StackPanel.Resources> </StackPanel.Resources>
<TextBlock x:Uid="EntryTitle" Style="{StaticResource EntryTextBlockStyle}" />
<local:TextBoxWithButton x:Uid="TitleTextBox" Text="{Binding Title, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonContent="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Title}" />
<actions:ToastAction x:Uid="ToastCopyTitle" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<TextBlock x:Uid="EntryLogin" Style="{StaticResource EntryTextBlockStyle}" /> <TextBlock x:Uid="EntryLogin" Style="{StaticResource EntryTextBlockStyle}" />
<local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}"> <local:TextBoxWithButton x:Uid="LoginTextBox" Text="{Binding UserName, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonContent="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick"> <core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding UserName}" /> <actions:ClipboardAction Text="{Binding UserName}" />
@@ -408,19 +70,9 @@
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</local:TextBoxWithButton> </local:TextBoxWithButton>
<TextBlock x:Uid="EntryPassword" Style="{StaticResource EntryTextBlockStyle}" /> <TextBlock x:Uid="EntryPassword" Style="{StaticResource EntryTextBlockStyle}" />
<local:PasswordBoxWithButton Password="{Binding Password, Mode=TwoWay}" IsPasswordRevealEnabled="True" Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource PasswordBoxWithButtonStyle}" IsEnabled="{Binding IsCurrentEntry}" ButtonSymbol="&#xE15E;" /> <controls:PasswordGenerationBox Password="{Binding Password, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
<local:TextBoxWithButton x:Uid="PasswordTextBox" Text="{Binding Password, Mode=TwoWay}" Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE16F;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Password}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="{Binding Title}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</local:TextBoxWithButton>
<ProgressBar Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Maximum="128" Width="350" HorizontalAlignment="Left" Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" />
<CheckBox x:Uid="EntryShowPassword" HorizontalAlignment="Left" Margin="-3,0,0,0" IsChecked="{Binding IsRevealPassword, Mode=TwoWay}" />
<TextBlock Text="URL" Style="{StaticResource EntryTextBlockStyle}"/> <TextBlock Text="URL" Style="{StaticResource EntryTextBlockStyle}"/>
<local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonSymbol="&#xE111;" IsEnabled="{Binding IsCurrentEntry}"> <local:TextBoxWithButton x:Uid="UrlTextBox" Text="{Binding Url, Mode=TwoWay}" Style="{StaticResource TextBoxWithButtonStyle}" ButtonContent="&#xE111;" IsEnabled="{Binding IsCurrentEntry}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick"> <core:EventTriggerBehavior EventName="ButtonClick">
<actions:NavigateToUrlAction Url="{Binding Url}" /> <actions:NavigateToUrlAction Url="{Binding Url}" />
@@ -428,8 +80,8 @@
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</local:TextBoxWithButton> </local:TextBoxWithButton>
<TextBlock x:Uid="EntryNotes" Style="{StaticResource EntryTextBlockStyle}" /> <TextBlock x:Uid="EntryNotes" Style="{StaticResource EntryTextBlockStyle}" />
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" /> <TextBox TextWrapping="Wrap" Text="{Binding Notes, Mode=TwoWay}" Width="350" Height="200" AcceptsReturn="True" IsSpellCheckEnabled="True" IsEnabled="{Binding IsCurrentEntry}" />
<CheckBox x:Uid="EntryExpirationDate" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" /> <CheckBox x:Uid="EntryExpirationDate" Margin="-3,0,0,0" IsChecked="{Binding HasExpirationDate, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" />
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -471,11 +123,11 @@
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<TextBlock x:Uid="EntryIcon" Style="{StaticResource EntryTextBlockStyle}" /> <TextBlock x:Uid="EntryIcon" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:SymbolPickerUserControl SelectedSymbol="{Binding Icon, Mode=TwoWay}" HorizontalAlignment="Left" IsEnabled="{Binding IsCurrentEntry}" /> <controls:SymbolPickerUserControl SelectedSymbol="{Binding Icon, Mode=TwoWay}" HorizontalAlignment="Left" IsEnabled="{Binding IsCurrentEntry}" />
<TextBlock x:Uid="EntryBackgroundColor" Style="{StaticResource EntryTextBlockStyle}" /> <TextBlock x:Uid="EntryBackgroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" /> <controls:ColorPickerUserControl SelectedColor="{Binding BackgroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" />
<TextBlock x:Uid="EntryForegroundColor" Style="{StaticResource EntryTextBlockStyle}" /> <TextBlock x:Uid="EntryForegroundColor" Style="{StaticResource EntryTextBlockStyle}" />
<userControls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" /> <controls:ColorPickerUserControl SelectedColor="{Binding ForegroundColor, Mode=TwoWay}" IsEnabled="{Binding IsCurrentEntry}" Width="250" />
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</HubSection> </HubSection>
@@ -520,8 +172,7 @@
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Vertical" Margin="5"> <StackPanel Orientation="Vertical" Margin="5">
<TextBlock Text="{Binding Name}" Style="{StaticResource EntryTextBlockStyle}" FontWeight="SemiBold" /> <TextBlock Text="{Binding Name}" Style="{StaticResource EntryTextBlockStyle}" FontWeight="SemiBold" />
<TextBlock HorizontalAlignment="Left" MaxLines="3" FontSize="12" Margin="2,0,2,5" Text="*****" Visibility="{Binding IsProtected, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource EntryTextBlockStyle}"/> <TextBlock HorizontalAlignment="Left" MaxLines="3" FontSize="12" Margin="2,0,2,5" Text="{Binding DisplayValue}" Style="{StaticResource EntryTextBlockStyle}"/>
<TextBlock HorizontalAlignment="Left" MaxLines="3" FontSize="12" Margin="2,0,2,5" Text="{Binding Value}" Visibility="{Binding IsProtected, Converter={StaticResource InverseBooleanToVisibilityConverter}}" Style="{StaticResource EntryTextBlockStyle}"/>
</StackPanel> </StackPanel>
</DataTemplate> </DataTemplate>
</local:SelectableTemplateListView.ItemTemplate> </local:SelectableTemplateListView.ItemTemplate>
@@ -561,62 +212,28 @@
</HubSection> </HubSection>
</Hub> </Hub>
</Grid> </Grid>
<controls:HamburgerMenuUserControl
Grid.Column="0"
x:Name="HamburgerMenu"
x:Uid="HistoryLeftListView"
ItemsSource="{Binding History}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding SetCurrentEntryCommand}" CommandParameter="{Binding SelectedItem, ElementName=HamburgerMenu}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</controls:HamburgerMenuUserControl>
</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 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" <controls:BreadcrumbUserControl x:Name="Breadcrumb" Group="{Binding Parent}" />
Command="{Binding GoBackCommand}" <controls:TopMenuUserControl
Height="{StaticResource MenuHeight}" x:Name="TopMenu" Grid.Column="1"
Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" />
</Button>
<Button Grid.Column="1"
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
Command="{Binding GoToParentCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Up" />
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox Grid.Column="2" MaxHeight="200">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox Grid.Column="3"
x:Uid="EntryTitle"
x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}"
Background="Transparent"
IsHitTestVisible="{Binding IsEditMode}"
FontSize="20"
FontWeight="Light"
TextWrapping="NoWrap"
VerticalAlignment="Center">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
<userControls:TopMenuUserControl
x:Name="TopMenu" Grid.Column="4"
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}}"
SaveCommand="{Binding SaveCommand}" SaveCommand="{Binding SaveCommand}"
@@ -628,7 +245,32 @@
<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 x:Name="PageLayout">
<VisualState x:Name="Small">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Hub" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="-30,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Medium">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Hub" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Hub" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid> </Grid>
</Page> </Page>

View File

@@ -28,8 +28,6 @@ namespace ModernKeePass.Views
if (args != null) if (args != null)
{ {
await Model.Initialize(args.Id); await Model.Initialize(args.Id);
Model.IsEditMode = args.IsNew;
if (args.IsNew) await Model.GeneratePassword();
} }
} }
@@ -47,18 +45,21 @@ namespace ModernKeePass.Views
VisualStateManager.GoToState(this, "Small", true); VisualStateManager.GoToState(this, "Small", true);
VisualStateManager.GoToState(TopMenu, "Collapsed", true); VisualStateManager.GoToState(TopMenu, "Collapsed", true);
VisualStateManager.GoToState(HamburgerMenu, "Hidden", true); VisualStateManager.GoToState(HamburgerMenu, "Hidden", true);
VisualStateManager.GoToState(Breadcrumb, "Small", true);
} }
else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008) else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008)
{ {
VisualStateManager.GoToState(this, "Medium", true); VisualStateManager.GoToState(this, "Medium", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true); VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true); VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
VisualStateManager.GoToState(Breadcrumb, "Medium", true);
} }
else else
{ {
VisualStateManager.GoToState(this, "Large", true); VisualStateManager.GoToState(this, "Large", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true); VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true); VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
VisualStateManager.GoToState(Breadcrumb, "Large", true);
} }
} }
} }

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"
@@ -15,14 +15,10 @@
<Page.Resources> <Page.Resources>
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/> <converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
<converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/> <converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/>
</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" />
@@ -44,14 +40,6 @@
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl
x:Name="HamburgerMenu"
x:Uid="GroupsLeftListView"
ItemsSource="{Binding Groups}"
CanDragItems="{Binding IsEditMode}"
SelectionChanged="groups_SelectionChanged"
ActionButtonCommand="{Binding CreateGroupCommand}"
IsButtonVisible="Visible" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" />
@@ -74,16 +62,15 @@
</StackPanel> </StackPanel>
</HyperlinkButton> </HyperlinkButton>
<SemanticZoom Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" ViewChangeStarted="SemanticZoom_ViewChangeStarted" ScrollViewer.HorizontalScrollBarVisibility="Visible"> <SemanticZoom x:Name="SemanticZoom" Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1" ViewChangeStarted="SemanticZoom_ViewChangeStarted" ScrollViewer.HorizontalScrollBarVisibility="Visible">
<SemanticZoom.ZoomedInView> <SemanticZoom.ZoomedInView>
<!-- 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"
SelectionChanged="entries_SelectionChanged"
IsSynchronizedWithCurrentItem="False" IsSynchronizedWithCurrentItem="False"
BorderBrush="{StaticResource ListViewItemSelectedBackgroundThemeBrush}" BorderBrush="{StaticResource ListViewItemSelectedBackgroundThemeBrush}"
AllowDrop="True" AllowDrop="True"
@@ -91,8 +78,11 @@
CanDragItems="{Binding IsEditMode}"> CanDragItems="{Binding IsEditMode}">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<actions:SetupFocusAction TargetObject="{Binding}" /> <actions:SetupFocusAction TargetObject="{Binding ElementName=GridView}" />
</core:DataTriggerBehavior> </core:DataTriggerBehavior>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding GoToEntryCommand}" CommandParameter="{Binding SelectedItem, ElementName=GridView}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
<GridView.ItemTemplate> <GridView.ItemTemplate>
<DataTemplate> <DataTemplate>
@@ -128,7 +118,7 @@
<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>
@@ -140,6 +130,7 @@
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</MenuFlyoutItem> </MenuFlyoutItem>
<MenuFlyoutItem x:Uid="EntryItemDelete" Command="{Binding DataContext.DeleteEntryCommand, ElementName=Page}" CommandParameter="{Binding}" />
</MenuFlyout> </MenuFlyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
@@ -151,23 +142,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>
@@ -176,64 +170,32 @@
</SemanticZoom.ZoomedOutView> </SemanticZoom.ZoomedOutView>
</SemanticZoom> </SemanticZoom>
</Grid> </Grid>
<userControls:HamburgerMenuUserControl
Grid.Column="0"
x:Name="HamburgerMenu"
x:Uid="GroupsLeftListView"
HeaderLabel="{Binding Title}"
ItemsSource="{Binding Groups}"
CanDragItems="{Binding IsEditMode}"
ActionButtonCommand="{Binding CreateGroupCommand}"
IsButtonVisible="Visible">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding GoToGroupCommand}" CommandParameter="{Binding SelectedItem, ElementName=HamburgerMenu}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</userControls:HamburgerMenuUserControl>
</Grid> </Grid>
<!-- 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 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" <userControls:BreadcrumbUserControl x:Name="Breadcrumb" Group="{Binding Parent}" />
Command="{Binding GoBackCommand}" <userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="1"
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" />
</Button>
<Button Grid.Column="1"
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
Command="{Binding GoToParentCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Up" />
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<Viewbox Grid.Column="2" MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox>
<Viewbox Grid.Column="2" MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox>
<TextBox Grid.Column="3"
x:Uid="GroupTitle"
x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}"
Background="Transparent"
IsHitTestVisible="{Binding IsEditMode}"
FontSize="20"
FontWeight="Light"
TextWrapping="NoWrap"
VerticalAlignment="Center">
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
<actions:SetupFocusAction TargetObject="{Binding ElementName=TitleTextBox}" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="2" />
</core:DataTriggerBehavior>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="False">
<core:ChangePropertyAction TargetObject="{Binding ElementName=TitleTextBox}" PropertyName="BorderThickness" Value="0" />
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</TextBox>
<userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="4"
SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"
EditButtonVisibility="Visible"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
SaveCommand="{Binding SaveCommand}" SaveCommand="{Binding SaveCommand}"
MoveCommand="{Binding MoveCommand}" MoveCommand="{Binding MoveCommand}"
@@ -246,7 +208,6 @@
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl> </userControls:TopMenuUserControl>
</Grid> </Grid>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PageLayout"> <VisualStateGroup x:Name="PageLayout">
@@ -258,6 +219,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="False"/> <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SemanticZoom" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="-60,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="Medium"> <VisualState x:Name="Medium">
@@ -265,6 +229,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="False"/> <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SemanticZoom" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="Large"> <VisualState x:Name="Large">
@@ -275,6 +242,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/> <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SemanticZoom" Storyboard.TargetProperty="Margin">
<DiscreteObjectKeyFrame KeyTime="0" Value="0,0,0,0"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>

View File

@@ -1,7 +1,6 @@
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 ModernKeePass.Application.Entry.Models;
using ModernKeePass.Models; using ModernKeePass.Models;
using ModernKeePass.ViewModels; using ModernKeePass.ViewModels;
@@ -38,33 +37,6 @@ namespace ModernKeePass.Views
#region Event Handlers #region Event Handlers
private void groups_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var listView = sender as ListView;
switch (listView?.SelectedIndex)
{
case -1:
return;
default:
var group = listView?.SelectedItem as Application.Group.Models.GroupVm;
Model.GoToGroup(group?.Id);
break;
}
}
private void entries_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
switch (GridView.SelectedIndex)
{
case -1:
return;
default:
var entry = GridView.SelectedItem as EntryVm;
Model.GoToEntry(entry?.Id);
break;
}
}
private void SemanticZoom_ViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e) private void SemanticZoom_ViewChangeStarted(object sender, SemanticZoomViewChangedEventArgs e)
{ {
// We need to synchronize the two lists (zoomed-in and zoomed-out) because the source is different // We need to synchronize the two lists (zoomed-in and zoomed-out) because the source is different
@@ -81,18 +53,21 @@ namespace ModernKeePass.Views
VisualStateManager.GoToState(this, "Small", true); VisualStateManager.GoToState(this, "Small", true);
VisualStateManager.GoToState(TopMenu, "Collapsed", true); VisualStateManager.GoToState(TopMenu, "Collapsed", true);
VisualStateManager.GoToState(HamburgerMenu, "Hidden", true); VisualStateManager.GoToState(HamburgerMenu, "Hidden", true);
VisualStateManager.GoToState(Breadcrumb, "Small", true);
} }
else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008) else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008)
{ {
VisualStateManager.GoToState(this, "Medium", true); VisualStateManager.GoToState(this, "Medium", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true); VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true); VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
VisualStateManager.GoToState(Breadcrumb, "Medium", true);
} }
else else
{ {
VisualStateManager.GoToState(this, "Large", true); VisualStateManager.GoToState(this, "Large", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true); VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Expanded", true); VisualStateManager.GoToState(HamburgerMenu, "Expanded", true);
VisualStateManager.GoToState(Breadcrumb, "Large", true);
} }
} }

View File

@@ -57,6 +57,7 @@
Grid.Column="0" Grid.Column="0"
Grid.Row="1" Grid.Row="1"
x:Name="MenuListView" x:Name="MenuListView"
ScrollViewer.VerticalScrollMode="Disabled"
SelectionChanged="ListView_SelectionChanged" SelectionChanged="ListView_SelectionChanged"
Background="{ThemeResource AppBarBackgroundThemeBrush}" Background="{ThemeResource AppBarBackgroundThemeBrush}"
ItemsSource="{Binding Source={StaticResource MenuItemsSource}}" ItemsSource="{Binding Source={StaticResource MenuItemsSource}}"

View File

@@ -40,7 +40,11 @@ namespace ModernKeePass.Views
protected override async void OnNavigatedTo(NavigationEventArgs e) protected override async void OnNavigatedTo(NavigationEventArgs e)
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
var file = e.Parameter as FileInfo; FileInfo file;
if (e.NavigationMode == NavigationMode.Back)
file = null;
else
file = e.Parameter as FileInfo;
await Model.Initialize(Frame, MenuFrame, file); await Model.Initialize(Frame, MenuFrame, file);
} }
} }

View File

@@ -1,5 +1,5 @@
<Page <Page
x:Class="ModernKeePass.Views.ImportExportPage" x:Class="ModernKeePass.Views.ImportPage"
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"

View File

@@ -5,9 +5,9 @@ namespace ModernKeePass.Views
/// <summary> /// <summary>
/// The import/export page. /// The import/export page.
/// </summary> /// </summary>
public sealed partial class ImportExportPage public sealed partial class ImportPage
{ {
public ImportExportPage() public ImportPage()
{ {
InitializeComponent(); InitializeComponent();
} }

View File

@@ -9,6 +9,11 @@
mc:Ignorable="d" mc:Ignorable="d"
DataContext="{Binding Source={StaticResource Locator}, Path=Recent}"> DataContext="{Binding Source={StaticResource Locator}, Path=Recent}">
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<CollectionViewSource
x:Name="RecentItemsSource"
Source="{Binding RecentItems}" />
</Grid.Resources>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="40" /> <RowDefinition Height="40" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />
@@ -21,7 +26,7 @@
</HyperlinkButton> </HyperlinkButton>
<controls:SelectableTemplateListView Grid.Row="1" <controls:SelectableTemplateListView Grid.Row="1"
SelectedIndex="{Binding SelectedIndex}" SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding RecentItems}" ItemsSource="{Binding Source={StaticResource RecentItemsSource}}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}"> ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<controls:SelectableTemplateListView.SelectedItemTemplate> <controls:SelectableTemplateListView.SelectedItemTemplate>
<DataTemplate> <DataTemplate>

View File

@@ -17,7 +17,7 @@ namespace ModernKeePass.Views
private void UIElement_OnKeyDown(object sender, KeyRoutedEventArgs e) 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)) if ((e.Key < VirtualKey.NumberPad0 || e.Key > VirtualKey.NumberPad9) && (e.Key < VirtualKey.Number0 || e.Key > VirtualKey.Number9))
{ {
e.Handled = true; e.Handled = true;
} }

View File

@@ -17,7 +17,7 @@ namespace ModernKeePass.Views.SettingsPageFrames
private void UIElement_OnKeyDown(object sender, KeyRoutedEventArgs e) 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)) if ((e.Key < VirtualKey.NumberPad0 || e.Key > VirtualKey.NumberPad9) && (e.Key < VirtualKey.Number0 || e.Key > VirtualKey.Number9))
{ {
e.Handled = true; e.Handled = true;
} }

View File

@@ -1,30 +1,15 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views.SettingsPageFrames namespace ModernKeePass.Views.SettingsPageFrames
{ {
/// <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 SettingsRecycleBinPage : Page public sealed partial class SettingsRecycleBinPage
{ {
public SettingsRecycleBinPage() public SettingsRecycleBinPage()
{ {
this.InitializeComponent(); InitializeComponent();
} }
} }
} }

View File

@@ -0,0 +1,117 @@
<UserControl
x:Class="ModernKeePass.Views.UserControls.BreadcrumbUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="using:ModernKeePass.Converters"
xmlns:templateSelectors="using:ModernKeePass.TemplateSelectors"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
mc:Ignorable="d">
<StackPanel x:Name="StackPanel" DataContext="{Binding Source={StaticResource Locator}, Path=Breadcrumb}" Orientation="Horizontal">
<StackPanel.Resources>
<converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/>
<DataTemplate x:Key="FirstItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,5,10,0" Text="{Binding Name}" FontStyle="Italic" HorizontalAlignment="Center" />
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=33}" Margin="0,3,0,0" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="OtherItemTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Margin="10,5,10,0" Text="&lt;" HorizontalAlignment="Center" />
<TextBlock Margin="10,5,10,0" Text="{Binding Name}" FontStyle="Italic" HorizontalAlignment="Center" />
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=33}" Margin="0,3,0,0" />
</StackPanel>
</DataTemplate>
</StackPanel.Resources>
<Button
Command="{Binding GoBackCommand}"
Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" />
</Button>
<Button
Height="{StaticResource MenuHeight}"
Command="{Binding GoUpCommand}"
Style="{StaticResource NoBorderButtonStyle}">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Up" />
<SymbolIcon x:Name="UpButtonIcon" Symbol="{Binding ParentGroupIcon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=33}" Margin="16,0,0,0" />
<TextBlock x:Name="UpButtonText" Margin="10,2,0,0" Text="{Binding ParentGroupName}" FontStyle="Italic" FontWeight="Normal" HorizontalAlignment="Center" />
</StackPanel>
<ToolTipService.ToolTip>
<ToolTip Content="{Binding ParentGroupName}" />
</ToolTipService.ToolTip>
</Button>
<ListView x:Name="ListView" ItemsSource="{Binding BreadcrumbItems}" ScrollViewer.HorizontalScrollMode="Auto">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel FlowDirection="RightToLeft" Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplateSelector>
<templateSelectors:FirstItemDataTemplateSelector FirstItem="{StaticResource FirstItemTemplate}" OtherItem="{StaticResource OtherItemTemplate}" />
</ListView.ItemTemplateSelector>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Margin" Value="0"/>
<Setter Property="Height" Value="{StaticResource MenuHeight}" />
</Style>
</ListView.ItemContainerStyle>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SelectionChanged">
<core:InvokeCommandAction Command="{Binding GoToCommand}" CommandParameter="{Binding SelectedIndex, ElementName=ListView}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ListView>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PageLayout">
<VisualState x:Name="Small">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UpButtonIcon" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UpButtonText" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Medium">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UpButtonIcon" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UpButtonText" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Large">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UpButtonIcon" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="UpButtonText" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,36 @@
using System.Linq;
using Windows.UI.Xaml;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.ViewModels;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace ModernKeePass.Views.UserControls
{
public sealed partial class BreadcrumbUserControl
{
public GroupVm Group
{
get { return (GroupVm)GetValue(GroupProperty); }
set { SetValue(GroupProperty, value); }
}
public static readonly DependencyProperty GroupProperty =
DependencyProperty.Register(
nameof(Group),
typeof(GroupVm),
typeof(BreadcrumbUserControl),
new PropertyMetadata(null, async (o, args) =>
{
var userControl = o as BreadcrumbUserControl;
var vm = userControl?.StackPanel.DataContext as BreadcrumbControlVm;
if (vm == null) return;
await vm.Initialize(args.NewValue as GroupVm).ConfigureAwait(false);
userControl?.ListView.ScrollIntoView(vm.BreadcrumbItems.Last());
}));
public BreadcrumbUserControl()
{
InitializeComponent();
}
}
}

View File

@@ -6,10 +6,13 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"> mc:Ignorable="d">
<ComboBox x:Name="ComboBox" <ComboBox x:Name="ComboBox"
ItemsSource="{Binding Colors, ElementName=UserControl}" DataContext="{Binding Source={StaticResource Locator}, Path=ColorPicker}"
SelectionChanged="Selector_OnSelectionChanged" ItemsSource="{Binding Colors}"
Loaded="ComboBox_Loaded" SelectedItem="{Binding SelectedItem}"
IsEnabled="{Binding IsEnabled, ElementName=UserControl}"> SelectedValue="{Binding SelectedColor, ElementName=UserControl}"
SelectedValuePath="ColorBrush"
SelectionChanged="ComboBox_OnSelectionChanged"
IsEnabled="{Binding IsEnabled, ElementName=UserControl}">
<ComboBox.ItemTemplate> <ComboBox.ItemTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,10,0"> <StackPanel Orientation="Horizontal" Margin="0,0,10,0">

View File

@@ -1,9 +1,8 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using System.Reflection;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media;
using ModernKeePass.ViewModels;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236 // The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
@@ -11,14 +10,6 @@ namespace ModernKeePass.Views.UserControls
{ {
public sealed partial class ColorPickerUserControl public sealed partial class ColorPickerUserControl
{ {
public struct Color
{
public string ColorName { get; set; }
public SolidColorBrush ColorBrush { get; set; }
}
public List<Color> Colors { get; }
public SolidColorBrush SelectedColor public SolidColorBrush SelectedColor
{ {
get { return (SolidColorBrush)GetValue(SelectedColorProperty); } get { return (SolidColorBrush)GetValue(SelectedColorProperty); }
@@ -29,33 +20,22 @@ namespace ModernKeePass.Views.UserControls
nameof(SelectedColor), nameof(SelectedColor),
typeof(SolidColorBrush), typeof(SolidColorBrush),
typeof(ColorPickerUserControl), typeof(ColorPickerUserControl),
new PropertyMetadata(new SolidColorBrush(), (o, args) => { })); new PropertyMetadata(new SolidColorBrush(), (o, args) =>
{
var colorPickerUserControl = o as ColorPickerUserControl;
var vm = colorPickerUserControl?.ComboBox.DataContext as ColorPickerControlVm;
vm?.Initialize(args.NewValue as SolidColorBrush);
}));
public ColorPickerUserControl() public ColorPickerUserControl()
{ {
InitializeComponent(); InitializeComponent();
Colors = new List<Color>();
var type = typeof(Windows.UI.Colors);
var properties = type.GetRuntimeProperties().ToArray();
foreach (var propertyInfo in properties)
{
Colors.Add(new Color
{
ColorName = propertyInfo.Name,
ColorBrush = new SolidColorBrush((Windows.UI.Color)propertyInfo.GetValue(null, null))
});
}
} }
private void ComboBox_Loaded(object sender, RoutedEventArgs e) private void ComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
ComboBox.SelectedItem = Colors.Find(c => c.ColorBrush.Color.Equals(SelectedColor.Color)); if (e.AddedItems.Any())
} SelectedColor = (e.AddedItems[0] as ColorPickerControlVm.Color)?.ColorBrush;
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedItem = ComboBox.SelectedItem as Color? ?? new Color();
SelectedColor = selectedItem.ColorBrush;
} }
} }
} }

View File

@@ -60,7 +60,10 @@
<RowDefinition Height="*" /> <RowDefinition Height="*" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}" Unchecked="ToggleButton_OnUnchecked"> <ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl, Mode=TwoWay}" Unchecked="ToggleButton_OnUnchecked">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding HeaderLabel, ElementName=UserControl}" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Checked"> <core:EventTriggerBehavior EventName="Checked">
<core:GoToStateAction StateName="Expanded" /> <core:GoToStateAction StateName="Expanded" />
@@ -94,7 +97,7 @@
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate x:Name="IsNormal"> <DataTemplate x:Name="IsNormal">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=48}" Margin="7,10,0,15"> <SymbolIcon Symbol="{Binding Icon, Converter={StaticResource IconToSymbolConverter}, ConverterParameter=33}" Margin="7,10,0,15">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip Content="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" /> <ToolTip Content="{Binding Path={Binding DisplayMemberPath, ElementName=UserControl}}" />
</ToolTipService.ToolTip> </ToolTipService.ToolTip>
@@ -125,6 +128,7 @@
</StackPanel> </StackPanel>
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click"> <core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=UserControl}" PropertyName="IsOpen" Value="True" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Visible" /> <core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Visible" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Collapsed" /> <core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Collapsed" />
<actions:SetupFocusAction TargetObject="{Binding ElementName=NewGroupTextBox}" /> <actions:SetupFocusAction TargetObject="{Binding ElementName=NewGroupTextBox}" />
@@ -142,7 +146,7 @@
ButtonCommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Text}" ButtonCommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
Style="{StaticResource TextBoxWithButtonStyle}" Style="{StaticResource TextBoxWithButtonStyle}"
KeyDown="NewGroupTextBox_OnKeyDown" KeyDown="NewGroupTextBox_OnKeyDown"
ButtonSymbol="&#xE109;"> ButtonContent="&#xE109;">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="LostFocus"> <core:EventTriggerBehavior EventName="LostFocus">
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Visible" /> <core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Visible" />

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Windows.Input; using System.Windows.Input;
using Windows.System; using Windows.System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
@@ -7,6 +6,7 @@ using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Input;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Controls; using ModernKeePass.Controls;
using System.Collections.ObjectModel;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236 // The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
@@ -62,18 +62,18 @@ namespace ModernKeePass.Views.UserControls
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(Visibility.Collapsed, (o, args) => { })); new PropertyMetadata(Visibility.Collapsed, (o, args) => { }));
public IEnumerable<IEntityVm> ItemsSource public ObservableCollection<IEntityVm> ItemsSource
{ {
get { return (IEnumerable<IEntityVm>)GetValue(ItemsSourceProperty); } get { return (ObservableCollection<IEntityVm>)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); } set { SetValue(ItemsSourceProperty, value); }
} }
public static readonly DependencyProperty ItemsSourceProperty = public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register( DependencyProperty.Register(
nameof(ItemsSource), nameof(ItemsSource),
typeof(IEnumerable<IEntityVm>), typeof(ObservableCollection<IEntityVm>),
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(new List<IEntityVm>(), (o, args) => { })); new PropertyMetadata(new ObservableCollection<IEntityVm>(), (o, args) => { }));
public object SelectedItem public object SelectedItem
{ {

View File

@@ -0,0 +1,384 @@
<UserControl x:Name="UserControl"
x:Class="ModernKeePass.Views.UserControls.PasswordGenerationBox"
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:controls="using:ModernKeePass.Controls"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:converters="using:ModernKeePass.Converters"
xmlns:actions="using:ModernKeePass.Actions"
mc:Ignorable="d"
BorderBrush="{ThemeResource ComboBoxBorderThemeBrush}">
<StackPanel x:Name="StackPanel" Orientation="Vertical" DataContext="{Binding Source={StaticResource Locator}, Path=PasswordGenerationBox}">
<StackPanel.Resources>
<converters:ProgressBarLegalValuesConverter x:Key="ProgressBarLegalValuesConverter" />
<converters:DoubleToSolidColorBrushConverter x:Key="DoubleToForegroundBrushComplexityConverter" />
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" />
<Style TargetType="controls: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 ControlContentThemeFontSize}" />
<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="Typography.StylisticSet20" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls: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="Italic"
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, UpdateSourceTrigger=PropertyChanged}"
IsPasswordRevealButtonEnabled="{TemplateBinding IsPasswordRevealEnabled}"/>
<Button x:Name="ActionButton"
AutomationProperties.AccessibilityView="Raw"
Background="Transparent"
FontWeight="{ThemeResource SearchBoxButtonThemeFontWeight}"
FontSize="12"
Grid.Column="1"
Style="{StaticResource ActionButtonStyle}"
Content="{TemplateBinding ButtonContent}"
IsEnabled="{TemplateBinding IsButtonEnabled}">
<Button.Flyout>
<Flyout>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding Password}" ComparisonCondition="NotEqual" Value="" >
<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>
</StackPanel.Resources>
<controls:PasswordBoxWithButton x:Uid="PasswordGenerationButton"
Password="{Binding Password, Mode=TwoWay, ElementName=UserControl}"
IsPasswordRevealEnabled="True"
Style="{StaticResource PasswordBoxWithButtonStyle}"
IsEnabled="{Binding IsEnabled, ElementName=UserControl}"
PlaceholderText="{Binding PlaceholderText, ElementName=UserControl}"
Visibility="{Binding IsRevealPassword, Converter={StaticResource InverseBooleanToVisibilityConverter}}"
BorderBrush="{Binding BorderBrush, ElementName=UserControl}" />
<controls:TextBoxWithButton
x:Uid="PasswordTextBox"
Text="{Binding Password, Mode=TwoWay, ElementName=UserControl}"
Visibility="{Binding IsRevealPassword, Converter={StaticResource BooleanToVisibilityConverter}}"
Style="{StaticResource TextBoxWithButtonStyle}"
ButtonContent="&#xE16F;"
IsEnabled="{Binding IsEnabled, ElementName=UserControl}"
BorderBrush="{Binding BorderBrush, ElementName=UserControl}" >
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="ButtonClick">
<actions:ClipboardAction Text="{Binding Password}" />
<actions:ToastAction x:Uid="ToastCopyPassword" Title="Password" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</controls:TextBoxWithButton>
<ProgressBar
Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}"
Maximum="128"
Width="{Binding ActualWidth, ElementName=UserControl}"
Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToForegroundBrushComplexityConverter}}" />
<ToggleSwitch x:Uid="EntryShowPassword" Margin="-3,-10,0,0" IsOn="{Binding IsRevealPassword, Mode=TwoWay}" />
</StackPanel>
</UserControl>

View File

@@ -0,0 +1,44 @@
using Windows.UI.Xaml;
using ModernKeePass.ViewModels;
// The User Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234236
namespace ModernKeePass.Views.UserControls
{
public sealed partial class PasswordGenerationBox
{
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register(
nameof(Password),
typeof(string),
typeof(PasswordGenerationBox),
new PropertyMetadata(string.Empty, (o, args) =>
{
var passwordGenerationBox = o as PasswordGenerationBox;
var vm = passwordGenerationBox?.StackPanel.DataContext as PasswordGenerationBoxControlVm;
if (vm != null) vm.Password = args.NewValue.ToString();
}));
public string PlaceholderText
{
get { return (string)GetValue(PlaceholderTextProperty); }
set { SetValue(PlaceholderTextProperty, value); }
}
public static readonly DependencyProperty PlaceholderTextProperty =
DependencyProperty.Register(
nameof(PlaceholderText),
typeof(string),
typeof(PasswordGenerationBox),
new PropertyMetadata(string.Empty, (o, args) => { }));
public PasswordGenerationBox()
{
InitializeComponent();
}
}
}

View File

@@ -7,10 +7,11 @@
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:converters="using:ModernKeePass.Converters" xmlns:converters="using:ModernKeePass.Converters"
xmlns:userControls="using:ModernKeePass.Views.UserControls"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<converters:ProgressBarLegalValuesConverter x:Key="ProgressBarLegalValuesConverter"/> <!--<converters:ProgressBarLegalValuesConverter x:Key="ProgressBarLegalValuesConverter"/>
<converters:DoubleToSolidColorBrushConverter x:Key="DoubleToSolidColorBrushConverter"/> <converters:DoubleToSolidColorBrushConverter x:Key="DoubleToSolidColorBrushConverter"/>-->
<converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/> <converters:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Grid DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}"> <Grid DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}">
@@ -23,30 +24,36 @@
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="45" /> <RowDefinition Height="Auto" />
<RowDefinition Height="45" /> <RowDefinition Height="Auto" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="40" /> <RowDefinition Height="40" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
<RowDefinition Height="40" /> <RowDefinition Height="40" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding HasPassword, Mode=TwoWay}" /> <CheckBox Grid.Row="0" Grid.Column="0" IsChecked="{Binding HasPassword, Mode=TwoWay}" />
<PasswordBox Grid.Row="0" Grid.Column="1" Height="30" <!--<PasswordBox Grid.Row="0" Grid.Column="1" Height="30"
x:Uid="CompositeKeyPassword" x:Uid="CompositeKeyPassword"
x:Name="PasswordBox" x:Name="PasswordBox"
Password="{Binding Password, Mode=TwoWay}" Password="{Binding Password, Mode=TwoWay}"
IsEnabled="{Binding HasPassword}" IsEnabled="{Binding HasPassword}"
IsPasswordRevealButtonEnabled="True" /> IsPasswordRevealButtonEnabled="True" />-->
<PasswordBox Grid.Row="1" Grid.Column="1" Height="30" <userControls:PasswordGenerationBox Grid.Row="0" Grid.Column="1"
x:Uid="CompositeKeyPassword"
x:Name="PasswordBox"
Password="{Binding Password, Mode=TwoWay}"
IsEnabled="{Binding HasPassword}" />
<PasswordBox Grid.Row="1" Grid.Column="1"
x:Uid="CompositeKeyConfirmPassword" x:Uid="CompositeKeyConfirmPassword"
x:Name="ConfirmPasswordBox" x:Name="ConfirmPasswordBox"
Margin="0,5,0,0"
Password="{Binding ConfirmPassword, Mode=TwoWay}" Password="{Binding ConfirmPassword, Mode=TwoWay}"
IsEnabled="{Binding HasPassword}" IsEnabled="{Binding HasPassword}"
IsPasswordRevealButtonEnabled="True" /> IsPasswordRevealButtonEnabled="True" />
<ProgressBar Grid.Row="1" Grid.Column="1" <!--<ProgressBar Grid.Row="1" Grid.Column="1"
Maximum="128" VerticalAlignment="Bottom" Maximum="128" VerticalAlignment="Bottom"
Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}" Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}}"
Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToSolidColorBrushConverter}}"/> Foreground="{Binding PasswordComplexityIndicator, ConverterParameter=128, Converter={StaticResource DoubleToSolidColorBrushConverter}}"/>-->
<TextBlock Grid.Row="2" Grid.Column="1" <TextBlock Grid.Row="2" Grid.Column="1"
FontSize="14" FontWeight="Light" FontSize="14" FontWeight="Light"
x:Uid="SetCredentialsControlMatchingPasswords" x:Uid="SetCredentialsControlMatchingPasswords"

View File

@@ -98,7 +98,7 @@
</MenuFlyout> </MenuFlyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
<ToggleButton Command="{Binding EditCommand, ElementName=UserControl}" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Checked="EditButton_Click" Style="{StaticResource MenuToggleButtonStyle}"> <ToggleButton Visibility="{Binding EditButtonVisibility, ElementName=UserControl}" Command="{Binding EditCommand, ElementName=UserControl}" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Checked="EditButton_Click" Style="{StaticResource MenuToggleButtonStyle}">
<SymbolIcon Symbol="Edit"> <SymbolIcon Symbol="Edit">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip x:Uid="TopMenuEditButton" /> <ToolTip x:Uid="TopMenuEditButton" />
@@ -151,7 +151,7 @@
<MenuFlyout Opening="OverflowFlyout_OnOpening"> <MenuFlyout Opening="OverflowFlyout_OnOpening">
<MenuFlyoutItem x:Uid="TopMenuSaveFlyout" x:Name="SaveFlyout" /> <MenuFlyoutItem x:Uid="TopMenuSaveFlyout" x:Name="SaveFlyout" />
<MenuFlyoutItem x:Uid="TopMenuRestoreFlyout" x:Name="RestoreFlyout" Visibility="{Binding RestoreButtonVisibility, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuRestoreFlyout" x:Name="RestoreFlyout" Visibility="{Binding RestoreButtonVisibility, ElementName=UserControl}" />
<ToggleMenuFlyoutItem x:Uid="TopMenuEditFlyout" x:Name="EditFlyout" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Click="EditButton_Click" /> <ToggleMenuFlyoutItem x:Uid="TopMenuEditFlyout" x:Name="EditFlyout" Visibility="{Binding EditButtonVisibility, ElementName=UserControl}" IsChecked="{Binding IsEditButtonChecked, ElementName=UserControl, Mode=TwoWay}" Click="EditButton_Click" />
<MenuFlyoutItem x:Uid="TopMenuDeleteFlyout" x:Name="DeleteFlyout" /> <MenuFlyoutItem x:Uid="TopMenuDeleteFlyout" x:Name="DeleteFlyout" />
<MenuFlyoutItem x:Uid="TopMenuSortEntriesFlyout" x:Name="SortEntriesFlyout" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuSortEntriesFlyout" x:Name="SortEntriesFlyout" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" />
<MenuFlyoutItem x:Uid="TopMenuSortGroupsFlyout" x:Name="SortGroupsFlyout" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" /> <MenuFlyoutItem x:Uid="TopMenuSortGroupsFlyout" x:Name="SortGroupsFlyout" Visibility="{Binding SortButtonVisibility, ElementName=UserControl}" />

View File

@@ -39,6 +39,18 @@ namespace ModernKeePass.Views.UserControls
typeof(TopMenuUserControl), typeof(TopMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
public Visibility EditButtonVisibility
{
get { return (Visibility)GetValue(EditButtonVisibilityProperty); }
set { SetValue(EditButtonVisibilityProperty, value); }
}
public static readonly DependencyProperty EditButtonVisibilityProperty =
DependencyProperty.Register(
nameof(EditButtonVisibility),
typeof(Visibility),
typeof(TopMenuUserControl),
new PropertyMetadata(Visibility.Collapsed, (o, args) => { }));
public ICommand DeleteCommand public ICommand DeleteCommand
{ {
get { return (ICommand)GetValue(DeleteCommandProperty); } get { return (ICommand)GetValue(DeleteCommandProperty); }
@@ -214,7 +226,6 @@ namespace ModernKeePass.Views.UserControls
MoveCommand.RaiseCanExecuteChanged(); MoveCommand.RaiseCanExecuteChanged();
} }
private async void EntrySearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args) private async void EntrySearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{ {
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata:/Assets/ModernKeePass-SmallLogo.scale-80.png")); var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata:/Assets/ModernKeePass-SmallLogo.scale-80.png"));

View File

@@ -93,14 +93,15 @@
<DependentUpon>App.xaml</DependentUpon> <DependentUpon>App.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="DependencyInjection.cs" /> <Compile Include="DependencyInjection.cs" />
<Compile Include="Log\HockeyAppLog.cs" />
<Compile Include="Models\NavigationItem.cs" /> <Compile Include="Models\NavigationItem.cs" />
<Compile Include="ViewModels\ViewModelLocator.cs" /> <Compile Include="ViewModels\ViewModelLocator.cs" />
<Compile Include="Views\MainPageFrames\DonatePage.xaml.cs"> <Compile Include="Views\MainPageFrames\DonatePage.xaml.cs">
<DependentUpon>DonatePage.xaml</DependentUpon> <DependentUpon>DonatePage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\BasePages\LayoutAwarePageBase.cs" /> <Compile Include="Views\BasePages\LayoutAwarePageBase.cs" />
<Compile Include="Views\MainPageFrames\ImportExportPage.xaml.cs"> <Compile Include="Views\MainPageFrames\ImportPage.xaml.cs">
<DependentUpon>ImportExportPage.xaml</DependentUpon> <DependentUpon>ImportPage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\SettingsPageFrames\SettingsHistoryPage.xaml.cs"> <Compile Include="Views\SettingsPageFrames\SettingsHistoryPage.xaml.cs">
<DependentUpon>SettingsHistoryPage.xaml</DependentUpon> <DependentUpon>SettingsHistoryPage.xaml</DependentUpon>
@@ -123,6 +124,9 @@
<Compile Include="Views\SettingsPageFrames\SettingsWelcomePage.xaml.cs"> <Compile Include="Views\SettingsPageFrames\SettingsWelcomePage.xaml.cs">
<DependentUpon>SettingsWelcomePage.xaml</DependentUpon> <DependentUpon>SettingsWelcomePage.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\UserControls\BreadcrumbUserControl.xaml.cs">
<DependentUpon>BreadcrumbUserControl.xaml</DependentUpon>
</Compile>
<Compile Include="Views\UserControls\ColorPickerUserControl.xaml.cs"> <Compile Include="Views\UserControls\ColorPickerUserControl.xaml.cs">
<DependentUpon>ColorPickerUserControl.xaml</DependentUpon> <DependentUpon>ColorPickerUserControl.xaml</DependentUpon>
</Compile> </Compile>
@@ -167,6 +171,9 @@
<Compile Include="Views\UserControls\HamburgerMenuUserControl.xaml.cs"> <Compile Include="Views\UserControls\HamburgerMenuUserControl.xaml.cs">
<DependentUpon>HamburgerMenuUserControl.xaml</DependentUpon> <DependentUpon>HamburgerMenuUserControl.xaml</DependentUpon>
</Compile> </Compile>
<Compile Include="Views\UserControls\PasswordGenerationBox.xaml.cs">
<DependentUpon>PasswordGenerationBox.xaml</DependentUpon>
</Compile>
<Compile Include="Views\UserControls\SymbolPickerUserControl.xaml.cs"> <Compile Include="Views\UserControls\SymbolPickerUserControl.xaml.cs">
<DependentUpon>SymbolPickerUserControl.xaml</DependentUpon> <DependentUpon>SymbolPickerUserControl.xaml</DependentUpon>
</Compile> </Compile>
@@ -185,10 +192,6 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
</None> </None>
<None Include="Win81App_StoreKey.pfx" /> <None Include="Win81App_StoreKey.pfx" />
<PRIResource Include="Strings\fr-FR\Resources.resw" />
<PRIResource Include="Strings\fr-FR\CodeBehind.resw" />
<PRIResource Include="Strings\en-US\CodeBehind.resw" />
<PRIResource Include="Strings\en-US\Resources.resw" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Package.StoreAssociation.xml" /> <None Include="Package.StoreAssociation.xml" />
@@ -203,7 +206,7 @@
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Page> </Page>
<Page Include="Views\MainPageFrames\ImportExportPage.xaml"> <Page Include="Views\MainPageFrames\ImportPage.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
@@ -219,6 +222,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\UserControls\BreadcrumbUserControl.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\UserControls\ColorPickerUserControl.xaml"> <Page Include="Views\UserControls\ColorPickerUserControl.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
@@ -316,6 +323,10 @@
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>
</Page> </Page>
<Page Include="Views\UserControls\PasswordGenerationBox.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Include="Views\UserControls\SymbolPickerUserControl.xaml"> <Page Include="Views\UserControls\SymbolPickerUserControl.xaml">
<SubType>Designer</SubType> <SubType>Designer</SubType>
<Generator>MSBuild:Compile</Generator> <Generator>MSBuild:Compile</Generator>

View File

@@ -1,8 +1,2 @@
Support for additional fields (Re-)Added a breadcrumb to help with navigation
Support for attachments Design improvements
Add an expiration timer for clipboard data
Ability to manually reorder groups
Ability to set max history count and size
Design changes
Update to KeePassLib version 2.45
Bug corrections

View File

@@ -1,8 +1,2 @@
Ajout des champs additionnels (R)Ajout du fil d'Ariane pour aider a la navigation
Ajout des pi<70>ces-jointes Ameliorations de design
Ajout d'une expiration du presse papier
Possibilite de reorganiser les groupes manuellement
Possibilite de parametrer le nombre max et la taille de l'historique
Quelques changements de design
Mise a jour de la KeePassLib version 2.45
Correction de bugs

View File

@@ -12,7 +12,7 @@
<package id="Microsoft.Extensions.DependencyInjection" version="1.1.1" targetFramework="win81" /> <package id="Microsoft.Extensions.DependencyInjection" version="1.1.1" targetFramework="win81" />
<package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.1.1" targetFramework="win81" /> <package id="Microsoft.Extensions.DependencyInjection.Abstractions" version="1.1.1" targetFramework="win81" />
<package id="Microsoft.NETCore.Platforms" version="2.1.1" targetFramework="win81" /> <package id="Microsoft.NETCore.Platforms" version="2.1.1" targetFramework="win81" />
<package id="Microsoft.NETCore.Portable.Compatibility" version="1.0.1" targetFramework="win81" /> <package id="Microsoft.NETCore.Portable.Compatibility" version="1.0.2" targetFramework="win81" />
<package id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.1.7" targetFramework="win81" /> <package id="Microsoft.NETCore.UniversalWindowsPlatform" version="6.1.7" targetFramework="win81" />
<package id="ModernKeePassLib" version="2.45.1" targetFramework="win81" /> <package id="ModernKeePassLib" version="2.45.1" targetFramework="win81" />
<package id="MvvmLight" version="5.4.1.1" targetFramework="win81" /> <package id="MvvmLight" version="5.4.1.1" targetFramework="win81" />

View File

@@ -159,8 +159,8 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="BouncyCastle.Crypto, Version=1.8.3.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL"> <Reference Include="BouncyCastle.Crypto, Version=1.8.5.0, Culture=neutral, PublicKeyToken=0e99375e54769942, processorArchitecture=MSIL">
<HintPath>..\packages\Portable.BouncyCastle.1.8.3\lib\netstandard1.0\BouncyCastle.Crypto.dll</HintPath> <HintPath>..\packages\Portable.BouncyCastle.1.8.5\lib\netstandard1.0\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="ModernKeePassLib, Version=2.39.1.22027, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="ModernKeePassLib, Version=2.39.1.22027, Culture=neutral, processorArchitecture=MSIL">

View File

@@ -4,14 +4,14 @@
<package id="Microsoft.NETCore.Portable.Compatibility" version="1.0.2" targetFramework="win81" /> <package id="Microsoft.NETCore.Portable.Compatibility" version="1.0.2" targetFramework="win81" />
<package id="ModernKeePassLib" version="2.39.1" targetFramework="win81" /> <package id="ModernKeePassLib" version="2.39.1" targetFramework="win81" />
<package id="NETStandard.Library" version="2.0.3" targetFramework="win81" /> <package id="NETStandard.Library" version="2.0.3" targetFramework="win81" />
<package id="Portable.BouncyCastle" version="1.8.3" targetFramework="win81" /> <package id="Portable.BouncyCastle" version="1.8.5" targetFramework="win81" />
<package id="Splat" version="3.0.0" targetFramework="win81" /> <package id="Splat" version="3.0.0" targetFramework="win81" />
<package id="System.Diagnostics.Contracts" version="4.3.0" targetFramework="win81" /> <package id="System.Diagnostics.Contracts" version="4.3.0" targetFramework="win81" />
<package id="System.Drawing.Primitives" version="4.3.0" targetFramework="win81" /> <package id="System.Drawing.Primitives" version="4.3.0" targetFramework="win81" />
<package id="System.Net.Requests" version="4.3.0" targetFramework="win81" /> <package id="System.Net.Requests" version="4.3.0" targetFramework="win81" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="win81" /> <package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="win81" />
<package id="System.Runtime.Serialization.Primitives" version="4.3.0" targetFramework="win81" /> <package id="System.Runtime.Serialization.Primitives" version="4.3.0" targetFramework="win81" />
<package id="System.Runtime.WindowsRuntime" version="4.3.0" targetFramework="win81" /> <package id="System.Runtime.WindowsRuntime" version="4.7.0" targetFramework="win81" />
<package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="win81" /> <package id="System.Xml.XmlSerializer" version="4.3.0" targetFramework="win81" />
<package id="Validation" version="2.4.18" targetFramework="win81" /> <package id="Validation" version="2.4.18" targetFramework="win81" />
</packages> </packages>

View File

@@ -1,5 +1,6 @@
using System; using System;
using Windows.ApplicationModel.DataTransfer; using Windows.ApplicationModel.DataTransfer;
using Windows.UI.Core;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Xaml.Interactivity; using Microsoft.Xaml.Interactivity;
@@ -10,7 +11,7 @@ namespace ModernKeePass.Actions
{ {
public class ClipboardAction : DependencyObject, IAction public class ClipboardAction : DependencyObject, IAction
{ {
private DispatcherTimer _dispatcher; private bool _isWindowActivated = true;
public string Text public string Text
{ {
@@ -19,29 +20,45 @@ namespace ModernKeePass.Actions
} }
public static readonly DependencyProperty TextProperty = public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ClipboardAction), new PropertyMetadata(string.Empty)); DependencyProperty.Register(nameof(Text), typeof(string), typeof(ClipboardAction), new PropertyMetadata(string.Empty));
public bool IsProtected
{
get { return (bool)GetValue(IsProtectedProperty); }
set { SetValue(IsProtectedProperty, value); }
}
public static readonly DependencyProperty IsProtectedProperty =
DependencyProperty.Register(nameof(IsProtected), typeof(bool), typeof(ClipboardAction), new PropertyMetadata(false));
public object Execute(object sender, object parameter) public object Execute(object sender, object parameter)
{ {
if (string.IsNullOrEmpty(Text)) return null; if (string.IsNullOrEmpty(Text)) return null;
var settings = App.Services.GetRequiredService<ISettingsProxy>(); var settings = App.Services.GetRequiredService<ISettingsProxy>();
var cryptography = App.Services.GetRequiredService<ICryptographyClient>();
_dispatcher = new DispatcherTimer {Interval = TimeSpan.FromSeconds(settings.GetSetting(Constants.Settings.ClipboardTimeout, 10))}; CoreWindow.GetForCurrentThread().Activated += ClipboardAction_Activated;
_dispatcher.Tick += Dispatcher_Tick;
var dispatcher = new DispatcherTimer {Interval = TimeSpan.FromSeconds(settings.GetSetting(Constants.Settings.ClipboardTimeout, 10))};
dispatcher.Tick += Dispatcher_Tick;
var dataPackage = new DataPackage { RequestedOperation = DataPackageOperation.Copy }; var dataPackage = new DataPackage { RequestedOperation = DataPackageOperation.Copy };
dataPackage.SetText(Text); dataPackage.SetText(IsProtected ? cryptography.UnProtect(Text).GetAwaiter().GetResult() : Text);
Clipboard.SetContent(dataPackage); Clipboard.SetContent(dataPackage);
_dispatcher.Start(); Clipboard.Flush();
dispatcher.Start();
return null; return null;
} }
private void ClipboardAction_Activated(CoreWindow sender, WindowActivatedEventArgs args)
{
_isWindowActivated = args.WindowActivationState != CoreWindowActivationState.Deactivated;
}
private void Dispatcher_Tick(object sender, object e) private void Dispatcher_Tick(object sender, object e)
{ {
Clipboard.SetContent(null); if (_isWindowActivated) Clipboard.SetContent(null);
_dispatcher.Stop();
} }
} }
} }

View File

@@ -8,14 +8,14 @@ namespace ModernKeePass.Controls
{ {
public event EventHandler<RoutedEventArgs> ButtonClick; public event EventHandler<RoutedEventArgs> ButtonClick;
public string ButtonSymbol public string ButtonContent
{ {
get { return (string)GetValue(ButtonSymbolProperty); } get { return (string)GetValue(ButtonContentProperty); }
set { SetValue(ButtonSymbolProperty, value); } set { SetValue(ButtonContentProperty, value); }
} }
public static readonly DependencyProperty ButtonSymbolProperty = public static readonly DependencyProperty ButtonContentProperty =
DependencyProperty.Register( DependencyProperty.Register(
nameof(ButtonSymbol), nameof(ButtonContent),
typeof(string), typeof(string),
typeof(PasswordBoxWithButton), typeof(PasswordBoxWithButton),
new PropertyMetadata("&#xE107;", (o, args) => { })); new PropertyMetadata("&#xE107;", (o, args) => { }));

View File

@@ -0,0 +1,87 @@
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
namespace ModernKeePass.Controls
{
public class PasswordGenerationBox : SearchBox
{
public event EventHandler<RoutedEventArgs> ButtonClick;
public string ButtonSymbol
{
get { return (string)GetValue(ButtonSymbolProperty); }
set { SetValue(ButtonSymbolProperty, value); }
}
public static readonly DependencyProperty ButtonSymbolProperty =
DependencyProperty.Register(
nameof(ButtonSymbol),
typeof(string),
typeof(PasswordGenerationBox),
new PropertyMetadata("&#xE107;", (o, args) => { }));
public string ButtonTooltip
{
get { return (string)GetValue(ButtonTooltipProperty); }
set { SetValue(ButtonTooltipProperty, value); }
}
public static readonly DependencyProperty ButtonTooltipProperty =
DependencyProperty.Register(
nameof(ButtonTooltip),
typeof(string),
typeof(PasswordGenerationBox),
new PropertyMetadata(string.Empty, (o, args) => { }));
public string Password
{
get { return (string)GetValue(PasswordProperty); }
set { SetValue(PasswordProperty, value); }
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register(
nameof(Password),
typeof(string),
typeof(PasswordGenerationBox),
new PropertyMetadata(string.Empty, (o, args) => { }));
public bool IsButtonEnabled
{
get { return (bool)GetValue(IsButtonEnabledProperty); }
set { SetValue(IsButtonEnabledProperty, value); }
}
public static readonly DependencyProperty IsButtonEnabledProperty =
DependencyProperty.Register(
nameof(IsButtonEnabled),
typeof(bool),
typeof(PasswordGenerationBox),
new PropertyMetadata(true, (o, args) => { }));
public bool IsPasswordRevealEnabled
{
get { return (bool)GetValue(IsPasswordRevealEnabledProperty); }
set { SetValue(IsPasswordRevealEnabledProperty, value); }
}
public static readonly DependencyProperty IsPasswordRevealEnabledProperty =
DependencyProperty.Register(
nameof(IsPasswordRevealEnabled),
typeof(bool),
typeof(PasswordGenerationBox),
new PropertyMetadata(true, (o, args) => { }));
public PasswordGenerationBox()
{
DefaultStyleKey = typeof(PasswordGenerationBox);
}
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
var actionButton = GetTemplateChild("ActionButton") as Button;
if (actionButton != null)
{
actionButton.Click += (sender, e) => ButtonClick?.Invoke(sender, e);
}
}
}
}

View File

@@ -9,14 +9,14 @@ namespace ModernKeePass.Controls
{ {
public event EventHandler<RoutedEventArgs> ButtonClick; public event EventHandler<RoutedEventArgs> ButtonClick;
public string ButtonSymbol public string ButtonContent
{ {
get { return (string)GetValue(ButtonSymbolProperty); } get { return (string)GetValue(ButtonContentProperty); }
set { SetValue(ButtonSymbolProperty, value); } set { SetValue(ButtonContentProperty, value); }
} }
public static readonly DependencyProperty ButtonSymbolProperty = public static readonly DependencyProperty ButtonContentProperty =
DependencyProperty.Register( DependencyProperty.Register(
nameof(ButtonSymbol), nameof(ButtonContent),
typeof(string), typeof(string),
typeof(TextBoxWithButton), typeof(TextBoxWithButton),
new PropertyMetadata("&#xE107;", (o, args) => { })); new PropertyMetadata("&#xE107;", (o, args) => { }));

View File

@@ -0,0 +1,7 @@
namespace Messages
{
public class PasswordGeneratedMessage
{
public string Password { get; set; }
}
}

View File

@@ -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>
@@ -372,9 +372,6 @@
<data name="GroupsLeftListView.ButtonLabel" xml:space="preserve"> <data name="GroupsLeftListView.ButtonLabel" xml:space="preserve">
<value>New group</value> <value>New group</value>
</data> </data>
<data name="GroupsLeftListView.HeaderLabel" xml:space="preserve">
<value>Groups</value>
</data>
<data name="HistoryLeftListView.HeaderLabel" xml:space="preserve"> <data name="HistoryLeftListView.HeaderLabel" xml:space="preserve">
<value>History</value> <value>History</value>
</data> </data>
@@ -453,9 +450,6 @@
<data name="ToastUpdateDatabase.Title" xml:space="preserve"> <data name="ToastUpdateDatabase.Title" xml:space="preserve">
<value>Composite Key</value> <value>Composite Key</value>
</data> </data>
<data name="EntryTitle.PlaceholderText" xml:space="preserve">
<value>New entry name...</value>
</data>
<data name="SearchButtonLabel.Text" xml:space="preserve"> <data name="SearchButtonLabel.Text" xml:space="preserve">
<value>Search</value> <value>Search</value>
</data> </data>
@@ -564,4 +558,22 @@
<data name="SettingsCopyExpiration.Text" xml:space="preserve"> <data name="SettingsCopyExpiration.Text" xml:space="preserve">
<value>Delete copied value from clipboard after how many seconds ?</value> <value>Delete copied value from clipboard after how many seconds ?</value>
</data> </data>
<data name="EntryShowPassword.OffContent" xml:space="preserve">
<value>Show password</value>
</data>
<data name="PasswordGenerationButton.ButtonContent" xml:space="preserve">
<value>Random</value>
</data>
<data name="EntryTitle.Text" xml:space="preserve">
<value>Title</value>
</data>
<data name="TitleTextBox.ButtonTooltip" xml:space="preserve">
<value>Copy title</value>
</data>
<data name="ToastCopyTitle.Message" xml:space="preserve">
<value>Title copied to clipboard</value>
</data>
<data name="EntryItemDelete.Text" xml:space="preserve">
<value>Delete</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>
@@ -375,9 +372,6 @@
<data name="GroupsLeftListView.ButtonLabel" xml:space="preserve"> <data name="GroupsLeftListView.ButtonLabel" xml:space="preserve">
<value>Nouveau groupe</value> <value>Nouveau groupe</value>
</data> </data>
<data name="GroupsLeftListView.HeaderLabel" xml:space="preserve">
<value>Groupes</value>
</data>
<data name="HistoryLeftListView.HeaderLabel" xml:space="preserve"> <data name="HistoryLeftListView.HeaderLabel" xml:space="preserve">
<value>Historique</value> <value>Historique</value>
</data> </data>
@@ -456,9 +450,6 @@
<data name="ToastUpdateDatabase.Title" xml:space="preserve"> <data name="ToastUpdateDatabase.Title" xml:space="preserve">
<value>Clé maître</value> <value>Clé maître</value>
</data> </data>
<data name="EntryTitle.PlaceholderText" xml:space="preserve">
<value>Nom de la nouvelle entrée...</value>
</data>
<data name="SearchButtonLabel.Text" xml:space="preserve"> <data name="SearchButtonLabel.Text" xml:space="preserve">
<value>Recherche</value> <value>Recherche</value>
</data> </data>
@@ -564,4 +555,25 @@
<data name="EntryAddAdditionalField.Text" xml:space="preserve"> <data name="EntryAddAdditionalField.Text" xml:space="preserve">
<value>Ajouter un champ</value> <value>Ajouter un champ</value>
</data> </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>
<data name="EntryTitle.Text" xml:space="preserve">
<value>Titre</value>
</data>
<data name="TitleTextBox.ButtonTooltip" xml:space="preserve">
<value>Copier le titre</value>
</data>
<data name="ToastCopyTitle.Message" xml:space="preserve">
<value>Titre copié dans le presse-papiers</value>
</data>
<data name="EntryItemDelete.Text" xml:space="preserve">
<value>Supprimer</value>
</data>
</root> </root>

View File

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

View File

@@ -1,12 +1,14 @@
using System.Linq; using System.Linq;
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight;
using Messages; using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
namespace ModernKeePass.ViewModels.ListItems namespace ModernKeePass.ViewModels.ListItems
{ {
public class EntryFieldVm: ViewModelBase public class EntryFieldVm: ViewModelBase
{ {
private readonly ICryptographyClient _cryptography;
private string _name; private string _name;
private string _value; private string _value;
private bool _isProtected; private bool _isProtected;
@@ -22,26 +24,39 @@ namespace ModernKeePass.ViewModels.ListItems
} }
} }
public string DisplayValue => IsProtected? "*****" : _value;
public string Value public string Value
{ {
get { return _value; } get
{
return IsProtected? _cryptography.UnProtect(_value).GetAwaiter().GetResult() : _value;
}
set set
{ {
MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = value, IsProtected = IsProtected }); var protectedValue = IsProtected ? _cryptography.Protect(value).GetAwaiter().GetResult() : value;
Set(nameof(Value), ref _value, value); MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = protectedValue, IsProtected = IsProtected });
Set(nameof(Value), ref _value, protectedValue);
RaisePropertyChanged(nameof(DisplayValue));
} }
} }
public bool IsProtected public bool IsProtected
{ {
get { return _isProtected; } get { return _isProtected; }
set set
{ {
MessengerInstance.Send(new EntryFieldValueChangedMessage { FieldName = Name, FieldValue = Value, IsProtected = value });
Set(nameof(IsProtected), ref _isProtected, value); Set(nameof(IsProtected), ref _isProtected, value);
if (!string.IsNullOrEmpty(Name)) Value = value ? _value : _cryptography.UnProtect(_value).GetAwaiter().GetResult();
} }
} }
public EntryFieldVm(string fieldName, string fieldValue, bool isProtected) public EntryFieldVm(ICryptographyClient cryptography)
{
_cryptography = cryptography;
}
public void Initialize(string fieldName, string fieldValue, bool isProtected)
{ {
_name = fieldName; _name = fieldName;
_value = fieldValue; _value = fieldValue;

View File

@@ -51,7 +51,7 @@ namespace ModernKeePass.ViewModels.Settings
_mediator = mediator; _mediator = mediator;
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); _database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
var rootGroup = _mediator.Send(new GetGroupQuery { Id = _database.RootGroupId }).GetAwaiter().GetResult(); var rootGroup = _mediator.Send(new GetGroupQuery { Id = _database.RootGroupId }).GetAwaiter().GetResult();
Groups = new ObservableCollection<IEntityVm>(rootGroup.SubGroups); Groups = new ObservableCollection<IEntityVm>(rootGroup.Groups);
} }
} }
} }

View File

@@ -0,0 +1,62 @@
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views;
using MediatR;
using ModernKeePass.Application.Common.Models;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Application.Group.Queries.GetGroup;
using ModernKeePass.Common;
using ModernKeePass.Domain.Enums;
using ModernKeePass.Models;
namespace ModernKeePass.ViewModels
{
public class BreadcrumbControlVm
{
public ObservableCollection<BreadcrumbItem> BreadcrumbItems { get; }
public string ParentGroupName { get; private set; }
public Icon ParentGroupIcon { get; private set; } = Icon.Folder;
public RelayCommand GoBackCommand { get; }
public RelayCommand GoUpCommand { get; private set; }
public RelayCommand<int> GoToCommand { get; }
private readonly IMediator _mediator;
private readonly INavigationService _navigation;
public BreadcrumbControlVm(IMediator mediator, INavigationService navigation)
{
_mediator = mediator;
_navigation = navigation;
BreadcrumbItems = new ObservableCollection<BreadcrumbItem>();
GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToCommand = new RelayCommand<int>(GoTo);
GoUpCommand = new RelayCommand(() => GoTo(BreadcrumbItems.Count - 1), () => !string.IsNullOrEmpty(ParentGroupName));
}
public async Task Initialize(GroupVm group)
{
if (group == null) return;
GoBackCommand.RaiseCanExecuteChanged();
ParentGroupName = group.Title;
ParentGroupIcon = group.Icon;
BreadcrumbItems.Add(new BreadcrumbItem { Path = group.Id, Name = group.Title, Icon = group.Icon });
var parentGroup = group;
while (!string.IsNullOrEmpty(parentGroup.ParentGroupId))
{
parentGroup = await _mediator.Send(new GetGroupQuery {Id = parentGroup.ParentGroupId});
BreadcrumbItems.Add(new BreadcrumbItem {Path = parentGroup.Id, Name = parentGroup.Title, Icon = parentGroup.Icon});
}
}
private void GoTo(int index)
{
if (BreadcrumbItems.Count == 0) return;
var breadcrumb = BreadcrumbItems[index];
_navigation.NavigateTo(Constants.Navigation.GroupPage, new NavigationItem { Id = breadcrumb.Path });
}
}
}

View File

@@ -0,0 +1,56 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Windows.UI.Xaml.Media;
using GalaSoft.MvvmLight;
namespace ModernKeePass.ViewModels
{
public class ColorPickerControlVm: ObservableObject
{
private Color _selectedItem;
public sealed class Color
{
public string ColorName { get; set; }
public SolidColorBrush ColorBrush { get; set; }
public bool Equals(SolidColorBrush color)
{
return color.Color.R == ColorBrush.Color.R &&
color.Color.G == ColorBrush.Color.G &&
color.Color.B == ColorBrush.Color.B &&
color.Color.A == ColorBrush.Color.A;
}
}
public List<Color> Colors { get; }
public Color SelectedItem
{
get { return _selectedItem; }
set { Set(() => SelectedItem, ref _selectedItem, value); }
}
public ColorPickerControlVm()
{
Colors = new List<Color>();
var type = typeof(Windows.UI.Colors);
var properties = type.GetRuntimeProperties().ToArray();
foreach (var propertyInfo in properties)
{
var color = new Color
{
ColorName = propertyInfo.Name,
ColorBrush = new SolidColorBrush((Windows.UI.Color) propertyInfo.GetValue(null, null))
};
Colors.Add(color);
}
}
public void Initialize(SolidColorBrush selectedColor)
{
SelectedItem = Colors.FirstOrDefault(c => c.Equals(selectedColor));
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Windows.UI.Xaml.Media;
namespace ModernKeePass.ViewModels
{
public class ColorPickerVm
{
public struct Color
{
public string ColorName { get; set; }
public SolidColorBrush ColorBrush { get; set; }
}
public List<Color> Colors { get; }
public Color SelectedItem { get; set; }
public ColorPickerVm()
{
Colors = new List<Color>();
var type = typeof(Windows.UI.Colors);
var properties = type.GetRuntimeProperties().ToArray();
foreach (var propertyInfo in properties)
{
var color = new Color
{
ColorName = propertyInfo.Name,
ColorBrush = new SolidColorBrush((Windows.UI.Color) propertyInfo.GetValue(null, null))
};
Colors.Add(color);
if (color.ColorBrush.Color.Equals(SelectedColor.Color)
}
}
}
}

View File

@@ -0,0 +1,117 @@
using System.Threading.Tasks;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Security.Commands.GeneratePassword;
using ModernKeePass.Common;
namespace ModernKeePass.ViewModels
{
public class PasswordGenerationBoxControlVm: ViewModelBase
{
private readonly IMediator _mediator;
private readonly ISettingsProxy _settings;
private readonly ICredentialsProxy _credentials;
private string _password;
private bool _isRevealPassword;
public double PasswordComplexityIndicator => _credentials.EstimatePasswordComplexity(Password);
public double PasswordLength
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.PasswordLength, 25); }
set
{
_settings.PutSetting(Constants.Settings.PasswordGenerationOptions.PasswordLength, value);
RaisePropertyChanged(() => PasswordLength);
}
}
public bool UpperCasePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.UpperCasePattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.UpperCasePattern, value); }
}
public bool LowerCasePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.LowerCasePattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.LowerCasePattern, value); }
}
public bool DigitsPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.DigitsPattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.DigitsPattern, value); }
}
public bool MinusPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.MinusPattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.MinusPattern, value); }
}
public bool UnderscorePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.UnderscorePattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.UnderscorePattern, value); }
}
public bool SpacePatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.SpacePattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.SpacePattern, value); }
}
public bool SpecialPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.SpecialPattern, true); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.SpecialPattern, value); }
}
public bool BracketsPatternSelected
{
get { return _settings.GetSetting(Constants.Settings.PasswordGenerationOptions.BracketsPattern, false); }
set { _settings.PutSetting(Constants.Settings.PasswordGenerationOptions.BracketsPattern, value); }
}
public string CustomChars { get; set; } = string.Empty;
public string Password
{
get { return _password; }
set
{
Set(() => Password, ref _password, value);
RaisePropertyChanged(() => PasswordComplexityIndicator);
}
}
public bool IsRevealPassword
{
get { return _isRevealPassword; }
set { Set(() => IsRevealPassword, ref _isRevealPassword, value); }
}
public RelayCommand GeneratePasswordCommand { get; }
public PasswordGenerationBoxControlVm(IMediator mediator, ISettingsProxy settings, ICredentialsProxy credentials)
{
_mediator = mediator;
_settings = settings;
_credentials = credentials;
GeneratePasswordCommand = new RelayCommand(async () => await GeneratePassword());
}
private 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
});
MessengerInstance.Send(new PasswordGeneratedMessage { Password = Password });
}
}
}

View File

@@ -13,7 +13,6 @@ namespace ModernKeePass.ViewModels
public class SetCredentialsVm : ViewModelBase public class SetCredentialsVm : ViewModelBase
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly ICredentialsProxy _credentials;
private readonly IResourceProxy _resource; private readonly IResourceProxy _resource;
private readonly IFileProxy _file; private readonly IFileProxy _file;
@@ -48,10 +47,9 @@ namespace ModernKeePass.ViewModels
get { return _password; } get { return _password; }
set set
{ {
_password = value; Set(() => Password, ref _password, value);
RaisePropertyChanged(nameof(IsPasswordValid)); RaisePropertyChanged(nameof(IsPasswordValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
RaisePropertyChanged(nameof(PasswordComplexityIndicator));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
} }
} }
@@ -60,7 +58,7 @@ namespace ModernKeePass.ViewModels
get { return _confirmPassword; } get { return _confirmPassword; }
set set
{ {
_confirmPassword = value; Set(() => ConfirmPassword, ref _confirmPassword, value);
RaisePropertyChanged(nameof(IsPasswordValid)); RaisePropertyChanged(nameof(IsPasswordValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
@@ -85,8 +83,6 @@ namespace ModernKeePass.ViewModels
set { Set(() => KeyFileText, ref _keyFileText, value); } set { Set(() => KeyFileText, ref _keyFileText, value); }
} }
public double PasswordComplexityIndicator => _credentials.EstimatePasswordComplexity(Password);
public bool IsPasswordValid => HasPassword && Password == ConfirmPassword || !HasPassword; public bool IsPasswordValid => HasPassword && Password == ConfirmPassword || !HasPassword;
public bool IsKeyFileValid => HasKeyFile && !string.IsNullOrEmpty(KeyFilePath) || !HasKeyFile; public bool IsKeyFileValid => HasKeyFile && !string.IsNullOrEmpty(KeyFilePath) || !HasKeyFile;
public bool IsValid => HasPassword && Password == ConfirmPassword || HasKeyFile && !string.IsNullOrEmpty(KeyFilePath) && (HasPassword || HasKeyFile); public bool IsValid => HasPassword && Password == ConfirmPassword || HasKeyFile && !string.IsNullOrEmpty(KeyFilePath) && (HasPassword || HasKeyFile);
@@ -102,10 +98,9 @@ namespace ModernKeePass.ViewModels
private string _keyFilePath; private string _keyFilePath;
private string _keyFileText; private string _keyFileText;
public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource, IFileProxy file) public SetCredentialsVm(IMediator mediator, IResourceProxy resource, IFileProxy file)
{ {
_mediator = mediator; _mediator = mediator;
_credentials = credentials;
_resource = resource; _resource = resource;
_file = file; _file = file;
@@ -114,6 +109,12 @@ namespace ModernKeePass.ViewModels
GenerateCredentialsCommand = new RelayCommand(GenerateCredentials, () => IsValid); GenerateCredentialsCommand = new RelayCommand(GenerateCredentials, () => IsValid);
_keyFileText = resource.GetResourceValue("CompositeKeyDefaultKeyFile"); _keyFileText = resource.GetResourceValue("CompositeKeyDefaultKeyFile");
MessengerInstance.Register<PasswordGeneratedMessage>(this, message =>
{
Password = message.Password;
ConfirmPassword = message.Password;
});
} }
private async Task OpenKeyFile() private async Task OpenKeyFile()

View File

@@ -21,6 +21,7 @@ using MediatR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.ViewModels.Settings; using ModernKeePass.ViewModels.Settings;
using ModernKeePass.Domain.Interfaces;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
@@ -45,7 +46,7 @@ namespace ModernKeePass.ViewModels
else else
{ {
// Create run time view services and models // Create run time view services and models
//SimpleIoc.Default.Register<IDataService, DataService>();IDataService SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IDateTime>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IMediator>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IMediator>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IRecentProxy>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IRecentProxy>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IResourceProxy>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IResourceProxy>());
@@ -67,6 +68,9 @@ namespace ModernKeePass.ViewModels
SimpleIoc.Default.Register<OpenDatabaseControlVm>(); SimpleIoc.Default.Register<OpenDatabaseControlVm>();
SimpleIoc.Default.Register<SetCredentialsVm>(); SimpleIoc.Default.Register<SetCredentialsVm>();
SimpleIoc.Default.Register<TopMenuVm>(); SimpleIoc.Default.Register<TopMenuVm>();
SimpleIoc.Default.Register<ColorPickerControlVm>();
SimpleIoc.Default.Register<PasswordGenerationBoxControlVm>();
SimpleIoc.Default.Register<BreadcrumbControlVm>();
SimpleIoc.Default.Register<NewVm>(); SimpleIoc.Default.Register<NewVm>();
SimpleIoc.Default.Register<OpenVm>(); SimpleIoc.Default.Register<OpenVm>();
SimpleIoc.Default.Register<RecentVm>(); SimpleIoc.Default.Register<RecentVm>();
@@ -82,6 +86,9 @@ namespace ModernKeePass.ViewModels
public OpenDatabaseControlVm OpenDatabaseControl => ServiceLocator.Current.GetInstance<OpenDatabaseControlVm>(Guid.NewGuid().ToString()); public OpenDatabaseControlVm OpenDatabaseControl => ServiceLocator.Current.GetInstance<OpenDatabaseControlVm>(Guid.NewGuid().ToString());
public SetCredentialsVm SetCredentials => ServiceLocator.Current.GetInstance<SetCredentialsVm>(Guid.NewGuid().ToString()); public SetCredentialsVm SetCredentials => ServiceLocator.Current.GetInstance<SetCredentialsVm>(Guid.NewGuid().ToString());
public TopMenuVm TopMenu => ServiceLocator.Current.GetInstance<TopMenuVm>(Guid.NewGuid().ToString()); public TopMenuVm TopMenu => ServiceLocator.Current.GetInstance<TopMenuVm>(Guid.NewGuid().ToString());
public ColorPickerControlVm ColorPicker => ServiceLocator.Current.GetInstance<ColorPickerControlVm>(Guid.NewGuid().ToString());
public PasswordGenerationBoxControlVm PasswordGenerationBox => ServiceLocator.Current.GetInstance<PasswordGenerationBoxControlVm>(Guid.NewGuid().ToString());
public BreadcrumbControlVm Breadcrumb => ServiceLocator.Current.GetInstance<BreadcrumbControlVm>(Guid.NewGuid().ToString());
public NewVm New => ServiceLocator.Current.GetInstance<NewVm>(Guid.NewGuid().ToString()); public NewVm New => ServiceLocator.Current.GetInstance<NewVm>(Guid.NewGuid().ToString());
public OpenVm Open => ServiceLocator.Current.GetInstance<OpenVm>(Guid.NewGuid().ToString()); public OpenVm Open => ServiceLocator.Current.GetInstance<OpenVm>(Guid.NewGuid().ToString());
public RecentVm Recent => ServiceLocator.Current.GetInstance<RecentVm>(Guid.NewGuid().ToString()); public RecentVm Recent => ServiceLocator.Current.GetInstance<RecentVm>(Guid.NewGuid().ToString());

View File

@@ -40,7 +40,9 @@
<Compile Include="$(MSBuildThisFileDirectory)Messages\EntryFieldValueChangedMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\EntryFieldValueChangedMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\FileNotFoundMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\FileNotFoundMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\NavigateToPageMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\NavigateToPageMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\PasswordGeneratedMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\SaveErrorMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\SaveErrorMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)TemplateSelectors\FirstItemDataTemplateSelector.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\AboutVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\AboutVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Items\EntryFieldVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Items\EntryFieldVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\CredentialsVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\CredentialsVm.cs" />
@@ -49,6 +51,9 @@
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\RecycleBinVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\RecycleBinVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\GeneralVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\GeneralVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\SecurityVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Settings\SecurityVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\BreadcrumbControlVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\ColorPickerControlVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\PasswordGenerationBoxControlVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\ViewModelLocatorCommon.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\ViewModelLocatorCommon.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Items\ListMenuItemVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Items\ListMenuItemVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\Items\MainMenuItemVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\Items\MainMenuItemVm.cs" />
@@ -61,4 +66,10 @@
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\SetCredentialsVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\SetCredentialsVm.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\TopMenuVm.cs" /> <Compile Include="$(MSBuildThisFileDirectory)ViewModels\UserControls\TopMenuVm.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PRIResource Include="$(MSBuildThisFileDirectory)Strings\en-US\CodeBehind.resw" />
<PRIResource Include="$(MSBuildThisFileDirectory)Strings\en-US\Resources.resw" />
<PRIResource Include="$(MSBuildThisFileDirectory)Strings\fr-FR\CodeBehind.resw" />
<PRIResource Include="$(MSBuildThisFileDirectory)Strings\fr-FR\Resources.resw" />
</ItemGroup>
</Project> </Project>