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 ReOpen(byte[] file);
Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V3);
Task Create(Credentials credentials, string name, DatabaseVersion version = DatabaseVersion.V4);
Task<byte[]> SaveDatabase();
Task<byte[]> SaveDatabase(byte[] newFileContents);
void UpdateCredentials(Credentials credentials);
void CloseDatabase();

View File

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

View File

@@ -18,10 +18,6 @@ namespace ModernKeePass.Application.Database.Commands.CloseDatabase
{
if (!_database.IsOpen) throw new DatabaseClosedException();
_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
{
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
}, message.Name, version);
_database.FileAccessToken = message.FilePath;

View File

@@ -27,27 +27,20 @@ namespace ModernKeePass.Application.Database.Commands.SaveDatabase
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();
await _database.ReOpen(contents);
// Transactional write to file
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)
{
throw new SaveException(exception);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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()
{
_pwDatabase?.Close();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,6 @@
xmlns:viewModels="using:ModernKeePass.ViewModels"
x:Class="ModernKeePass.Views.AboutPage"
mc:Ignorable="d">
<Page.DataContext>
<viewModels:AboutVm/>
</Page.DataContext>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -8,20 +8,85 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:converters="using:ModernKeePass.Converters"
xmlns:actions="using:ModernKeePass.Actions"
xmlns:controls="using:ModernKeePass.Controls"
mc:Ignorable="d">
<UserControl.Resources>
<converters:IconToSymbolConverter x:Key="IconToSymbolConverter"/>
</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
x:Name="ListView"
Grid.Row="1"
ItemsSource="{Binding ItemsSource, ElementName=UserControl}"
SelectionChanged="Selector_OnSelectionChanged"
SelectedItem="{Binding SelectedItem, ElementName=UserControl, Mode=TwoWay}"
SelectedIndex="{Binding SelectedIndex, ElementName=UserControl, Mode=TwoWay}"
IsSwipeEnabled="false"
IsSynchronizedWithCurrentItem="False"
RequestedTheme="Dark"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
Foreground="{ThemeResource TextColorLightBrush}"
Background="{ThemeResource AppBarBackgroundThemeBrush}"
ItemContainerStyle="{StaticResource ListViewLeftIndicatorItemExpanded}">
<ListView.Resources>
<DataTemplate x:Name="IsSpecial">
@@ -50,38 +115,16 @@
</ListView.ItemTemplateSelector>
<ListView.HeaderTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ToggleButton Style="{StaticResource HamburgerToggleButton}" IsChecked="{Binding IsOpen, ElementName=UserControl}">
<ToolTipService.ToolTip>
<ToolTip Content="{Binding HeaderLabel, ElementName=UserControl}" />
</ToolTipService.ToolTip>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Checked">
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource ExpandedMenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="Unchecked">
<core:ChangePropertyAction PropertyName="Width" Value="{StaticResource MenuSize}" TargetObject="{Binding ResizeTarget, ElementName=UserControl}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ToggleButton>
<TextBlock Text="{Binding HeaderLabel, ElementName=UserControl}" FontWeight="Bold" FontSize="18" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="30,0,20,0" HorizontalAlignment="Center" />
</StackPanel>
</DataTemplate>
</ListView.HeaderTemplate>
<ListView.FooterTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border BorderBrush="White" BorderThickness="0,0,0,1" />
<Button Padding="0" Margin="0"
Height="{StaticResource MenuSize}"
<StackPanel Orientation="Vertical" Visibility="{Binding IsButtonVisible, ElementName=UserControl}">
<Button x:Name="NewGroupButton"
Padding="0" Margin="0"
Height="{StaticResource MenuWidth}"
Visibility="{Binding IsButtonVisible, ElementName=UserControl}"
Style="{StaticResource NoBorderButtonStyle}"
Background="Transparent"
Foreground="{ThemeResource TextColorLightBrush}"
BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}"
HorizontalContentAlignment="Left"
Command="{Binding ActionButtonCommand, ElementName=UserControl}">
HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" Margin="17,0,5,0">
<SymbolIcon Symbol="Add">
<ToolTipService.ToolTip>
@@ -90,11 +133,45 @@
</SymbolIcon>
<TextBlock Text="{Binding ButtonLabel, ElementName=UserControl}" FontWeight="SemiBold" TextWrapping="NoWrap" FontSize="16" VerticalAlignment="Center" Margin="30,0,20,0" />
</StackPanel>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupTextBox}" PropertyName="Visibility" Value="Visible" />
<core:ChangePropertyAction TargetObject="{Binding ElementName=NewGroupButton}" PropertyName="Visibility" Value="Collapsed" />
<actions:SetupFocusAction TargetObject="{Binding ElementName=NewGroupTextBox}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Button>
<controls:TextBoxWithButton
x:Uid="NewGroupTextBox"
x:Name="NewGroupTextBox"
Margin="0,5,0,5"
Visibility="Collapsed"
Width="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"
Height="{StaticResource MenuSize}"
Height="{StaticResource MenuWidth}"
Style="{StaticResource NoBorderButtonStyle}"
Foreground="{ThemeResource TextColorLightBrush}"
Background="Transparent"
BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}"
@@ -115,9 +192,8 @@
</Button>
<Button
Padding="0" Margin="0"
Height="{StaticResource MenuSize}"
Height="{StaticResource MenuWidth}"
Style="{StaticResource NoBorderButtonStyle}"
Foreground="{ThemeResource TextColorLightBrush}"
Background="Transparent"
BorderThickness="0"
Width="{StaticResource ExpandedMenuSize}"
@@ -140,4 +216,5 @@
</DataTemplate>
</ListView.FooterTemplate>
</ListView>
</Grid>
</UserControl>

