9 Commits
V1.16 ... V1.17

Author SHA1 Message Date
Geoffroy BONNEVILLE
1488c3244f Better exception handling 2020-05-05 19:26:38 +02:00
Geoffroy BONNEVILLE
2e22a2bd92 Added some translations in file pickers
Corrected key file creation picker issue (any not allowed)
Fixed some Sonar issues
2020-05-05 17:32:07 +02:00
Geoffroy BONNEVILLE
8fb468358e Hamburger button state is now correct (no more double clicks) but it's a bit hacky
Changed Help tooltip location in New Database Settings page
Suggest Save As when opening DB when another is opened and there is a save error
2020-05-05 16:59:49 +02:00
Geoffroy BONNEVILLE
5ce0262318 TextBoxWithButton control correctly updates field value
Create Group now allows inline input of the group name
2020-05-05 15:27:34 +02:00
Geoffroy BONNEVILLE
2f30389f6c Big redesign (closer to Win10 UWP)
Replaced breadcrumb with Up button
Search button now integrated in top menu
Hamburger menu make better use of visual states
Better visual states changes when size changes
2020-05-04 20:56:19 +02:00
Geoffroy BONNEVILLE
b3c7683c12 Send a message on save to update commands can execute 2020-05-04 14:29:52 +02:00
Geoffroy BONNEVILLE
1e7662def7 Save error is now handled via Messenger instead of unhandled exception handler (which didn't work)
Save as actually works now
WIP Styles
Code cleanup
2020-05-04 12:48:27 +02:00
Geoffroy BONNEVILLE
97b10baedc Resuming correctly re-opens the previsouly opened database 2020-05-02 14:39:42 +02:00
Geoffroy BONNEVILLE
654bd6b4e5 Corrected some Sonar issues
Changed a little bit the Open and Set credentials User controls
2020-05-02 14:21:59 +02:00
73 changed files with 853 additions and 725 deletions

View File

@@ -25,9 +25,8 @@ namespace ModernKeePass.Application.Common.Interfaces
Task Open(byte[] file, Credentials credentials); Task Open(byte[] file, Credentials credentials);
Task ReOpen(byte[] file); Task ReOpen(byte[] file);
Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V3); Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V4);
Task<byte[]> SaveDatabase(); Task<byte[]> SaveDatabase();
Task<byte[]> SaveDatabase(byte[] newFileContents);
void UpdateCredentials(Credentials credentials); void UpdateCredentials(Credentials credentials);
void CloseDatabase(); void CloseDatabase();

View File

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

View File

@@ -18,10 +18,6 @@ namespace ModernKeePass.Application.Database.Commands.CloseDatabase
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
_database.CloseDatabase(); _database.CloseDatabase();
// Cleanup
_database.FileAccessToken = null;
_database.Size = 0;
} }
} }
} }

View File

@@ -44,7 +44,7 @@ namespace ModernKeePass.Application.Database.Commands.CreateDatabase
await _database.Create(new Credentials await _database.Create(new Credentials
{ {
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.OpenBinaryFile(message.KeyFilePath) : null, KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.ReadBinaryFile(message.KeyFilePath) : null,
Password = message.Password Password = message.Password
}, message.Name, version); }, message.Name, version);
_database.FileAccessToken = message.FilePath; _database.FileAccessToken = message.FilePath;

View File

@@ -27,27 +27,20 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
try try
{ {
byte[] contents; if (!string.IsNullOrEmpty(message.FilePath))
if (string.IsNullOrEmpty(message.FilePath))
{ {
contents = await _database.SaveDatabase(); _database.FileAccessToken = message.FilePath;
}
// Test DB integrity before writing changes to file var contents = await _database.SaveDatabase();
// Test DB integrity
_database.CloseDatabase(); _database.CloseDatabase();
await _database.ReOpen(contents); await _database.ReOpen(contents);
// Transactional write to file
await _file.WriteBinaryContentsToFile(_database.FileAccessToken, contents); await _file.WriteBinaryContentsToFile(_database.FileAccessToken, contents);
} }
else
{
var newFileContents = await _file.OpenBinaryFile(message.FilePath);
contents = await _database.SaveDatabase(newFileContents);
await _file.WriteBinaryContentsToFile(message.FilePath, contents);
_file.ReleaseFile(_database.FileAccessToken);
_database.FileAccessToken = message.FilePath;
}
}
catch (Exception exception) catch (Exception exception)
{ {
throw new SaveException(exception); throw new SaveException(exception);

View File

@@ -27,7 +27,7 @@ namespace ModernKeePass.Application.Database.Commands.UpdateCredentials
if (!_database.IsOpen) throw new DatabaseClosedException(); if (!_database.IsOpen) throw new DatabaseClosedException();
_database.UpdateCredentials(new Credentials _database.UpdateCredentials(new Credentials
{ {
KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.OpenBinaryFile(message.KeyFilePath) : null, KeyFileContents = !string.IsNullOrEmpty(message.KeyFilePath) ? await _file.ReadBinaryFile(message.KeyFilePath) : null,
Password = message.Password Password = message.Password
}); });
} }

View File