View File

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

View File

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

View File

@@ -1,7 +1,4 @@
using System;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.System;
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Input;
using ModernKeePass.ViewModels;
@@ -38,23 +35,5 @@ namespace ModernKeePass.Views.UserControls
// Stop the event from triggering twice
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:InverseBooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter"/>
</UserControl.Resources>
<Grid x:Name="Grid" DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}">
<Grid DataContext="{Binding Source={StaticResource Locator}, Path=SetCredentials}">
<Grid.Resources>
<SolidColorBrush x:Key="ErrorBrush" Color="Red" />
<SolidColorBrush x:Key="ValidBrush" Color="Green" />
@@ -35,17 +35,13 @@
x:Uid="CompositeKeyPassword"
x:Name="PasswordBox"
Password="{Binding Password, Mode=TwoWay}"
IsPasswordRevealButtonEnabled="True" >
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="GotFocus">
<core:ChangePropertyAction TargetObject="{Binding}" PropertyName="HasPassword" Value="True" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</PasswordBox>
IsEnabled="{Binding HasPassword}"
IsPasswordRevealButtonEnabled="True" />
<PasswordBox Grid.Row="1" Grid.Column="1" Height="30"
x:Uid="CompositeKeyConfirmPassword"
x:Name="ConfirmPasswordBox"
Password="{Binding ConfirmPassword, Mode=TwoWay}"
IsEnabled="{Binding HasPassword}"
IsPasswordRevealButtonEnabled="True" />
<ProgressBar Grid.Row="1" Grid.Column="1"
Maximum="128" VerticalAlignment="Bottom"
@@ -61,11 +57,9 @@
<HyperlinkButton Grid.Row="3" Grid.Column="1" Margin="-15,0,0,0"
x:Name="HyperlinkButton"
Content="{Binding KeyFileText}"
IsEnabled="{Binding HasKeyFile}"
Click="KeyFileButton_Click" />
Command="{Binding OpenKeyFileCommand}" />
<HyperlinkButton Grid.Row="3" Grid.Column="1" HorizontalAlignment="Right"
IsEnabled="{Binding HasKeyFile}"
Click="CreateKeyFileButton_Click">
Command="{Binding CreateKeyFileCommand}">
<SymbolIcon Symbol="Add">
<ToolTipService.ToolTip>
<ToolTip x:Uid="CompositeKeyNewKeyFileTooltip" />

View File