@@ -27,11 +27,11 @@ namespace ModernKeePass.Application.Database.Queries.OpenDatabase
{ {
if (_database.IsDirty) throw new DatabaseOpenException(); if (_database.IsDirty) throw new DatabaseOpenException();
var file = await _file.OpenBinaryFile(message.FilePath); var file = await _file.ReadBinaryFile(message.FilePath);
var hasKeyFile = !string.IsNullOrEmpty(message.KeyFilePath); var hasKeyFile = !string.IsNullOrEmpty(message.KeyFilePath);
await _database.Open(file, new Credentials await _database.Open(file, new Credentials
{ {
KeyFileContents = hasKeyFile ? await _file.OpenBinaryFile(message.KeyFilePath): null, KeyFileContents = hasKeyFile ? await _file.ReadBinaryFile(message.KeyFilePath): null,
Password = message.Password Password = message.Password
}); });
if (hasKeyFile) _file.ReleaseFile(message.KeyFilePath); if (hasKeyFile) _file.ReleaseFile(message.KeyFilePath);

View File

@@ -20,9 +20,10 @@ namespace ModernKeePass.Application.Database.Queries.ReOpenDatabase
public async Task Handle(ReOpenDatabaseQuery message) public async Task Handle(ReOpenDatabaseQuery message)
{ {
if (!_database.IsOpen) throw new DatabaseClosedException(); if (_database.IsOpen) throw new DatabaseOpenException();
if (string.IsNullOrEmpty(_database.FileAccessToken)) throw new DatabaseClosedException();
var file = await _file.OpenBinaryFile(_database.FileAccessToken); var file = await _file.ReadBinaryFile(_database.FileAccessToken);
await _database.ReOpen(file); await _database.ReOpen(file);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Linq;
using System.Linq;
using MediatR; using MediatR;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;

View File

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

View File

@@ -152,15 +152,6 @@ namespace ModernKeePass.Infrastructure.KeePass
}); });
} }
public async Task<byte[]> SaveDatabase(byte[] newFileContents)
{
return await Task.Run(() =>
{
_pwDatabase.SaveAs(IOConnectionInfo.FromByteArray(newFileContents), true, new NullStatusLogger());
return _pwDatabase.IOConnectionInfo.Bytes;
});
}
public void CloseDatabase() public void CloseDatabase()
{ {
_pwDatabase?.Close(); _pwDatabase?.Close();

View File

@@ -5,20 +5,69 @@ using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.AccessCache; using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.Infrastructure.UWP namespace ModernKeePass.Infrastructure.UWP
{ {
public class StorageFileClient: IFileProxy public class StorageFileClient: IFileProxy
{ {
public async Task<byte[]> OpenBinaryFile(string path) public async Task<FileInfo> OpenFile(string name, string extension, bool addToRecent)
{
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add(extension);
// Application now has read/write access to the picked file
var file = await picker.PickSingleFileAsync().AsTask();
if (file == null) return null;
var token = addToRecent
? StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path)
: StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
return new FileInfo
{
Id = token,
Name = file.Name,
Path = file.Path
};
}
public async Task<FileInfo> CreateFile(string name, string extension, string description, bool addToRecent)
{
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
SuggestedFileName = name
};
savePicker.FileTypeChoices.Add(description, new List<string> { extension });
var file = await savePicker.PickSaveFileAsync().AsTask();
if (file == null) return null;
var token = addToRecent
? StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path)
: StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
return new FileInfo
{
Id = token,
Name = file.Name,
Path = file.Path
};
}
public async Task<byte[]> ReadBinaryFile(string path)
{ {
var file = await GetFile(path); var file = await GetFile(path);
var result = await FileIO.ReadBufferAsync(file).AsTask(); var result = await FileIO.ReadBufferAsync(file).AsTask();
return result.ToArray(); return result.ToArray();
} }
public async Task<IList<string>> OpenTextFile(string path) public async Task<IList<string>> ReadTextFile(string path)
{ {
var file = await GetFile(path); var file = await GetFile(path);
var result = await FileIO.ReadLinesAsync(file).AsTask(); var result = await FileIO.ReadLinesAsync(file).AsTask();

View File

@@ -1,17 +1,16 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.ApplicationModel; using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Activation;
using Windows.Storage; using Windows.Storage;
using Windows.Storage.AccessCache; using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.HockeyApp; using Microsoft.HockeyApp;
using ModernKeePass.Application; using ModernKeePass.Application;
@@ -39,8 +38,9 @@ namespace ModernKeePass
private readonly ISettingsProxy _settings; private readonly ISettingsProxy _settings;
private readonly INavigationService _navigation; private readonly INavigationService _navigation;
private readonly IHockeyClient _hockey; private readonly IHockeyClient _hockey;
private readonly IDialogService _dialog;
private readonly INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file;
private readonly IMessenger _messenger;
public static IServiceProvider Services { get; private set; } public static IServiceProvider Services { get; private set; }
@@ -63,60 +63,39 @@ namespace ModernKeePass
_resource = Services.GetService<IResourceProxy>(); _resource = Services.GetService<IResourceProxy>();
_settings = Services.GetService<ISettingsProxy>(); _settings = Services.GetService<ISettingsProxy>();
_navigation = Services.GetService<INavigationService>(); _navigation = Services.GetService<INavigationService>();
_dialog = Services.GetService<IDialogService>();
_notification = Services.GetService<INotificationService>(); _notification = Services.GetService<INotificationService>();
_hockey = Services.GetService<IHockeyClient>(); _hockey = Services.GetService<IHockeyClient>();
_file = Services.GetService<IFileProxy>();
_messenger = Services.GetService<IMessenger>();
InitializeComponent(); InitializeComponent();
Suspending += OnSuspending; Suspending += OnSuspending;
Resuming += OnResuming; Resuming += OnResuming;
UnhandledException += OnUnhandledException; UnhandledException += OnUnhandledException;
_messenger.Register<SaveErrorMessage>(this, async message => await HandleSaveError(message));
}
private async Task HandleSaveError(SaveErrorMessage message)
{
_notification.Show(_resource.GetResourceValue("MessageDialogSaveErrorTitle"), message?.Message);
var database = await _mediator.Send(new GetDatabaseQuery());
var file = await _file.CreateFile($"{database.Name} - copy",
Domain.Common.Constants.Extensions.Kdbx,
_resource.GetResourceValue("MessageDialogSaveErrorFileTypeDesc"), true);
if (file != null)
{
await _mediator.Send(new SaveDatabaseCommand { FilePath = file.Id });
_messenger.Send(new DatabaseSavedMessage());
}
} }
#region Event Handlers #region Event Handlers
private async void OnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs) private void OnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{ {
// Save the argument exception because it's cleared on first access _hockey.TrackException(e.Exception);
var exception = unhandledExceptionEventArgs.Exception; _hockey.Flush();
var realException =
exception is TargetInvocationException &&
exception.InnerException != null
? exception.InnerException
: exception;
_hockey.TrackException(realException);
if (realException is SaveException)
{
// TODO: this is not working
unhandledExceptionEventArgs.Handled = true;
await _dialog.ShowMessage(realException.Message,
_resource.GetResourceValue("MessageDialogSaveErrorTitle"),
_resource.GetResourceValue("MessageDialogSaveErrorButtonSaveAs"),
_resource.GetResourceValue("MessageDialogSaveErrorButtonDiscard"),
async isOk =>
{
if (isOk)
{
var database = await _mediator.Send(new GetDatabaseQuery());
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
SuggestedFileName = $"{database.Name} - copy"
};
savePicker.FileTypeChoices.Add(
_resource.GetResourceValue("MessageDialogSaveErrorFileTypeDesc"),
new List<string> {".kdbx"});
var file = await savePicker.PickSaveFileAsync();
if (file != null)
{
var token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
await _mediator.Send(new SaveDatabaseCommand {FilePath = token});
}
}
});
}
} }
/// <summary> /// <summary>
@@ -153,9 +132,6 @@ namespace ModernKeePass
{ {
// Load state from previously terminated application // Load state from previously terminated application
await SuspensionManager.RestoreAsync(); await SuspensionManager.RestoreAsync();
#if DEBUG
await _dialog.ShowMessage("Windows or an error made the app terminate", "App terminated");
#endif
} }
// Place the frame in the current Window // Place the frame in the current Window
@@ -179,13 +155,27 @@ namespace ModernKeePass
_notification.Show("App resumed", "Database reopened (changes were saved)"); _notification.Show("App resumed", "Database reopened (changes were saved)");
#endif #endif
} }
catch (Exception) catch (DatabaseOpenException)
{
#if DEBUG
_notification.Show("App resumed", "Previous database still open because it couldn't be closed (probable save error)");
#endif
}
catch (DatabaseClosedException)
{ {
_navigation.NavigateTo(Constants.Navigation.MainPage);
#if DEBUG #if DEBUG
_notification.Show("App resumed", "Nothing to do, no previous database opened"); _notification.Show("App resumed", "Nothing to do, no previous database opened");
#endif #endif
} }
catch (Exception ex)
{
_hockey.TrackException(ex);
_hockey.Flush();
}
finally
{
_navigation.NavigateTo(Constants.Navigation.MainPage);
}
} }
/// <summary> /// <summary>
@@ -222,9 +212,14 @@ namespace ModernKeePass
await _mediator.Send(new CloseDatabaseCommand()).ConfigureAwait(false); await _mediator.Send(new CloseDatabaseCommand()).ConfigureAwait(false);
} }
} }
catch (Exception exception) catch (SaveException ex)
{ {
_notification.Show(exception.Source, exception.Message); _notification.Show(_resource.GetResourceValue("MessageDialogSaveErrorTitle"), ex.Message);
}
catch (Exception ex)
{
_hockey.TrackException(ex);
_hockey.Flush();
} }
finally finally
{ {

View File

@@ -1,5 +1,6 @@
using System.Reflection; using System.Reflection;
using AutoMapper; using AutoMapper;
using GalaSoft.MvvmLight.Messaging;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.HockeyApp; using Microsoft.HockeyApp;
@@ -24,6 +25,7 @@ namespace ModernKeePass
nav.Configure(Constants.Navigation.GroupPage, typeof(GroupDetailPage)); nav.Configure(Constants.Navigation.GroupPage, typeof(GroupDetailPage));
return nav; return nav;
}); });
services.AddSingleton(provider => Messenger.Default);
services.AddTransient(typeof(IDialogService), typeof(DialogService)); services.AddTransient(typeof(IDialogService), typeof(DialogService));
services.AddSingleton(provider => services.AddSingleton(provider =>

View File

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

View File

@@ -13,14 +13,14 @@
<VisualState x:Name="PointerOver"> <VisualState x:Name="PointerOver">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid" <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Grid"
Storyboard.TargetProperty="Opacity"> Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="0.8" /> <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ToggleButtonPointerOverBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
</VisualStateManager.VisualStateGroups> </VisualStateManager.VisualStateGroups>
<Grid x:Name="Grid" Background="{StaticResource MainColor}" Margin="0" Width="{StaticResource MenuSize}" Height="{StaticResource MenuSize}"> <Grid x:Name="Grid" Background="{StaticResource AppBarBackgroundThemeBrush}" Margin="0" Width="{StaticResource MenuWidth}" Height="{StaticResource MenuHeight}">
<Canvas x:Name="HamburgerMenu" HorizontalAlignment="Center" Height="17" UseLayoutRounding="False" VerticalAlignment="Center" Width="28"> <Canvas x:Name="HamburgerMenu" HorizontalAlignment="Center" Height="17" UseLayoutRounding="False" VerticalAlignment="Center" Width="28">
<Canvas x:Name="Layer1" Height="17" Canvas.Left="0" Width="28" Margin="0" RenderTransformOrigin="0.5,0.5"> <Canvas x:Name="Layer1" Height="17" Canvas.Left="0" Width="28" Margin="0" RenderTransformOrigin="0.5,0.5">
<Canvas.RenderTransform> <Canvas.RenderTransform>
@@ -52,7 +52,7 @@
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
</Style> </Style>
<Style x:Key="HeaderTextBoxStyle" TargetType="TextBox"> <Style x:Key="HeaderTextBoxStyle" TargetType="TextBox">
<Setter Property="FontSize" Value="40"/> <Setter Property="FontSize" Value="30"/>
<Setter Property="FontWeight" Value="Light"/> <Setter Property="FontWeight" Value="Light"/>
<Setter Property="BorderBrush" Value="Transparent" /> <Setter Property="BorderBrush" Value="Transparent" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />

View File

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

View File

@@ -3,39 +3,31 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Common theme values --> <!-- Common theme values -->
<x:Double x:Key="MenuSize">60</x:Double> <x:Double x:Key="MenuWidth">60</x:Double>
<x:Double x:Key="MenuHeight">40</x:Double>
<x:Double x:Key="ExpandedMenuSize">300</x:Double> <x:Double x:Key="ExpandedMenuSize">300</x:Double>
<GridLength x:Key="MenuGridLength">60</GridLength> <GridLength x:Key="MenuHeightGridLength">40</GridLength>
<GridLength x:Key="MenuWidthGridLength">60</GridLength>
<GridLength x:Key="ExpandedMenuGridLength">300</GridLength> <GridLength x:Key="ExpandedMenuGridLength">300</GridLength>
<!-- Only available for Windows 10 -->
<!--<SolidColorBrush x:Key="MainColor" Color="{ThemeResource SystemAccentColor}" />
<SolidColorBrush x:Key="TextColor" Color="{ThemeResource SystemColorHighlightTextColor}" />
<SolidColorBrush x:Key="CheckBoxForegroundChecked" Color="{ThemeResource MainColor}"/>
<SolidColorBrush x:Key="CheckBoxCheckGlyphForegroundChecked" Color="{ThemeResource TextColorLight}"/>
<SolidColorBrush x:Key="CheckBoxCheckBackgroundStrokeChecked" Color="{ThemeResource MainColor}"/>
<SolidColorBrush x:Key="CheckBoxCheckBackgroundFillChecked" Color="{ThemeResource MainColor}"/>-->
<Color x:Key="MainColor">SlateBlue</Color> <Color x:Key="MainColor">SlateBlue</Color>
<Color x:Key="MainColorLight">MediumPurple</Color> <Color x:Key="MainColorLight">MediumPurple</Color>
<Color x:Key="MainColorDark">Indigo</Color> <Color x:Key="MainColorDark">Indigo</Color>
<Color x:Key="TextColorLight">WhiteSmoke</Color> <Color x:Key="TextColorLight">WhiteSmoke</Color>
<Color x:Key="TextColorDark">DarkSlateGray</Color>
<Color x:Key="BorderColor">DarkGray</Color> <Color x:Key="BorderColor">DarkGray</Color>
<Color x:Key="FlyoutColor">#FFF0F0F0</Color>
<SolidColorBrush x:Key="MainColorBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="MainColorBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="MainColorLightBrush" Color="{ThemeResource MainColorLight}" /> <SolidColorBrush x:Key="MainColorLightBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="MainColorDarkBrush" Color="{ThemeResource MainColorDark}" /> <SolidColorBrush x:Key="MainColorDarkBrush" Color="{ThemeResource MainColorDark}" />
<SolidColorBrush x:Key="TextColorLightBrush" Color="{ThemeResource TextColorLight}" /> <SolidColorBrush x:Key="TextColorLightBrush" Color="{ThemeResource TextColorLight}" />
<SolidColorBrush x:Key="TextColorDarkBrush" Color="{ThemeResource TextColorDark}" />
<Style TargetType="TextBlock" x:Key="TextBlockSettingsHeaderStyle" > <Style TargetType="TextBlock" x:Key="TextBlockSettingsHeaderStyle" >
<Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="FontFamily" Value="Segoe UI" />
<Setter Property="FontSize" Value="14.667" /> <Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="SemiLight" /> <Setter Property="FontWeight" Value="SemiLight" />
</Style> </Style>
<SolidColorBrush x:Key="TextBoxForegroundThemeBrush" Color="{ThemeResource TextColorDark}" />
<SolidColorBrush x:Key="TextBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" /> <SolidColorBrush x:Key="TextBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
<SolidColorBrush x:Key="TextSelectionHighlightColorThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="TextSelectionHighlightColorThemeBrush" Color="{ThemeResource MainColor}" />
@@ -50,9 +42,9 @@
<SolidColorBrush x:Key="HyperlinkForegroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="HyperlinkForegroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="HyperlinkPointerOverForegroundThemeBrush" Color="{ThemeResource MainColorLight}" /> <SolidColorBrush x:Key="HyperlinkPointerOverForegroundThemeBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="SearchBoxForegroundThemeBrush" Color="{ThemeResource TextColorDark}" />
<!--<SolidColorBrush x:Key="SearchBoxPointerOverBorderThemeBrush" Color="{ThemeResource MainColorLight}" />--> <!--<SolidColorBrush x:Key="SearchBoxPointerOverBorderThemeBrush" Color="{ThemeResource MainColorLight}" />-->
<SolidColorBrush x:Key="SearchBoxPointerOverTextThemeBrush" Color="{ThemeResource MainColorLight}" /> <SolidColorBrush x:Key="SearchBoxPointerOverTextThemeBrush" Color="{ThemeResource MainColorLight}" />
<SolidColorBrush x:Key="SearchBoxBorderThemeBrush" Color="{ThemeResource BorderColor}" />
<SolidColorBrush x:Key="SearchBoxFocusedBorderThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SearchBoxFocusedBorderThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="SearchBoxButtonBackgroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SearchBoxButtonBackgroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="SearchBoxHitHighlightForegroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SearchBoxHitHighlightForegroundThemeBrush" Color="{ThemeResource MainColor}" />
@@ -83,4 +75,10 @@
<SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="{ThemeResource MainColor}" /> <SolidColorBrush x:Key="SliderTrackDecreaseBackgroundThemeBrush" Color="{ThemeResource MainColor}" />
<SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="{ThemeResource MainColorDark}" /> <SolidColorBrush x:Key="SliderTrackDecreasePressedBackgroundThemeBrush" Color="{ThemeResource MainColorDark}" />
<SolidColorBrush x:Key="SliderTrackDecreasePointerOverBackgroundThemeBrush" Color="{ThemeResource MainColorLight}" /> <SolidColorBrush x:Key="SliderTrackDecreasePointerOverBackgroundThemeBrush" Color="{ThemeResource MainColorLight}" />
<Thickness x:Key="MenuFlyoutPresenterThemePadding">0</Thickness>
<Thickness x:Key="FlyoutContentThemePadding">5</Thickness>
<Thickness x:Key="FlyoutBorderThemeThickness">0</Thickness>
<SolidColorBrush x:Key="FlyoutBorderThemeBrush" Color="{ThemeResource FlyoutColor}" />
<SolidColorBrush x:Key="FlyoutBackgroundThemeBrush" Color="{ThemeResource FlyoutColor}" />
</ResourceDictionary> </ResourceDictionary>

View File

@@ -291,7 +291,7 @@
TextWrapping="NoWrap" TextWrapping="NoWrap"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Margin="0" Margin="0"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=Text}" /> Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay, Path=Text, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="ActionButton" <Button x:Name="ActionButton"
AutomationProperties.AccessibilityView="Raw" AutomationProperties.AccessibilityView="Raw"
Background="Transparent" Background="Transparent"
@@ -301,7 +301,8 @@
Style="{StaticResource ActionButtonStyle}" Style="{StaticResource ActionButtonStyle}"
Content="{TemplateBinding ButtonSymbol}" Content="{TemplateBinding ButtonSymbol}"
IsEnabled="{TemplateBinding IsButtonEnabled}" IsEnabled="{TemplateBinding IsButtonEnabled}"
Command="{TemplateBinding ButtonCommand}"> Command="{TemplateBinding ButtonCommand}"
CommandParameter="{TemplateBinding ButtonCommandParameter}">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip Content="{TemplateBinding ButtonTooltip}" /> <ToolTip Content="{TemplateBinding ButtonTooltip}" />
</ToolTipService.ToolTip> </ToolTipService.ToolTip>

View File

@@ -267,4 +267,13 @@
<data name="FileNotFoundTitle" xml:space="preserve"> <data name="FileNotFoundTitle" xml:space="preserve">
<value>File not found</value> <value>File not found</value>
</data> </data>
<data name="MessageDialogSaveNameSuggestion" xml:space="preserve">
<value>KeePass</value>
</data>
<data name="CompositeKeyFileTypeDesc" xml:space="preserve">
<value>Key file</value>
</data>
<data name="CompositeKeyFileNameSuggestion" xml:space="preserve">
<value>Key</value>
</data>
</root> </root>

View File

@@ -513,4 +513,7 @@
<data name="SettingsSaveDatabaseSuspendDesc.Text" xml:space="preserve"> <data name="SettingsSaveDatabaseSuspendDesc.Text" xml:space="preserve">
<value>This settings is generally recommended. If you enable it, database will automatically be saved on application suspension and closing, provided your database is less than 1MB big. Saving bigger databases may take too long and Windows may then forcibly kill the app before saving is finished, resulting in your changes not being saved.</value> <value>This settings is generally recommended. If you enable it, database will automatically be saved on application suspension and closing, provided your database is less than 1MB big. Saving bigger databases may take too long and Windows may then forcibly kill the app before saving is finished, resulting in your changes not being saved.</value>
</data> </data>
<data name="NewGroupTextBox.ButtonTooltip" xml:space="preserve">
<value>New group name</value>
</data>
</root> </root>

View File

@@ -267,4 +267,13 @@
<data name="FileNotFoundTitle" xml:space="preserve"> <data name="FileNotFoundTitle" xml:space="preserve">
<value>Fichier manquant</value> <value>Fichier manquant</value>
</data> </data>
<data name="MessageDialogSaveNameSuggestion" xml:space="preserve">
<value>KeePass</value>
</data>
<data name="CompositeKeyFileTypeDesc" xml:space="preserve">
<value>Fichier de clé</value>
</data>
<data name="CompositeKeyFileNameSuggestion" xml:space="preserve">
<value>Clé</value>
</data>
</root> </root>

View File

@@ -492,9 +492,6 @@
<data name="NewImportFormat.Text" xml:space="preserve"> <data name="NewImportFormat.Text" xml:space="preserve">
<value>Format</value> <value>Format</value>
</data> </data>
<data name="NewImportFormatHelp.Text" xml:space="preserve">
<value>Le fichier CSV doit être formatté de la façon suivante: Nom du compte;Login;Mot de passe:URL;Commentaires</value>
</data>
<data name="CloseButton.Content" xml:space="preserve"> <data name="CloseButton.Content" xml:space="preserve">
<value>Fermer sans sauvegarder</value> <value>Fermer sans sauvegarder</value>
</data> </data>
@@ -516,4 +513,7 @@
<data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve"> <data name="CompositeKeyConfirmPassword.PlaceholderText" xml:space="preserve">
<value>Confirmer le mot de passe</value> <value>Confirmer le mot de passe</value>
</data> </data>
<data name="NewGroupTextBox.ButtonTooltip" xml:space="preserve">
<value>Nom du groupe</value>
</data>
</root> </root>

View File

@@ -9,6 +9,7 @@ using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command; using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Models; using ModernKeePass.Application.Database.Models;
@@ -28,12 +29,13 @@ 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.Exceptions;
using ModernKeePass.Extensions; using ModernKeePass.Extensions;
using ModernKeePass.Models; using ModernKeePass.Models;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class EntryDetailVm : ObservableObject public class EntryDetailVm : ViewModelBase
{ {
public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password); public bool IsRevealPasswordEnabled => !string.IsNullOrEmpty(Password);
public bool HasExpired => HasExpirationDate && ExpiryDate < DateTime.Now; public bool HasExpired => HasExpirationDate && ExpiryDate < DateTime.Now;
@@ -264,6 +266,8 @@ namespace ModernKeePass.ViewModels
DeleteCommand = new RelayCommand(async () => await AskForDelete()); DeleteCommand = new RelayCommand(async () => await AskForDelete());
GoBackCommand = new RelayCommand(() => _navigation.GoBack()); GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToParentCommand = new RelayCommand(() => GoToGroup(_parent.Id)); GoToParentCommand = new RelayCommand(() => GoToGroup(_parent.Id));
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
} }
public async Task Initialize(string entryId) public async Task Initialize(string entryId)
@@ -362,7 +366,14 @@ namespace ModernKeePass.ViewModels
private async Task SaveChanges() private async Task SaveChanges()
{ {
await AddHistory(); await AddHistory();
try
{
await _mediator.Send(new SaveDatabaseCommand()); await _mediator.Send(new SaveDatabaseCommand());
}
catch (SaveException e)
{
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
}
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
_isDirty = false; _isDirty = false;
} }

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
@@ -9,6 +8,7 @@ using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command; using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Models; using ModernKeePass.Application.Database.Models;
@@ -26,14 +26,14 @@ using ModernKeePass.Application.Group.Commands.SortGroups;
using ModernKeePass.Application.Group.Commands.UpdateGroup; using ModernKeePass.Application.Group.Commands.UpdateGroup;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;
using ModernKeePass.Application.Group.Queries.GetGroup; using ModernKeePass.Application.Group.Queries.GetGroup;
using ModernKeePass.Application.Group.Queries.SearchEntries;
using ModernKeePass.Common; using ModernKeePass.Common;
using ModernKeePass.Domain.Enums; using ModernKeePass.Domain.Enums;
using ModernKeePass.Domain.Exceptions;
using ModernKeePass.Models; using ModernKeePass.Models;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class GroupDetailVm : ObservableObject public class GroupDetailVm : ViewModelBase
{ {
public ObservableCollection<EntryVm> Entries { get; private set; } public ObservableCollection<EntryVm> Entries { get; private set; }
@@ -90,7 +90,7 @@ namespace ModernKeePass.ViewModels
public RelayCommand SortGroupsCommand { get; } public RelayCommand SortGroupsCommand { get; }
public RelayCommand<string> MoveCommand { get; } public RelayCommand<string> MoveCommand { get; }
public RelayCommand CreateEntryCommand { get; } public RelayCommand CreateEntryCommand { get; }
public RelayCommand CreateGroupCommand { get; } public RelayCommand<string> CreateGroupCommand { get; }
public RelayCommand DeleteCommand { get; set; } public RelayCommand DeleteCommand { get; set; }
public RelayCommand GoBackCommand { get; set; } public RelayCommand GoBackCommand { get; set; }
public RelayCommand GoToParentCommand { get; set; } public RelayCommand GoToParentCommand { get; set; }
@@ -120,10 +120,12 @@ namespace ModernKeePass.ViewModels
SortGroupsCommand = new RelayCommand(async () => await SortGroupsAsync(), () => IsEditMode); SortGroupsCommand = new RelayCommand(async () => await SortGroupsAsync(), () => IsEditMode);
MoveCommand = new RelayCommand<string>(async destination => await Move(destination), destination => IsNotRoot && !string.IsNullOrEmpty(destination) && destination != Id); MoveCommand = new RelayCommand<string>(async destination => await Move(destination), destination => IsNotRoot && !string.IsNullOrEmpty(destination) && destination != Id);
CreateEntryCommand = new RelayCommand(async () => await AddNewEntry(), () => !IsInRecycleBin && Database.RecycleBinId != Id); CreateEntryCommand = new RelayCommand(async () => await AddNewEntry(), () => !IsInRecycleBin && Database.RecycleBinId != Id);
CreateGroupCommand = new RelayCommand(async () => await AddNewGroup(), () => !IsInRecycleBin && Database.RecycleBinId != Id); CreateGroupCommand = new RelayCommand<string>(async newGroupName => await AddNewGroup(newGroupName), _ => !IsInRecycleBin && Database.RecycleBinId != Id);
DeleteCommand = new RelayCommand(async () => await AskForDelete(),() => IsNotRoot); DeleteCommand = new RelayCommand(async () => await AskForDelete(),() => IsNotRoot);
GoBackCommand = new RelayCommand(() => _navigation.GoBack()); GoBackCommand = new RelayCommand(() => _navigation.GoBack());
GoToParentCommand= new RelayCommand(() => GoToGroup(_parent.Id), () => _parent != null); GoToParentCommand= new RelayCommand(() => GoToGroup(_parent.Id), () => _parent != null);
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
} }
public async Task Initialize(string groupId) public async Task Initialize(string groupId)
@@ -159,7 +161,7 @@ namespace ModernKeePass.ViewModels
public async Task AddNewGroup(string name = "") public async Task AddNewGroup(string name = "")
{ {
var group = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group}); var group = await _mediator.Send(new CreateGroupCommand {Name = name, ParentGroup = _group});
GoToGroup(group.Id, true); Groups.Add(group);
} }
public async Task AddNewEntry() public async Task AddNewEntry()
@@ -175,14 +177,16 @@ namespace ModernKeePass.ViewModels
GoToGroup(destinationId); GoToGroup(destinationId);
} }
public async Task<IEnumerable<EntryVm>> Search(string queryText)
{
return await _mediator.Send(new SearchEntriesQuery {GroupId = Id, SearchText = queryText});
}
private async Task SaveChanges() private async Task SaveChanges()
{
try
{ {
await _mediator.Send(new SaveDatabaseCommand()); await _mediator.Send(new SaveDatabaseCommand());
}
catch (SaveException e)
{
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
}
SaveCommand.RaiseCanExecuteChanged(); SaveCommand.RaiseCanExecuteChanged();
} }

View File

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

View File

@@ -3,7 +3,6 @@ using System.Linq;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using GalaSoft.MvvmLight; using GalaSoft.MvvmLight;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Domain.Interfaces; using ModernKeePass.Domain.Interfaces;

View File

@@ -365,17 +365,18 @@
</TransitionCollection> </TransitionCollection>
</Grid.ChildrenTransitions> </Grid.ChildrenTransitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuGridLength}"/> <RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}" x:Name="LeftListViewColumn" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl x:Uid="HistoryLeftListView" <userControls:HamburgerMenuUserControl
x:Name="HamburgerMenu"
x:Uid="HistoryLeftListView"
ItemsSource="{Binding History}" ItemsSource="{Binding History}"
ResizeTarget="{Binding ElementName=LeftListViewColumn}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}" /> SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
@@ -456,36 +457,38 @@
<!-- Bouton Précédent et titre de la page --> <!-- Bouton Précédent et titre de la page -->
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}"> <Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}"/> <ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" <Button Grid.Column="0"
Command="{Binding GoBackCommand}" Command="{Binding GoBackCommand}"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuSize}" Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back" AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton" AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}"> Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" /> <SymbolIcon Symbol="Back" />
</Button> </Button>
<Grid Grid.Column="1"> <StackPanel Grid.Column="1" Orientation="Horizontal">
<Grid.ColumnDefinitions> <Button
<ColumnDefinition Width="60" /> Height="{StaticResource MenuHeight}"
<ColumnDefinition Width="*" /> Width="{StaticResource MenuWidth}"
</Grid.ColumnDefinitions> Command="{Binding GoToParentCommand}"
<Grid.RowDefinitions> Style="{StaticResource NoBorderButtonStyle}">
<RowDefinition Height="40" /> <SymbolIcon Symbol="Up" />
<RowDefinition Height="20" /> <ToolTipService.ToolTip>
</Grid.RowDefinitions> <ToolTip Content="{Binding ParentGroupName}" />
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"> </ToolTipService.ToolTip>
</Button>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" /> <userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox> </Viewbox>
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}"> <Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" /> <SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox> </Viewbox>
<TextBox Grid.Column="1" Grid.Row="0" <TextBox
x:Uid="EntryTitle" x:Uid="EntryTitle"
x:Name="TitleTextBox" x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}" Text="{Binding Title, Mode=TwoWay}"
@@ -494,6 +497,7 @@
FontSize="20" FontSize="20"
FontWeight="Light" FontWeight="Light"
TextWrapping="NoWrap" TextWrapping="NoWrap"
MinWidth="360"
VerticalAlignment="Center"> VerticalAlignment="Center">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
@@ -505,11 +509,7 @@
</core:DataTriggerBehavior> </core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</TextBox> </TextBox>
<HyperlinkButton Grid.Column="1" Grid.Row="1" </StackPanel>
FontWeight="Light" FontSize="12" Padding="0" Margin="5,-5,0,0"
Content="{Binding ParentGroupName}"
Command="{Binding GoToParentCommand}"/>
</Grid>
<userControls:TopMenuUserControl <userControls:TopMenuUserControl
x:Name="TopMenu" Grid.Column="2" x:Name="TopMenu" Grid.Column="2"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"