@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using Windows.Storage.AccessCache;
using Windows.Storage.Pickers;
using Windows.UI.Xaml;
using ModernKeePass.ViewModels;
using Windows.UI.Xaml;
// 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
{
private SetCredentialsVm Model => (SetCredentialsVm)Grid.DataContext;
public string ButtonLabel
{
get { return (string)GetValue(ButtonLabelProperty); }
@@ -29,40 +22,5 @@ namespace ModernKeePass.Views.UserControls
{
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:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core"
xmlns:actions="using:ModernKeePass.Actions"
mc:Ignorable="d">
<UserControl.Resources>
<Style BasedOn="{StaticResource NoBorderButtonStyle}" TargetType="Button" x:Key="MenuButtonStyle" >
<Setter Property="Padding" Value="25,0,25,0" />
<Setter Property="Height" Value="{StaticResource MenuSize}" />
<Setter Property="Height" Value="{StaticResource MenuHeight}" />
</Style>
<Style BasedOn="{StaticResource NoBorderToggleButtonStyle}" TargetType="ToggleButton" x:Key="MenuToggleButtonStyle" >
<Setter Property="Padding" Value="25,0,25,0" />
<Setter Property="Height" Value="{StaticResource MenuSize}" />
<Setter Property="Height" Value="{StaticResource MenuHeight}" />
</Style>
</UserControl.Resources>
<StackPanel x:Name="StackPanel" Orientation="Horizontal" DataContext="{Binding Source={StaticResource Locator}, Path=TopMenu}">
@@ -26,6 +29,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
@@ -36,6 +42,9 @@
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SearchButtonLabel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
@@ -56,13 +65,12 @@
</SymbolIcon>
<Button.Flyout>
<Flyout Opening="MoveButtonFlyout_OnOpening">
<StackPanel>
<StackPanel Background="Transparent">
<SearchBox x:Uid="GroupsSearch"
Padding="12" Width="350"
Width="250"
Margin="0,5,0,5"
FontSize="15"
SuggestionsRequested="SearchBox_OnSuggestionsRequested"
ResultSuggestionChosen="SearchBox_OnResultSuggestionChosen"
SuggestionsRequested="GroupSearchBox_OnSuggestionsRequested"
ResultSuggestionChosen="GroupSearchBox_OnResultSuggestionChosen"
SearchHistoryEnabled="False">
</SearchBox>
<Button x:Name="MoveButton" x:Uid="MoveButton" />
@@ -104,6 +112,38 @@
</ToolTipService.ToolTip>
</SymbolIcon>
</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>
<Button x:Name="MoreButton" Style="{StaticResource MenuButtonStyle}">
<SymbolIcon Symbol="More" />

View File

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

View File

@@ -1,7 +1,5 @@
Les problemes de corruption de bases de donn<6E>es sont maintenant totalement corrigees :
L'integrite de la base de donnees est verfiee apres chaque sauvegarde
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'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
Possibilite de deplacer des groupes et des entree
Possibilite de supprimer et restaurer des versions de l'historique d'entrees
Libraire mise a jour en version 2.44
Redesign de l'interface utilisateur (menu superieur et menu hamburger)
Le re-chargement de l'app re-ouvre la base de donnees ouverte precedemment
L'utlisateur est prevenu et un popup de sauvegarde est affiche quand il y a une erreur de sauvegarde
La fonctionnalite Sauvegarder Sous fonctionne correctement
La creation d'un nouveau groupe se fait directement dans le menu

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)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=ModernKeePass&metric=alert_status)](https://sonarcloud.io/dashboard?id=ModernKeePass)
# Introduction
**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
- Search 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
- List recently opened databases
- 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
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*)
# Contribute
@@ -43,4 +44,4 @@ Otherwise, there are still many things left to implement:
# Credits
*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
{
public class Constants
public static class Constants
{
public class Navigation
public static class Navigation
{
public static string MainPage => nameof(MainPage);
public static string EntryPage => nameof(EntryPage);
public static string GroupPage => nameof(GroupPage);
}
public class File
public static class File
{
public static int OneMegaByte => 1048576;
}
public class Settings
public static class Settings
{
public static string SaveSuspend => nameof(SaveSuspend);
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),
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
{
get { return (bool)GetValue(IsButtonEnabledProperty); }

View File

@@ -1,6 +1,4 @@
using ModernKeePass.Application.Database.Models;
namespace Messages
namespace Messages
{
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 GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views;
using MediatR;
using Messages;
@@ -15,16 +16,36 @@ namespace ModernKeePass.ViewModels
private readonly IMediator _mediator;
private readonly ISettingsProxy _settings;
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;
_settings = settings;
_navigation = navigation;
_file = file;
_resource = resource;
CreateDatabaseFileCommand = new RelayCommand(async () => await CreateDatabaseFile());
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)
{
var database = await _mediator.Send(new GetDatabaseQuery());
@@ -45,7 +66,7 @@ namespace ModernKeePass.ViewModels
Password = message.Password,
Name = "ModernKeePass",
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());

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
{
public class OpenVm: ViewModelBase
{
private readonly IFileProxy _file;
private string _name;
private string _path;
private string _token;
@@ -30,5 +36,27 @@ namespace ModernKeePass.ViewModels
get { return _path; }
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 Windows.Storage;
using Windows.Storage.AccessCache;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Views;
using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Database.Commands.CloseDatabase;
using ModernKeePass.Application.Database.Commands.SaveDatabase;
using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Common;
using RelayCommand = GalaSoft.MvvmLight.Command.RelayCommand;
using ModernKeePass.Domain.Exceptions;
namespace ModernKeePass.ViewModels
{
public class SaveVm
public class SaveVm: ViewModelBase
{
public bool IsSaveEnabled => _mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult().IsDirty;
public RelayCommand SaveAsCommand { get; }
public RelayCommand SaveCommand { get; }
public RelayCommand CloseCommand { get; }
private readonly IMediator _mediator;
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;
_navigation = navigation;
_file = file;
_resource = resource;
SaveAsCommand = new RelayCommand(async () => await SaveAs());
SaveCommand = new RelayCommand(async () => await Save(), () => IsSaveEnabled);
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());
if (close) await _mediator.Send(new CloseDatabaseCommand());
_navigation.NavigateTo(Constants.Navigation.MainPage);
await Close();
}
public async Task Save(StorageFile file)
catch (SaveException e)
{
var token = StorageApplicationPermissions.FutureAccessList.Add(file, file.Name);
await _mediator.Send(new SaveDatabaseCommand { FilePath = token });
_navigation.NavigateTo(Constants.Navigation.MainPage);
MessengerInstance.Send(new SaveErrorMessage { Message = e.Message });
}
}
public async Task Close()

View File

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

View File

@@ -5,6 +5,8 @@ using MediatR;
using Messages;
using ModernKeePass.Application.Common.Interfaces;
using ModernKeePass.Application.Security.Commands.GenerateKeyFile;
using ModernKeePass.Domain.Common;
using ModernKeePass.Domain.Dtos;
namespace ModernKeePass.ViewModels
{
@@ -12,6 +14,8 @@ namespace ModernKeePass.ViewModels
{
private readonly IMediator _mediator;
private readonly ICredentialsProxy _credentials;
private readonly IResourceProxy _resource;
private readonly IFileProxy _file;
public bool HasPassword
{
@@ -33,6 +37,8 @@ namespace ModernKeePass.ViewModels
Set(() => HasKeyFile, ref _hasKeyFile, value);
RaisePropertyChanged(nameof(IsKeyFileValid));
RaisePropertyChanged(nameof(IsValid));
OpenKeyFileCommand.RaiseCanExecuteChanged();
CreateKeyFileCommand.RaiseCanExecuteChanged();
GenerateCredentialsCommand.RaiseCanExecuteChanged();
}
}
@@ -85,6 +91,8 @@ namespace ModernKeePass.ViewModels
public bool IsKeyFileValid => HasKeyFile && !string.IsNullOrEmpty(KeyFilePath) || !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; }
private bool _hasPassword;
@@ -94,20 +102,45 @@ namespace ModernKeePass.ViewModels
private string _keyFilePath;
private string _keyFileText;
public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource)
public SetCredentialsVm(IMediator mediator, ICredentialsProxy credentials, IResourceProxy resource, IFileProxy file)
{
_mediator = mediator;
_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);
_keyFileText = resource.GetResourceValue("CompositeKeyDefaultKeyFile");
}
public async Task GenerateKeyFile()
private async Task OpenKeyFile()
{
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()
{
MessengerInstance.Send(new CredentialsSetMessage

View File

@@ -1,19 +1,46 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using GalaSoft.MvvmLight.Views;
using MediatR;
using ModernKeePass.Application.Database.Models;
using ModernKeePass.Application.Database.Queries.GetDatabase;
using ModernKeePass.Application.Entry.Models;
using ModernKeePass.Application.Group.Models;
using ModernKeePass.Application.Group.Queries.GetAllGroups;
using ModernKeePass.Application.Group.Queries.SearchEntries;
using ModernKeePass.Common;
using ModernKeePass.Models;
namespace ModernKeePass.ViewModels
{
public class TopMenuVm
{
private readonly IMediator _mediator;
private readonly INavigationService _navigation;
private readonly DatabaseVm _database;
public IEnumerable<GroupVm> Groups { get; set; }
public TopMenuVm(IMediator mediator)
public TopMenuVm(IMediator mediator, INavigationService navigation)
{
var database = mediator.Send(new GetDatabaseQuery()).GetAwaiter().GetResult();
Groups = mediator.Send(new GetAllGroupsQuery { GroupId = database.RootGroupId }).GetAwaiter().GetResult();
_mediator = mediator;
_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<IResourceProxy>());
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<IDialogService>());
SimpleIoc.Default.Register(() => App.Services.GetRequiredService<INavigationService>());

View File

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