View File

@@ -42,8 +42,24 @@ namespace ModernKeePass.Views
private void EntryDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e) private void EntryDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
{ {
VisualStateManager.GoToState(this, e.NewSize.Width < 700 ? "Small" : "Large", true); if (e.NewSize.Width <= 640)
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true); {
VisualStateManager.GoToState(this, "Small", true);
VisualStateManager.GoToState(TopMenu, "Collapsed", true);
VisualStateManager.GoToState(HamburgerMenu, "Hidden", true);
}
else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008)
{
VisualStateManager.GoToState(this, "Medium", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
}
else
{
VisualStateManager.GoToState(this, "Large", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
}
} }
} }
} }

View File

@@ -36,21 +36,20 @@
</TransitionCollection> </TransitionCollection>
</Grid.ChildrenTransitions> </Grid.ChildrenTransitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuGridLength}"/> <RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid Grid.Row="1"> <Grid Grid.Row="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource ExpandedMenuGridLength}" x:Name="LeftListViewColumn" /> <ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" /> <ColumnDefinition Width="*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<userControls:HamburgerMenuUserControl <userControls:HamburgerMenuUserControl
x:Name="HamburgerMenu"
x:Uid="GroupsLeftListView" x:Uid="GroupsLeftListView"
ItemsSource="{Binding Groups}" ItemsSource="{Binding Groups}"
SelectionChanged="groups_SelectionChanged" SelectionChanged="groups_SelectionChanged"
ActionButtonCommand="{Binding CreateGroupCommand}" ActionButtonCommand="{Binding CreateGroupCommand}"
ResizeTarget="{Binding ElementName=LeftListViewColumn}"
IsOpen="True"
IsButtonVisible="Visible" /> IsButtonVisible="Visible" />
<Grid Grid.Column="1"> <Grid Grid.Column="1">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -116,7 +115,7 @@
<TextBlock Text="{Binding Username}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" /> <TextBlock Text="{Binding Username}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
<TextBlock Text="{Binding Url}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" /> <TextBlock Text="{Binding Url}" Style="{StaticResource BodyTextBlockStyle}" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}" MaxHeight="60" />
</StackPanel> </StackPanel>
<Button Grid.Column="2" Style="{StaticResource NoBorderButtonStyle}" Background="{StaticResource AppBarBackgroundThemeBrush}" VerticalAlignment="Bottom" Foreground="{StaticResource TextColorDarkBrush}"> <Button Grid.Column="2" Style="{StaticResource NoBorderButtonStyle}" Background="{StaticResource AppBarBackgroundThemeBrush}" VerticalAlignment="Bottom">
<SymbolIcon Symbol="More" /> <SymbolIcon Symbol="More" />
<Button.Flyout> <Button.Flyout>
<MenuFlyout> <MenuFlyout>
@@ -183,37 +182,38 @@
<!-- Back button and page title --> <!-- Back button and page title -->
<Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}"> <Grid Grid.Row="0" Background="{ThemeResource AppBarBackgroundThemeBrush}">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="{StaticResource MenuGridLength}"/> <ColumnDefinition Width="{StaticResource MenuWidthGridLength}"/>
<ColumnDefinition Width="*"/> <ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/> <ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<Button Grid.Column="0" <Button Grid.Column="0"
Command="{Binding GoBackCommand}" Command="{Binding GoBackCommand}"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuHeight}"
Width="{StaticResource MenuSize}" Width="{StaticResource MenuWidth}"
AutomationProperties.Name="Back" AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton" AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" AutomationProperties.ItemType="Navigation Button"
Style="{StaticResource NoBorderButtonStyle}"> Style="{StaticResource NoBorderButtonStyle}">
<SymbolIcon Symbol="Back" /> <SymbolIcon Symbol="Back" />
</Button> </Button>
<Grid Grid.Column="1" > <StackPanel Grid.Column="1" Orientation="Horizontal">
<Grid.ColumnDefinitions> <Button
<ColumnDefinition Width="60" /> Height="{StaticResource MenuHeight}"
<ColumnDefinition Width="*" /> Width="{StaticResource MenuWidth}"
</Grid.ColumnDefinitions> Command="{Binding GoToParentCommand}"
<Grid.RowDefinitions> Style="{StaticResource NoBorderButtonStyle}">
<RowDefinition Height="40" /> <SymbolIcon Symbol="Up" />
<RowDefinition Height="20" /> <ToolTipService.ToolTip>
</Grid.RowDefinitions> <ToolTip Content="{Binding ParentGroupName}" />
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"> </ToolTipService.ToolTip>
</Button>
<Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}">
<userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" /> <userControls:SymbolPickerUserControl Width="100" Height="70" SelectedSymbol="{Binding Icon, Mode=TwoWay}" />
</Viewbox> </Viewbox>
<Viewbox MaxHeight="200" Width="200" Grid.Column="0" Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}"> <Viewbox MaxHeight="200" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBooleanToVisibilityConverter}}">
<SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" /> <SymbolIcon Symbol="{Binding Icon}" Width="100" Height="70" />
</Viewbox> </Viewbox>
<TextBox Grid.Column="1" Grid.Row="0" <TextBox
x:Uid="GroupTitle" x:Uid="GroupTitle"
x:Name="TitleTextBox" x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}" Text="{Binding Title, Mode=TwoWay}"
@@ -222,6 +222,7 @@
FontSize="20" FontSize="20"
FontWeight="Light" FontWeight="Light"
TextWrapping="NoWrap" TextWrapping="NoWrap"
MinWidth="360"
VerticalAlignment="Center"> VerticalAlignment="Center">
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True"> <core:DataTriggerBehavior Binding="{Binding IsEditMode}" Value="True">
@@ -233,11 +234,7 @@
</core:DataTriggerBehavior> </core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</TextBox> </TextBox>
<HyperlinkButton Grid.Column="1" Grid.Row="1" </StackPanel>
FontWeight="Light" FontSize="12" Padding="0" Margin="5,-5,0,0"
Content="{Binding ParentGroupName}"
Command="{Binding GoToParentCommand}" />
</Grid>
<userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="2" <userControls:TopMenuUserControl x:Name="TopMenu" Grid.Column="2"
SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}" SortButtonVisibility="{Binding IsEditMode, Converter={StaticResource BooleanToVisibilityConverter}}"
IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}" IsEditButtonChecked="{Binding IsEditMode, Mode=TwoWay}"
@@ -252,31 +249,7 @@
</core:EventTriggerBehavior> </core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors> </interactivity:Interaction.Behaviors>
</userControls:TopMenuUserControl> </userControls:TopMenuUserControl>
<Button Grid.Column="3" x:Name="SearchButton" Style="{StaticResource NoBorderButtonStyle}" Background="{ThemeResource ToggleButtonBackgroundThemeBrush}" Height="{StaticResource MenuSize}" Padding="25,0,25,0">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Find" />
<TextBlock x:Uid="SearchButtonLabel" x:Name="SearchButtonLabel" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
<ToolTipService.ToolTip>
<ToolTip x:Uid="SearchButtonTooltip" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchBox}" PropertyName="Visibility" Value="Visible" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchButton}" PropertyName="Visibility" Value="Collapsed" />
<!-- TODO: make this work -->
<actions:SetupFocusAction TargetObject="{Binding ElementName=SearchBox}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<SearchBox Grid.Column="3" x:Uid="EntriesSearch" x:Name="SearchBox" Padding="12" Width="350" Visibility="Collapsed" Margin="0,5,0,5" FontSize="15" SuggestionsRequested="SearchBox_OnSuggestionsRequested" SearchHistoryEnabled="False" ResultSuggestionChosen="SearchBox_OnResultSuggestionChosen">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="LostFocus">
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchBox}" PropertyName="Visibility" Value="Collapsed" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchButton}" PropertyName="Visibility" Value="Visible" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
</Grid> </Grid>
<VisualStateManager.VisualStateGroups> <VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="DragDropGroup"> <VisualStateGroup x:Name="DragDropGroup">
@@ -295,14 +268,21 @@
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
<VisualStateGroup x:Name="TopMenuGroup"> <VisualStateGroup x:Name="PageLayout">
<VisualState x:Name="Small"> <VisualState x:Name="Small">
<Storyboard> <Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> <DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Medium">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="False"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
@@ -311,8 +291,8 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="AddEntryTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HamburgerMenu" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> <DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>

View File

@@ -1,7 +1,4 @@
using System; using Windows.ApplicationModel.DataTransfer;
using System.Linq;
using Windows.ApplicationModel.DataTransfer;
using Windows.Storage.Streams;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation; using Windows.UI.Xaml.Navigation;
@@ -84,25 +81,26 @@ namespace ModernKeePass.Views
e.Data.RequestedOperation = DataPackageOperation.Move; e.Data.RequestedOperation = DataPackageOperation.Move;
} }
private async void SearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png"));
var results = (await Model.Search(args.QueryText)).Take(5);
foreach (var result in results)
{
args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title, result.ParentGroupName, result.Id, imageUri, string.Empty);
}
}
private void SearchBox_OnResultSuggestionChosen(SearchBox sender, SearchBoxResultSuggestionChosenEventArgs args)
{
Model.GoToEntry(args.Tag);
}
private void GroupDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e) private void GroupDetailPage_OnSizeChanged(object sender, SizeChangedEventArgs e)
{ {
VisualStateManager.GoToState(this, e.NewSize.Width < 800 ? "Small" : "Large", true); if (e.NewSize.Width <= 640)
VisualStateManager.GoToState(TopMenu, e.NewSize.Width < 800 ? "Collapsed" : "Overflowed", true); {
VisualStateManager.GoToState(this, "Small", true);
VisualStateManager.GoToState(TopMenu, "Collapsed", true);
VisualStateManager.GoToState(HamburgerMenu, "Hidden", true);
}
else if (e.NewSize.Width > 640 && e.NewSize.Width <= 1008)
{
VisualStateManager.GoToState(this, "Medium", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Collapsed", true);
}
else
{
VisualStateManager.GoToState(this, "Large", true);
VisualStateManager.GoToState(TopMenu, "Overflowed", true);
VisualStateManager.GoToState(HamburgerMenu, "Expanded", true);
}
} }
#endregion #endregion

View File

@@ -27,7 +27,7 @@
</TransitionCollection> </TransitionCollection>
</Grid.ChildrenTransitions> </Grid.ChildrenTransitions>
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuGridLength}"/> <RowDefinition Height="{StaticResource MenuHeightGridLength}"/>
<RowDefinition Height="*"/> <RowDefinition Height="*"/>
</Grid.RowDefinitions> </Grid.RowDefinitions>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
@@ -44,7 +44,7 @@
<Button x:Name="BackButton" <Button x:Name="BackButton"
Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}" Command="{Binding NavigationHelper.GoBackCommand, ElementName=PageRoot}"
Visibility="Collapsed" Visibility="Collapsed"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuWidth}"
AutomationProperties.Name="Back" AutomationProperties.Name="Back"
AutomationProperties.AutomationId="BackButton" AutomationProperties.AutomationId="BackButton"
AutomationProperties.ItemType="Navigation Button" AutomationProperties.ItemType="Navigation Button"

View File

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

View File

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

View File

@@ -1,8 +1,4 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
@@ -15,21 +11,5 @@ namespace ModernKeePass.Views
{ {
InitializeComponent(); InitializeComponent();
} }
private async void ImportFileButton_OnClick(object sender, RoutedEventArgs e)
{
var picker =
new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add(".csv");
// Application now has read/write access to the picked file
var file = await picker.PickSingleFileAsync().AsTask();
if (file == null) return;
}
} }
} }

View File

@@ -13,7 +13,7 @@
</Page.Resources> </Page.Resources>
<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<HyperlinkButton x:Uid="NewCreateButton" Click="CreateDatabaseButton_OnClick" /> <HyperlinkButton x:Uid="NewCreateButton" Command="{Binding CreateDatabaseFileCommand}" />
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="NewCreateDesc" /> <TextBlock Style="{StaticResource BodyTextBlockStyle}" Margin="15,0,0,30" x:Uid="NewCreateDesc" />
<Border HorizontalAlignment="Left" BorderThickness="1" BorderBrush="AliceBlue" Width="550" Visibility="{Binding IsFileSelected, Converter={StaticResource BooleanToVisibilityConverter}}"> <Border HorizontalAlignment="Left" BorderThickness="1" BorderBrush="AliceBlue" Width="550" Visibility="{Binding IsFileSelected, Converter={StaticResource BooleanToVisibilityConverter}}">
<StackPanel Margin="25,0,25,0"> <StackPanel Margin="25,0,25,0">

View File

@@ -1,11 +1,4 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
using System.Collections.Generic;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using ModernKeePass.ViewModels;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
@@ -14,28 +7,9 @@ namespace ModernKeePass.Views
/// </summary> /// </summary>
public sealed partial class NewDatabasePage public sealed partial class NewDatabasePage
{ {
private NewVm Model => (NewVm)DataContext;
public NewDatabasePage() public NewDatabasePage()
{ {
InitializeComponent(); InitializeComponent();
} }
private async void CreateDatabaseButton_OnClick(object sender, RoutedEventArgs e)
{
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
SuggestedFileName = "New Database"
};
savePicker.FileTypeChoices.Add("KeePass 2.x database", new List<string> {".kdbx"});
var file = await savePicker.PickSaveFileAsync().AsTask();
if (file == null) return;
Model.Token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
Model.Name = file.Name;
Model.Path = file.Path;
}
} }
} }

View File

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

View File

@@ -1,8 +1,4 @@
using System; using Windows.UI.Xaml.Navigation;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Navigation;
using ModernKeePass.Domain.Dtos; using ModernKeePass.Domain.Dtos;
using ModernKeePass.ViewModels; using ModernKeePass.ViewModels;
@@ -26,31 +22,7 @@ namespace ModernKeePass.Views
{ {
base.OnNavigatedTo(e); base.OnNavigatedTo(e);
var file = e.Parameter as FileInfo; var file = e.Parameter as FileInfo;
if (file != null) Model.SetFileInformation(file);
{
Model.Path = file.Path;
Model.Name = file.Name;
Model.Token = file.Id;
}
}
private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add(".kdbx");
// Application now has read/write access to the picked file
var file = await picker.PickSingleFileAsync().AsTask();
if (file == null) return;
// TODO: use service
Model.Token = StorageApplicationPermissions.MostRecentlyUsedList.Add(file, file.Path);
Model.Path = file.Path;
Model.Name = file.Name;
} }
} }
} }

View File

@@ -11,7 +11,7 @@
<Page.Resources> <Page.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/> <converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Page.Resources> </Page.Resources>
<Grid x:Name="Grid" Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="40" /> <RowDefinition Height="40" />
<RowDefinition Height="*" /> <RowDefinition Height="*" />

View File

@@ -1,7 +1,5 @@
// Pour en savoir plus sur le modèle d'élément Page vierge, consultez la page http://go.microsoft.com/fwlink/?LinkId=234238 // Pour en savoir plus sur le modèle d'élément Page vierge, consultez la page http://go.microsoft.com/fwlink/?LinkId=234238
using ModernKeePass.ViewModels;
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
/// <summary> /// <summary>
@@ -9,8 +7,6 @@ namespace ModernKeePass.Views
/// </summary> /// </summary>
public sealed partial class RecentDatabasesPage public sealed partial class RecentDatabasesPage
{ {
private RecentVm Model => (RecentVm)DataContext;
public RecentDatabasesPage() public RecentDatabasesPage()
{ {
InitializeComponent(); InitializeComponent();

View File

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

View File

@@ -1,10 +1,4 @@
using System; // The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
using System.Collections.Generic;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using ModernKeePass.ViewModels;
// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238
namespace ModernKeePass.Views namespace ModernKeePass.Views
{ {
@@ -13,24 +7,9 @@ namespace ModernKeePass.Views
/// </summary> /// </summary>
public sealed partial class SaveDatabasePage public sealed partial class SaveDatabasePage
{ {
public SaveVm Model => (SaveVm)DataContext;
public SaveDatabasePage() public SaveDatabasePage()
{ {
InitializeComponent(); InitializeComponent();
} }
private async void SaveAsButton_OnClick(object sender, RoutedEventArgs e)
{
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
SuggestedFileName = "New Database"
};
savePicker.FileTypeChoices.Add("KeePass 2.x database", new List<string> { ".kdbx" });
var file = await savePicker.PickSaveFileAsync().AsTask();
if (file == null) return;
await Model.Save(file);
}
} }
} }

View File

@@ -13,18 +13,12 @@
</StackPanel.Resources> </StackPanel.Resources>
<TextBlock x:Uid="SettingsNewDatabaseDesc" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,10"/> <TextBlock x:Uid="SettingsNewDatabaseDesc" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,0,0,10"/>
<ToggleSwitch x:Uid="SettingsNewDatabaseSample" IsOn="{Binding IsCreateSample, Mode=TwoWay}" /> <ToggleSwitch x:Uid="SettingsNewDatabaseSample" IsOn="{Binding IsCreateSample, Mode=TwoWay}" />
<TextBlock x:Uid="SettingsNewDatabaseKdf" Style="{StaticResource TextBlockSettingsHeaderStyle}" Margin="5,20,0,10" /> <StackPanel Orientation="Horizontal" Margin="5,20,0,10">
<Grid> <TextBlock x:Uid="SettingsNewDatabaseKdf" Style="{StaticResource TextBlockSettingsHeaderStyle}" />
<Grid.ColumnDefinitions> <Button Style="{StaticResource TextBlockButtonStyle}" Foreground="{StaticResource ButtonPointerOverForegroundThemeBrush}" Margin="0,-2,0,0">
<ColumnDefinition Width="Auto" /> <SymbolIcon Symbol="Help" RenderTransformOrigin="0.5,0.5">
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ComboBox Grid.Column="1" ItemsSource="{Binding Source={StaticResource KeyDerivations}}" SelectedItem="{Binding DatabaseFormatVersion, Mode=TwoWay}" DisplayMemberPath="DisplayText" />
<Button Grid.Column="2" Style="{StaticResource TextBlockButtonStyle}">
<SymbolIcon Symbol="Help" RenderTransformOrigin="0.5,0.5" >
<SymbolIcon.RenderTransform> <SymbolIcon.RenderTransform>
<CompositeTransform ScaleX="0.7" ScaleY="0.7"/> <CompositeTransform ScaleX="0.6" ScaleY="0.6"/>
</SymbolIcon.RenderTransform> </SymbolIcon.RenderTransform>
</SymbolIcon> </SymbolIcon>
<Button.Flyout> <Button.Flyout>
@@ -33,6 +27,7 @@
</Flyout> </Flyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
</Grid> </StackPanel>
<ComboBox ItemsSource="{Binding Source={StaticResource KeyDerivations}}" SelectedItem="{Binding DatabaseFormatVersion, Mode=TwoWay}" DisplayMemberPath="DisplayText" />
</StackPanel> </StackPanel>
</Page> </Page>

View File

@@ -8,20 +8,85 @@
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:actions="using:ModernKeePass.Actions"
xmlns:controls="using:ModernKeePass.Controls"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/> <converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/>
</UserControl.Resources> </UserControl.Resources>
<Grid HorizontalAlignment="Left">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisibilityStates">
<VisualState x:Name="Hidden">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource MenuWidth}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Expanded">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ListView" Storyboard.TargetProperty="Width">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource ExpandedMenuSize}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderTextBlock" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid.RowDefinitions>
<RowDefinition Height="{StaticResource MenuHeightGridLength}" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}" Unchecked="ToggleButton_OnUnchecked">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Checked">
<core:GoToStateAction StateName="Expanded" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ToggleButton>
<TextBlock
x:Name="HeaderTextBlock"
Text="{Binding HeaderLabel, ElementName=UserControl}"
FontWeight="Bold"
FontSize="18"
TextWrapping="NoWrap"
VerticalAlignment="Center"
Margin="30,0,20,0"
HorizontalAlignment="Center" />
</StackPanel>
<ListView <ListView
x:Name="ListView"
Grid.Row="1"
ItemsSource="{Binding ItemsSource, ElementName=UserControl}" ItemsSource="{Binding ItemsSource, ElementName=UserControl}"
SelectionChanged="Selector_OnSelectionChanged" SelectionChanged="Selector_OnSelectionChanged"
SelectedItem="{Binding SelectedItem, ElementName=UserControl, Mode=TwoWay}" SelectedItem="{Binding SelectedItem, ElementName=UserControl, Mode=TwoWay}"
SelectedIndex="{Binding SelectedIndex, ElementName=UserControl, Mode=TwoWay}" SelectedIndex="{Binding SelectedIndex, ElementName=UserControl, Mode=TwoWay}"
IsSwipeEnabled="false" IsSwipeEnabled="false"
IsSynchronizedWithCurrentItem="False" IsSynchronizedWithCurrentItem="False"
RequestedTheme="Dark" Background="{ThemeResource AppBarBackgroundThemeBrush}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Foreground="{ThemeResource TextColorLightBrush}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}"> ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<ListView.Resources> <ListView.Resources>
<DataTemplate x:Name="IsSpecial"> <DataTemplate x:Name="IsSpecial">
@@ -50,38 +115,16 @@
</ListView.ItemTemplateSelector> </ListView.ItemTemplateSelector>
<ListView.HeaderTemplate> <ListView.HeaderTemplate>
<DataTemplate> <DataTemplate>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Vertical" Visibility="{Binding IsButtonVisible, ElementName=UserControl}">
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}"> <Button x:Name="NewGroupButton"
<ToolTipService.ToolTip> Padding="0" Margin="0"
<ToolTip Content="{Binding HeaderLabel, ElementName=UserControl}" /> Height="{StaticResource MenuWidth}"
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Checked">
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource ExpandedMenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="Unchecked">
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource MenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ToggleButton>
<TextBlock Text="{Binding HeaderLabel, ElementName=UserControl}" FontWeight="Bold" FontSize="18" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="30,0,20,0" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.FooterTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border BorderBrush="White" BorderThickness="0,0,0,1" />
<Button Padding="0" Margin="0"
Height="{StaticResource MenuSize}"
Visibility="{Binding IsButtonVisible, ElementName=UserControl}" Visibility="{Binding IsButtonVisible, ElementName=UserControl}"
Style="{StaticResource NoBorderButtonStyle}" Style="{StaticResource NoBorderButtonStyle}"
Background="Transparent" Background="Transparent"
Foreground="{ThemeResource TextColorLightBrush}"
BorderThickness="0" BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}" Width="{StaticResource ExpandedMenuSize}"
HorizontalContentAlignment="Left" HorizontalContentAlignment="Left">
Command="{Binding ActionButtonCommand, ElementName=UserControl}">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0"> <StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<SymbolIcon Symbol="Add"> <SymbolIcon Symbol="Add">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
@@ -90,11 +133,45 @@
</SymbolIcon> </SymbolIcon>
<TextBlock Text="{Binding ButtonLabel, ElementName=UserControl}" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" /> <TextBlock Text="{Binding ButtonLabel, ElementName=UserControl}" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" />
</StackPanel> </StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Visible" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Collapsed" />
<actions:SetupFocusAction TargetObject="{Binding ElementName=NewGroupTextBox}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button> </Button>
<controls:TextBoxWithButton
x:Uid="NewGroupTextBox"
x:Name="NewGroupTextBox"
Margin="0,5,0,5"
Visibility="Collapsed"
Width="280"
HorizontalAlignment="Center"
ButtonCommand="{Binding ActionButtonCommand, ElementName=UserControl}"
ButtonCommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Text}"
Style="{StaticResource TextBoxWithButtonStyle}"
KeyDown="NewGroupTextBox_OnKeyDown"
ButtonSymbol="&#xE111;">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="LostFocus">
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Visible" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Collapsed" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Text" Value="" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</controls:TextBoxWithButton>
<Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
</StackPanel>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.FooterTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border BorderBrush="DarkGray" BorderThickness="0,0,0,1" />
<Button Padding="0" Margin="0" <Button Padding="0" Margin="0"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuWidth}"
Style="{StaticResource NoBorderButtonStyle}" Style="{StaticResource NoBorderButtonStyle}"
Foreground="{ThemeResource TextColorLightBrush}"
Background="Transparent" Background="Transparent"
BorderThickness="0" BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}" Width="{StaticResource ExpandedMenuSize}"
@@ -115,9 +192,8 @@
</Button> </Button>
<Button <Button
Padding="0" Margin="0" Padding="0" Margin="0"
Height="{StaticResource MenuSize}" Height="{StaticResource MenuWidth}"
Style="{StaticResource NoBorderButtonStyle}" Style="{StaticResource NoBorderButtonStyle}"
Foreground="{ThemeResource TextColorLightBrush}"
Background="Transparent" Background="Transparent"
BorderThickness="0" BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}" Width="{StaticResource ExpandedMenuSize}"
@@ -140,4 +216,5 @@
</DataTemplate> </DataTemplate>
</ListView.FooterTemplate> </ListView.FooterTemplate>
</ListView> </ListView>
</Grid>
</UserControl> </UserControl>

View File

@@ -1,9 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Windows.Input; using System.Windows.Input;
using Windows.System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Input;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Controls;
// 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,11 +14,6 @@ namespace ModernKeePass.Views.UserControls
{ {
public sealed partial class HamburgerMenuUserControl public sealed partial class HamburgerMenuUserControl
{ {
public HamburgerMenuUserControl()
{
InitializeComponent();
}
public string HeaderLabel public string HeaderLabel
{ {
get { return (string)GetValue(HeaderLabelProperty); } get { return (string)GetValue(HeaderLabelProperty); }
@@ -52,18 +50,6 @@ namespace ModernKeePass.Views.UserControls
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata("Title", (o, args) => { })); new PropertyMetadata("Title", (o, args) => { }));
public object ResizeTarget
{
get { return GetValue(ResizeTargetProperty); }
set { SetValue(ResizeTargetProperty, value); }
}
public static readonly DependencyProperty ResizeTargetProperty =
DependencyProperty.Register(
nameof(ResizeTarget),
typeof(object),
typeof(HamburgerMenuUserControl),
new PropertyMetadata(null, (o, args) => { }));
public Visibility IsButtonVisible public Visibility IsButtonVisible
{ {
get { return (Visibility)GetValue(IsButtonVisibleProperty); } get { return (Visibility)GetValue(IsButtonVisibleProperty); }
@@ -137,11 +123,32 @@ namespace ModernKeePass.Views.UserControls
typeof(HamburgerMenuUserControl), typeof(HamburgerMenuUserControl),
new PropertyMetadata(null, (o, args) => { })); new PropertyMetadata(null, (o, args) => { }));
public event EventHandler<SelectionChangedEventArgs> SelectionChanged; public event EventHandler<SelectionChangedEventArgs> SelectionChanged;
public HamburgerMenuUserControl()
{
InitializeComponent();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e) private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
SelectionChanged?.Invoke(sender, e); SelectionChanged?.Invoke(sender, e);
} }
private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
{
var parent = Parent as FrameworkElement;
if (parent == null) return;
VisualStateManager.GoToState(this, parent.ActualWidth <= 640 ? "Hidden" : "Collapsed", true);
}
private void NewGroupTextBox_OnKeyDown(object sender, KeyRoutedEventArgs e)
{
if (e.Key != VirtualKey.Enter) return;
var textBox = sender as TextBoxWithButton;
ActionButtonCommand.Execute(textBox?.Text);
// Stop the event from triggering twice
e.Handled = true;
}
} }
} }

View File

@@ -40,8 +40,7 @@
<HyperlinkButton Grid.Row="1" Grid.Column="1" Margin="-15,0,0,0" <HyperlinkButton Grid.Row="1" Grid.Column="1" Margin="-15,0,0,0"
x:Name="HyperlinkButton" x:Name="HyperlinkButton"
Content="{Binding KeyFileText}" Content="{Binding KeyFileText}"
IsEnabled="{Binding HasKeyFile}" Command="{Binding OpenKeyFileCommand}" />
Click="KeyFileButton_Click" />
<Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" <Button Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2"
x:Uid="OpenDatabaseControlButton" x:Uid="OpenDatabaseControlButton"
@@ -79,6 +78,7 @@
</VisualStateGroup> </VisualStateGroup>
</VisualStateManager.VisualStateGroups> </VisualStateManager.VisualStateGroups>
<interactivity:Interaction.Behaviors> <interactivity:Interaction.Behaviors>
<!-- TODO: Correct the Runtime template binding error (even though it is actually working as intended) -->
<core:DataTriggerBehavior Binding="{Binding IsError}" Value="True"> <core:DataTriggerBehavior Binding="{Binding IsError}" Value="True">
<core:GoToStateAction StateName="Error"/> <core:GoToStateAction StateName="Error"/>
</core:DataTriggerBehavior> </core:DataTriggerBehavior>

View File

@@ -1,7 +1,4 @@
using System; using Windows.System;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.System;
using Windows.UI.Xaml; using Windows.UI.Xaml;
using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Input;
using ModernKeePass.ViewModels; using ModernKeePass.ViewModels;
@@ -38,23 +35,5 @@ namespace ModernKeePass.Views.UserControls
// Stop the event from triggering twice // Stop the event from triggering twice
e.Handled = true; e.Handled = true;
} }
private async void KeyFileButton_Click(object sender, RoutedEventArgs e)
{
var picker = new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
// Application now has read/write access to the picked file
var file = await picker.PickSingleFileAsync();
if (file == null) return;
var token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
Model.KeyFilePath = token;
Model.KeyFileText = file.Name;
}
} }
} }

View File

@@ -13,7 +13,7 @@
<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 x:Name="Grid" DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}"> <Grid DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}">
<Grid.Resources> <Grid.Resources>
<SolidColorBrush x:Key="ErrorBrush" Color="Red" /> <SolidColorBrush x:Key="ErrorBrush" Color="Red" />
<SolidColorBrush x:Key="ValidBrush" Color="Green" /> <SolidColorBrush x:Key="ValidBrush" Color="Green" />
@@ -35,17 +35,13 @@
x:Uid="CompositeKeyPassword" x:Uid="CompositeKeyPassword"
x:Name="PasswordBox" x:Name="PasswordBox"
Password="{Binding Password, Mode=TwoWay}" Password="{Binding Password, Mode=TwoWay}"
IsPasswordRevealButtonEnabled="True" > IsEnabled="{Binding HasPassword}"
<interactivity:Interaction.Behaviors> IsPasswordRevealButtonEnabled="True" />
<core:EventTriggerBehavior EventName="GotFocus">
<core:ChangePropertyAction TargetObject="{Binding}" PropertyName="HasPassword" Value="True" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</PasswordBox>
<PasswordBox Grid.Row="1" Grid.Column="1" Height="30" <PasswordBox Grid.Row="1" Grid.Column="1" Height="30"
x:Uid="CompositeKeyConfirmPassword" x:Uid="CompositeKeyConfirmPassword"
x:Name="ConfirmPasswordBox" x:Name="ConfirmPasswordBox"
Password="{Binding ConfirmPassword, Mode=TwoWay}" Password="{Binding ConfirmPassword, Mode=TwoWay}"
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"
@@ -61,11 +57,9 @@
<HyperlinkButton Grid.Row="3" Grid.Column="1" Margin="-15,0,0,0" <HyperlinkButton Grid.Row="3" Grid.Column="1" Margin="-15,0,0,0"
x:Name="HyperlinkButton" x:Name="HyperlinkButton"
Content="{Binding KeyFileText}" Content="{Binding KeyFileText}"
IsEnabled="{Binding HasKeyFile}" Command="{Binding OpenKeyFileCommand}" />
Click="KeyFileButton_Click" />
<HyperlinkButton Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right" <HyperlinkButton Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right"
IsEnabled="{Binding HasKeyFile}" Command="{Binding CreateKeyFileCommand}">
Click="CreateKeyFileButton_Click">
<SymbolIcon Symbol="Add"> <SymbolIcon Symbol="Add">
<ToolTipService.ToolTip> <ToolTipService.ToolTip>
<ToolTip x:Uid="CompositeKeyNewKeyFileTooltip" /> <ToolTip x:Uid="CompositeKeyNewKeyFileTooltip" />

View File

@@ -1,9 +1,4 @@
using System; using Windows.UI.Xaml;
using System.Collections.Generic;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using ModernKeePass.ViewModels;
// The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236 // The User Control item template is documented at https://go.microsoft.com/fwlink/?LinkId=234236
@@ -11,8 +6,6 @@ namespace ModernKeePass.Views.UserControls
{ {
public sealed partial class SetCredentialsUserControl public sealed partial class SetCredentialsUserControl
{ {
private SetCredentialsVm Model => (SetCredentialsVm)Grid.DataContext;
public string ButtonLabel public string ButtonLabel
{ {
get { return (string)GetValue(ButtonLabelProperty); } get { return (string)GetValue(ButtonLabelProperty); }
@@ -29,40 +22,5 @@ namespace ModernKeePass.Views.UserControls
{ {
InitializeComponent(); InitializeComponent();
} }
private async void KeyFileButton_Click(object sender, RoutedEventArgs e)
{
var picker =
new FileOpenPicker
{
ViewMode = PickerViewMode.List,
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
picker.FileTypeFilter.Add("*");
// Application now has read/write access to the picked file
var file = await picker.PickSingleFileAsync();
if (file == null) return;
Model.KeyFilePath = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
Model.KeyFileText = file.Name;
}
private async void CreateKeyFileButton_Click(object sender, RoutedEventArgs e)
{
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary,
SuggestedFileName = "Key"
};
savePicker.FileTypeChoices.Add("Key file", new List<string> { ".key" });
var file = await savePicker.PickSaveFileAsync();
if (file == null) return;
Model.KeyFilePath = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
Model.KeyFileText = file.Name;
await Model.GenerateKeyFile();
}
} }
} }

View File

@@ -4,15 +4,18 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:actions="using:ModernKeePass.Actions"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Resources> <UserControl.Resources>
<Style BasedOn="{StaticResource NoBorderButtonStyle}" TargetType="Button" x:Key="MenuButtonStyle" > <Style BasedOn="{StaticResource NoBorderButtonStyle}" TargetType="Button" x:Key="MenuButtonStyle" >
<Setter Property="Padding" Value="25,0,25,0" /> <Setter Property="Padding" Value="25,0,25,0" />
<Setter Property="Height" Value="{StaticResource MenuSize}" /> <Setter Property="Height" Value="{StaticResource MenuHeight}" />
</Style> </Style>
<Style BasedOn="{StaticResource NoBorderToggleButtonStyle}" TargetType="ToggleButton" x:Key="MenuToggleButtonStyle" > <Style BasedOn="{StaticResource NoBorderToggleButtonStyle}" TargetType="ToggleButton" x:Key="MenuToggleButtonStyle" >
<Setter Property="Padding" Value="25,0,25,0" /> <Setter Property="Padding" Value="25,0,25,0" />
<Setter Property="Height" Value="{StaticResource MenuSize}" /> <Setter Property="Height" Value="{StaticResource MenuHeight}" />
</Style> </Style>
</UserControl.Resources> </UserControl.Resources>
<StackPanel x:Name="StackPanel" Orientation="Horizontal" DataContext="{Binding Source={StaticResource Locator}, Path=TopMenu}"> <StackPanel x:Name="StackPanel" Orientation="Horizontal" DataContext="{Binding Source={StaticResource Locator}, Path=TopMenu}">
@@ -26,6 +29,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/> <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
<VisualState x:Name="Collapsed"> <VisualState x:Name="Collapsed">
@@ -36,6 +42,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility"> <ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/> <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames> </ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard> </Storyboard>
</VisualState> </VisualState>
</VisualStateGroup> </VisualStateGroup>
@@ -56,13 +65,12 @@
</SymbolIcon> </SymbolIcon>
<Button.Flyout> <Button.Flyout>
<Flyout Opening="MoveButtonFlyout_OnOpening"> <Flyout Opening="MoveButtonFlyout_OnOpening">
<StackPanel> <StackPanel Background="Transparent">
<SearchBox x:Uid="GroupsSearch" <SearchBox x:Uid="GroupsSearch"
Padding="12" Width="350" Width="250"
Margin="0,5,0,5" Margin="0,5,0,5"
FontSize="15" SuggestionsRequested="GroupSearchBox_OnSuggestionsRequested"
SuggestionsRequested="SearchBox_OnSuggestionsRequested" ResultSuggestionChosen="GroupSearchBox_OnResultSuggestionChosen"
ResultSuggestionChosen="SearchBox_OnResultSuggestionChosen"
SearchHistoryEnabled="False"> SearchHistoryEnabled="False">
</SearchBox> </SearchBox>
<Button x:Name="MoveButton" x:Uid="MoveButton" /> <Button x:Name="MoveButton" x:Uid="MoveButton" />
@@ -104,6 +112,38 @@
</ToolTipService.ToolTip> </ToolTipService.ToolTip>
</SymbolIcon> </SymbolIcon>
</Button> </Button>
<Button x:Name="SearchButton" Style="{StaticResource NoBorderButtonStyle}" Height="{StaticResource MenuHeight}" Padding="25,0,25,0">
<StackPanel Orientation="Horizontal">
<SymbolIcon Symbol="Find" />
<TextBlock x:Uid="SearchButtonLabel" x:Name="SearchButtonLabel" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="10,0,0,0" />
</StackPanel>
<ToolTipService.ToolTip>
<ToolTip x:Uid="SearchButtonTooltip" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchBox}" PropertyName="Visibility" Value="Visible" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchButton}" PropertyName="Visibility" Value="Collapsed" />
<actions:SetupFocusAction TargetObject="{Binding ElementName=SearchBox}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<SearchBox
x:Uid="EntriesSearch"
x:Name="SearchBox"
Margin="0,5,0,5"
Width="350"
Visibility="Collapsed"
SuggestionsRequested="EntrySearchBox_OnSuggestionsRequested"
SearchHistoryEnabled="False"
ResultSuggestionChosen="EntrySearchBox_OnResultSuggestionChosen">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="LostFocus">
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchBox}" PropertyName="Visibility" Value="Collapsed" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=SearchButton}" PropertyName="Visibility" Value="Visible" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
</StackPanel> </StackPanel>
<Button x:Name="MoreButton" Style="{StaticResource MenuButtonStyle}"> <Button x:Name="MoreButton" Style="{StaticResource MenuButtonStyle}">
<SymbolIcon Symbol="More" /> <SymbolIcon Symbol="More" />

View File

@@ -194,7 +194,7 @@ namespace ModernKeePass.Views.UserControls
SortGroupsButtonFlyout.Command = SortGroupsCommand; SortGroupsButtonFlyout.Command = SortGroupsCommand;
} }
private void SearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args) private void GroupSearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{ {
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png")); var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png"));
var groups = Model.Groups.Where(g => g.Title.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5); var groups = Model.Groups.Where(g => g.Title.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5);
@@ -209,10 +209,26 @@ namespace ModernKeePass.Views.UserControls
} }
} }
private void SearchBox_OnResultSuggestionChosen(SearchBox sender, SearchBoxResultSuggestionChosenEventArgs args) private void GroupSearchBox_OnResultSuggestionChosen(SearchBox sender, SearchBoxResultSuggestionChosenEventArgs args)
{ {
MoveButton.CommandParameter = args.Tag; MoveButton.CommandParameter = args.Tag;
MoveCommand.RaiseCanExecuteChanged(); MoveCommand.RaiseCanExecuteChanged();
} }
private async void EntrySearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args)
{
var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appdata://Assets/ModernKeePass-SmallLogo.scale-80.png"));
var results = (await Model.Search(args.QueryText)).Take(5);
foreach (var result in results)
{
args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title, result.ParentGroupName, result.Id, imageUri, string.Empty);
}
}
private void EntrySearchBox_OnResultSuggestionChosen(SearchBox sender, SearchBoxResultSuggestionChosenEventArgs args)
{
Model.GoToEntry(args.Tag);
}
} }
} }

View File

@@ -1,7 +1,5 @@
Database corruption issues should now be a thing of the past: Redesign of the UI (top menu and hamburger menu)
Database integrity is checked after each save Resuming the app re-opens previously opened database
Data is written to the filesystem using a transactional model (meaning: if an error occurs, the original file is left untouched) Notify user and show the Save As dialog when an error is encountered during saving
Auto-save on suspend/exit is now only active when database has changes and its size is under 1MB Save As actually works now
Added the ability to move entries and groups Creating a new group is now done inline
Allows restoring and deleting from entry history
Updated KeePass lib to version 2.44

View File

@@ -1,7 +1,5 @@
Les problemes de corruption de bases de donn<6E>es sont maintenant totalement corrigees : Redesign de l'interface utilisateur (menu superieur et menu hamburger)
L'integrite de la base de donnees est verfiee apres chaque sauvegarde Le re-chargement de l'app re-ouvre la base de donnees ouverte precedemment
Les donnees sont ecrites dans le systeme de fichiers en utilisant un modele transactionnel (autrement dit, s'il y a une erreur, le fichier original n'est pas modifie) L'utlisateur est prevenu et un popup de sauvegarde est affiche quand il y a une erreur de sauvegarde
L'auto-sauvegarde lors de la suspension/sortie ne se fait que lorsqu'il y a eu des changements et que la taille de la base de donnees est inferieure a 1MB La fonctionnalite Sauvegarder Sous fonctionne correctement
Possibilite de deplacer des groupes et des entree La creation d'un nouveau groupe se fait directement dans le menu
Possibilite de supprimer et restaurer des versions de l'historique d'entrees
Libraire mise a jour en version 2.44

View File

@@ -1,4 +1,5 @@
[<img src="https://geogeob.visualstudio.com/_apis/public/build/definitions/04291454-0e79-47a4-9502-5bd374804ccf/2/badge"/>](https://geogeob.visualstudio.com/_apis/public/build/index?definitionId=2) [<img src="https://geogeob.visualstudio.com/_apis/public/build/definitions/04291454-0e79-47a4-9502-5bd374804ccf/2/badge"/>](https://geogeob.visualstudio.com/_apis/public/build/index?definitionId=2)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ModernKeePass&metric=alert_status)](https://sonarcloud.io/dashboard?id=ModernKeePass)
# Introduction # Introduction
**ModernKeePass** is port of the classic Windows application KeePass 2.x for the Windows Store. **ModernKeePass** is port of the classic Windows application KeePass 2.x for the Windows Store.
@@ -17,7 +18,7 @@ You can get it [here](https://www.microsoft.com/en-us/store/p/modernkeepass/9mwq
- Use Recycle Bin - Use Recycle Bin
- Search entries - Search entries
- Sort and reorder entries - Sort and reorder entries
- View entries history - View, delete and restore from entry history
- Use Semantic Zoom to see your entries in a grouped mode - Use Semantic Zoom to see your entries in a grouped mode
- List recently opened databases - List recently opened databases
- Open database from Windows Explorer - Open database from Windows Explorer
@@ -29,7 +30,7 @@ You can get it [here](https://www.microsoft.com/en-us/store/p/modernkeepass/9mwq
# Build and Test # Build and Test
1. Clone the repository 1. Clone the repository
2. Build the main app (the library reference dll is actually a NuGet dependency, built from the [**ModernKeePassLib** project](../ModernKeePassLib/README.md)) 2. Build the main app (the library reference dll is actually a NuGet dependency, built from the [**ModernKeePassLib** project](https://github.com/wismna/ModernKeePassLib))
3. Edit the `.appxmanifest` file to select another certificate (you can create one using Visual Studio or *certutil.exe*) 3. Edit the `.appxmanifest` file to select another certificate (you can create one using Visual Studio or *certutil.exe*)
# Contribute # Contribute
@@ -43,4 +44,4 @@ Otherwise, there are still many things left to implement:
# Credits # Credits
*Dominik Reichl* for the [KeePass application](https://keepass.info/), library and file format *Dominik Reichl* for the [KeePass application](https://keepass.info/), library and file format
*David Lechner* for his [PCL adapatation](https://github.com/dlech/KeePass2PCL) of the KeePass Library and the correlated tests which served as an inspiration basis for my own adaptation *David Lechner* for his [PCL adapatation](https://github.com/dlech/KeePass2PCL) of the KeePass Library and the related tests which served as an inspiration basis for my own adaptation

View File

@@ -1,20 +1,20 @@
namespace ModernKeePass.Common namespace ModernKeePass.Common
{ {
public class Constants public static class Constants
{ {
public class Navigation public static class Navigation
{ {
public static string MainPage => nameof(MainPage); public static string MainPage => nameof(MainPage);
public static string EntryPage => nameof(EntryPage); public static string EntryPage => nameof(EntryPage);
public static string GroupPage => nameof(GroupPage); public static string GroupPage => nameof(GroupPage);
} }
public class File public static class File
{ {
public static int OneMegaByte => 1048576; public static int OneMegaByte => 1048576;
} }
public class Settings public static class Settings
{ {
public static string SaveSuspend => nameof(SaveSuspend); public static string SaveSuspend => nameof(SaveSuspend);
public static string Sample => nameof(Sample); public static string Sample => nameof(Sample);

View File

@@ -1,56 +0,0 @@
using System;
using System.Threading.Tasks;
using Windows.UI.Popups;
namespace ModernKeePass.Common
{
public static class MessageDialogHelper
{
public static async Task ShowActionDialog(string title, string contentText, string actionButtonText, string cancelButtonText, UICommandInvokedHandler actionCommand, UICommandInvokedHandler cancelCommand)
{
// Create the message dialog and set its content
var messageDialog = CreateBasicDialog(title, contentText, cancelButtonText, cancelCommand);
// Add commands and set their callbacks; both buttons use the same callback function instead of inline event handlers
messageDialog.Commands.Add(new UICommand(actionButtonText, actionCommand));
// Show the message dialog
await messageDialog.ShowAsync().AsTask().ConfigureAwait(false);
}
public static async Task ShowErrorDialog(Exception exception)
{
if (exception == null) return;
// Create the message dialog and set its content
var messageDialog = CreateBasicDialog(exception.Message, exception.StackTrace, "OK");
// Show the message dialog
await messageDialog.ShowAsync().AsTask().ConfigureAwait(false);
}
public static async Task ShowNotificationDialog(string title, string message)
{
var dialog = CreateBasicDialog(title, message, "OK");
// Show the message dialog
await dialog.ShowAsync().AsTask().ConfigureAwait(false);
}
private static MessageDialog CreateBasicDialog(string title, string message, string dismissActionText, UICommandInvokedHandler cancelCommand = null)
{
// Create the message dialog and set its content
var messageDialog = new MessageDialog(message, title);
// Add commands and set their callbacks
messageDialog.Commands.Add(new UICommand(dismissActionText, cancelCommand));
// Set the command that will be invoked by default
messageDialog.DefaultCommandIndex = 1;
// Set the command to be invoked when escape is pressed
messageDialog.CancelCommandIndex = 1;
return messageDialog;
}
}
}

View File

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

View File

@@ -1,6 +1,4 @@
using ModernKeePass.Application.Database.Models; namespace Messages
namespace Messages
{ {
public class DatabaseAlreadyOpenedMessage public class DatabaseAlreadyOpenedMessage
{ {

View File

@@ -0,0 +1,7 @@
namespace Messages
{
public class DatabaseSavedMessage
{
}
}

View File

@@ -1,4 +1,5 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages; using Messages;
@@ -15,16 +16,36 @@ namespace ModernKeePass.ViewModels
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 IFileProxy _file;
private readonly IResourceProxy _resource;
public NewVm(IMediator mediator, IRecentProxy recent, ISettingsProxy settings, INavigationService navigation) public RelayCommand CreateDatabaseFileCommand { get; }
public NewVm(IMediator mediator, ISettingsProxy settings, INavigationService navigation, IFileProxy file, IResourceProxy resource): base(file)
{ {
_mediator = mediator; _mediator = mediator;
_settings = settings; _settings = settings;
_navigation = navigation; _navigation = navigation;
_file = file;
_resource = resource;
CreateDatabaseFileCommand = new RelayCommand(async () => await CreateDatabaseFile());
MessengerInstance.Register<CredentialsSetMessage>(this, async m => await TryCreateDatabase(m)); MessengerInstance.Register<CredentialsSetMessage>(this, async m => await TryCreateDatabase(m));
} }
private async Task CreateDatabaseFile()
{
var file = await _file.CreateFile(_resource.GetResourceValue("MessageDialogSaveNameSuggestion"),
Domain.Common.Constants.Extensions.Kdbx,
_resource.GetResourceValue("MessageDialogSaveErrorFileTypeDesc"),
true);
if (file == null) return;
Token = file.Id;
Path = file.Path;
Name = file.Name;
}
private async Task TryCreateDatabase(CredentialsSetMessage message) private async Task TryCreateDatabase(CredentialsSetMessage message)
{ {
var database = await _mediator.Send(new GetDatabaseQuery()); var database = await _mediator.Send(new GetDatabaseQuery());
@@ -45,7 +66,7 @@ namespace ModernKeePass.ViewModels
Password = message.Password, Password = message.Password,
Name = "ModernKeePass", Name = "ModernKeePass",
Version = _settings.GetSetting(Constants.Settings.DefaultFileFormat, "4"), Version = _settings.GetSetting(Constants.Settings.DefaultFileFormat, "4"),
CreateSampleData = _settings.GetSetting<bool>(Constants.Settings.Sample, true) CreateSampleData = _settings.GetSetting(Constants.Settings.Sample, true)
}); });
var database = await _mediator.Send(new GetDatabaseQuery()); var database = await _mediator.Send(new GetDatabaseQuery());

View File

@@ -1,9 +1,15 @@
using GalaSoft.MvvmLight; using System.Threading.Tasks;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class OpenVm: ViewModelBase public class OpenVm: ViewModelBase
{ {
private readonly IFileProxy _file;
private string _name; private string _name;
private string _path; private string _path;
private string _token; private string _token;
@@ -30,5 +36,27 @@ namespace ModernKeePass.ViewModels
get { return _path; } get { return _path; }
set { Set(() => Path, ref _path, value); } set { Set(() => Path, ref _path, value); }
} }
public RelayCommand OpenDatabaseFileCommand { get; }
public OpenVm(IFileProxy file)
{
_file = file;
OpenDatabaseFileCommand = new RelayCommand(async () => await OpenDatabaseFile());
}
public void SetFileInformation(FileInfo file)
{
if (file == null) return;
Token = file.Id;
Path = file.Path;
Name = file.Name;
}
private async Task OpenDatabaseFile()
{
var file = await _file.OpenFile(string.Empty, Constants.Extensions.Kdbx, true);
SetFileInformation(file);
}
} }
} }

View File

@@ -1,46 +1,67 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Storage; using GalaSoft.MvvmLight;
using Windows.Storage.AccessCache; using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views; using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.CloseDatabase; using ModernKeePass.Application.Database.Commands.CloseDatabase;
using ModernKeePass.Application.Database.Commands.SaveDatabase; using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Common; using ModernKeePass.Common;
using RelayCommand = GalaSoft.MvvmLight.Command.RelayCommand; using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class SaveVm public class SaveVm: ViewModelBase
{ {
public bool IsSaveEnabled => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult().IsDirty; public bool IsSaveEnabled => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult().IsDirty;
public RelayCommand SaveAsCommand { get; }
public RelayCommand SaveCommand { get; } public RelayCommand SaveCommand { get; }
public RelayCommand CloseCommand { get; } public RelayCommand CloseCommand { get; }
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly INavigationService _navigation; private readonly INavigationService _navigation;
private readonly IFileProxy _file;
private readonly IResourceProxy _resource;
public SaveVm(IMediator mediator, INavigationService navigation) public SaveVm(IMediator mediator, INavigationService navigation, IFileProxy file, IResourceProxy resource)
{ {
_mediator = mediator; _mediator = mediator;
_navigation = navigation; _navigation = navigation;
_file = file;
_resource = resource;
SaveAsCommand = new RelayCommand(async () => await SaveAs());
SaveCommand = new RelayCommand(async () => await Save(), () => IsSaveEnabled); SaveCommand = new RelayCommand(async () => await Save(), () => IsSaveEnabled);
CloseCommand = new RelayCommand(async () => await Close()); CloseCommand = new RelayCommand(async () => await Close());
MessengerInstance.Register<DatabaseSavedMessage>(this, _ => SaveCommand.RaiseCanExecuteChanged());
} }
public async Task Save(bool close = true) private async Task SaveAs()
{
var file = await _file.CreateFile(_resource.GetResourceValue("MessageDialogSaveNameSuggestion"),
Domain.Common.Constants.Extensions.Kdbx,
_resource.GetResourceValue("MessageDialogSaveErrorFileTypeDesc"),
true);
if (file == null) return;
await _mediator.Send(new SaveDatabaseCommand { FilePath = file.Id });
_navigation.NavigateTo(Constants.Navigation.MainPage);
}
public async Task Save()
{
try
{ {
await _mediator.Send(new SaveDatabaseCommand()); await _mediator.Send(new SaveDatabaseCommand());
if (close) await _mediator.Send(new CloseDatabaseCommand()); await Close();
_navigation.NavigateTo(Constants.Navigation.MainPage);
} }
catch (SaveException e)
public async Task Save(StorageFile file)
{ {
var token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name); MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
await _mediator.Send(new SaveDatabaseCommand { FilePath = token }); }
_navigation.NavigateTo(Constants.Navigation.MainPage);
} }
public async Task Close() public async Task Close()

View File

@@ -9,6 +9,7 @@ using Messages;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Database.Queries.OpenDatabase; using ModernKeePass.Application.Database.Queries.OpenDatabase;
using ModernKeePass.Domain.Common;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
@@ -85,11 +86,13 @@ namespace ModernKeePass.ViewModels
public bool IsValid => !IsOpening && (HasPassword || HasKeyFile && !string.IsNullOrEmpty(KeyFilePath)); public bool IsValid => !IsOpening && (HasPassword || HasKeyFile && !string.IsNullOrEmpty(KeyFilePath));
public RelayCommand OpenKeyFileCommand { get; }
public RelayCommand<string> OpenDatabaseCommand { get; } public RelayCommand<string> OpenDatabaseCommand { get; }
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly IResourceProxy _resource; private readonly IResourceProxy _resource;
private readonly INotificationService _notification; private readonly INotificationService _notification;
private readonly IFileProxy _file;
private bool _hasPassword; private bool _hasPassword;
private bool _hasKeyFile; private bool _hasKeyFile;
private bool _isOpening; private bool _isOpening;
@@ -99,15 +102,26 @@ namespace ModernKeePass.ViewModels
private string _keyFileText; private string _keyFileText;
private bool _isError; private bool _isError;
public OpenDatabaseControlVm(IMediator mediator, IResourceProxy resource, INotificationService notification) public OpenDatabaseControlVm(IMediator mediator, IResourceProxy resource, INotificationService notification, IFileProxy file)
{ {
_mediator = mediator; _mediator = mediator;
_resource = resource; _resource = resource;
_notification = notification; _notification = notification;
_file = file;
OpenKeyFileCommand = new RelayCommand(async () => await OpenKeyFile());
OpenDatabaseCommand = new RelayCommand<string>(async databaseFilePath => await TryOpenDatabase(databaseFilePath), _ => IsValid); OpenDatabaseCommand = new RelayCommand<string>(async databaseFilePath => await TryOpenDatabase(databaseFilePath), _ => IsValid);
_keyFileText = _resource.GetResourceValue("CompositeKeyDefaultKeyFile"); _keyFileText = _resource.GetResourceValue("CompositeKeyDefaultKeyFile");
} }
private async Task OpenKeyFile()
{
var file = await _file.OpenFile(string.Empty, Constants.Extensions.Any, false);
if (file == null) return;
KeyFilePath = file.Id;
KeyFileText = file.Name;
HasKeyFile = true;
}
public async Task TryOpenDatabase(string databaseFilePath) public async Task TryOpenDatabase(string databaseFilePath)
{ {
MessengerInstance.Send(new DatabaseOpeningMessage {Token = databaseFilePath}); MessengerInstance.Send(new DatabaseOpeningMessage {Token = databaseFilePath});

View File

@@ -5,6 +5,8 @@ using MediatR;
using Messages; using Messages;
using ModernKeePass.Application.Common.Interfaces; using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Security.Commands.GenerateKeyFile; using ModernKeePass.Application.Security.Commands.GenerateKeyFile;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
@@ -12,6 +14,8 @@ namespace ModernKeePass.ViewModels
{ {
private readonly IMediator _mediator; private readonly IMediator _mediator;
private readonly ICredentialsProxy _credentials; private readonly ICredentialsProxy _credentials;
private readonly IResourceProxy _resource;
private readonly IFileProxy _file;
public bool HasPassword public bool HasPassword
{ {
@@ -33,6 +37,8 @@ namespace ModernKeePass.ViewModels
Set(() => HasKeyFile, ref _hasKeyFile, value); Set(() => HasKeyFile, ref _hasKeyFile, value);
RaisePropertyChanged(nameof(IsKeyFileValid)); RaisePropertyChanged(nameof(IsKeyFileValid));
RaisePropertyChanged(nameof(IsValid)); RaisePropertyChanged(nameof(IsValid));
OpenKeyFileCommand.RaiseCanExecuteChanged();
CreateKeyFileCommand.RaiseCanExecuteChanged();
GenerateCredentialsCommand.RaiseCanExecuteChanged(); GenerateCredentialsCommand.RaiseCanExecuteChanged();
} }
} }
@@ -85,6 +91,8 @@ namespace ModernKeePass.ViewModels
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);
public RelayCommand OpenKeyFileCommand { get; }
public RelayCommand CreateKeyFileCommand { get; }
public RelayCommand GenerateCredentialsCommand{ get; } public RelayCommand GenerateCredentialsCommand{ get; }
private bool _hasPassword; private bool _hasPassword;
@@ -94,18 +102,43 @@ namespace ModernKeePass.ViewModels
private string _keyFilePath; private string _keyFilePath;
private string _keyFileText; private string _keyFileText;
public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource) public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource, IFileProxy file)
{ {
_mediator = mediator; _mediator = mediator;
_credentials = credentials; _credentials = credentials;
_resource = resource;
_file = file;
OpenKeyFileCommand = new RelayCommand(async () => await OpenKeyFile(), () => HasKeyFile);
CreateKeyFileCommand = new RelayCommand(async () => await CreateKeyFile(), () => HasKeyFile);
GenerateCredentialsCommand = new RelayCommand(GenerateCredentials, () => IsValid); GenerateCredentialsCommand = new RelayCommand(GenerateCredentials, () => IsValid);
_keyFileText = resource.GetResourceValue("CompositeKeyDefaultKeyFile"); _keyFileText = resource.GetResourceValue("CompositeKeyDefaultKeyFile");
} }
public async Task GenerateKeyFile() private async Task OpenKeyFile()
{ {
await _mediator.Send(new GenerateKeyFileCommand {KeyFilePath = KeyFilePath}); var file = await _file.OpenFile(string.Empty, Constants.Extensions.Any, false);
if (file == null) return;
SetKeyFileInfo(file);
}
private async Task CreateKeyFile()
{
var file = await _file.CreateFile(_resource.GetResourceValue("CompositeKeyFileNameSuggestion"),
Constants.Extensions.Key, _resource.GetResourceValue("CompositeKeyFileTypeDesc"),
false);
if (file == null) return;
SetKeyFileInfo(file);
await _mediator.Send(new GenerateKeyFileCommand { KeyFilePath = KeyFilePath });
}
private void SetKeyFileInfo(FileInfo file)
{
KeyFilePath = file.Id;
KeyFileText = file.Name;
HasKeyFile = true;
} }
private void GenerateCredentials() private void GenerateCredentials()

View File

@@ -1,19 +1,46 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks;
using GalaSoft.MvvmLight.Views;
using MediatR; using MediatR;
using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase; using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Application.Group.Models; using ModernKeePass.Application.Group.Models;
using ModernKeePass.Application.Group.Queries.GetAllGroups; using ModernKeePass.Application.Group.Queries.GetAllGroups;
using ModernKeePass.Application.Group.Queries.SearchEntries;
using ModernKeePass.Common;
using ModernKeePass.Models;
namespace ModernKeePass.ViewModels namespace ModernKeePass.ViewModels
{ {
public class TopMenuVm public class TopMenuVm
{ {
private readonly IMediator _mediator;
private readonly INavigationService _navigation;
private readonly DatabaseVm _database;
public IEnumerable<GroupVm> Groups { get; set; } public IEnumerable<GroupVm> Groups { get; set; }
public TopMenuVm(IMediator mediator) public TopMenuVm(IMediator mediator, INavigationService navigation)
{ {
var database = mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult(); _mediator = mediator;
Groups = mediator.Send(new GetAllGroupsQuery { GroupId = database.RootGroupId }).GetAwaiter().GetResult(); _navigation = navigation;
_database = _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
Groups = _mediator.Send(new GetAllGroupsQuery { GroupId = _database.RootGroupId }).GetAwaiter().GetResult();
}
public void GoToEntry(string entryId, bool isNew = false)
{
_navigation.NavigateTo(Constants.Navigation.EntryPage, new NavigationItem
{
Id = entryId,
IsNew = isNew
});
}
public async Task<IEnumerable<EntryVm>> Search(string queryText)
{
return await _mediator.Send(new SearchEntriesQuery { GroupId = _database.RootGroupId, SearchText = queryText });
} }
} }
} }

View File

@@ -50,6 +50,7 @@ namespace ModernKeePass.ViewModels
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>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<ISettingsProxy>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<ISettingsProxy>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IFileProxy>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<ICredentialsProxy>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<ICredentialsProxy>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IDialogService>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<IDialogService>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<INavigationService>()); SimpleIoc.Default.Register(() => App.Services.GetRequiredService<INavigationService>());

View File

@@ -36,6 +36,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseClosedMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseClosedMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseOpenedMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseOpenedMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseOpeningMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseOpeningMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Messages\DatabaseSavedMessage.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\SaveErrorMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Messages\SaveErrorMessage.cs" />