Created a Settings Service

Created a Recent Service
Created a Resources Service
Code refactor
Updated tests
This commit is contained in:
BONNEVILLE Geoffroy
2017-12-01 17:59:38 +01:00
parent f172e31250
commit 744858df81
35 changed files with 552 additions and 141 deletions

View File

@@ -1,16 +1,13 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using Windows.ApplicationModel;
using Windows.ApplicationModel.Activation;
using Windows.Data.Json;
using Windows.Storage;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using ModernKeePass.Common;
using ModernKeePass.Exceptions;
using ModernKeePass.Interfaces;
using ModernKeePass.Services;
// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227
@@ -22,7 +19,7 @@ namespace ModernKeePass
/// </summary>
sealed partial class App
{
public DatabaseService Database { get; set; } = new DatabaseService();
public DatabaseService Database { get; private set; }
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
@@ -49,7 +46,7 @@ namespace ModernKeePass
if (!(realException is SaveException)) return;
unhandledExceptionEventArgs.Handled = true;
MessageDialogService.SaveErrorDialog(realException as SaveException, Database);
MessageDialogHelper.SaveErrorDialog(realException as SaveException, Database);
}
/// <summary>
@@ -124,6 +121,7 @@ namespace ModernKeePass
}*/
// Ensure the current window is active
Window.Current.Activate();
Database = new DatabaseService();
}
/// <summary>

View File

@@ -5,9 +5,9 @@ using Windows.UI.Popups;
using ModernKeePass.Exceptions;
using ModernKeePass.Interfaces;
namespace ModernKeePass.Services
namespace ModernKeePass.Common
{
public static class MessageDialogService
public static class MessageDialogHelper
{
public static async void ShowActionDialog(string title, string contentText, string actionButtonText, string cancelButtonText, UICommandInvokedHandler action)
{

View File

@@ -5,9 +5,9 @@ using Windows.UI.Notifications;
using ModernKeePass.Interfaces;
using ModernKeePass.ViewModels;
namespace ModernKeePass.Services
namespace ModernKeePass.Common
{
public static class ToastNotificationService
public static class ToastNotificationHelper
{
public static void ShowMovedToast(IPwEntity entity, string action, string text)
{

View File

@@ -0,0 +1,14 @@
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using Windows.Storage;
namespace ModernKeePass.Interfaces
{
public interface IRecent
{
int EntryCount { get; }
Task<IStorageItem> GetFileAsync(string token);
ObservableCollection<IRecentItem> GetAllFiles(bool removeIfNonExistant = true);
void Add(IStorageItem file, string metadata);
}
}

View File

@@ -0,0 +1,11 @@
using Windows.Storage;
namespace ModernKeePass.Interfaces
{
public interface IRecentItem
{
StorageFile DatabaseFile { get; }
string Token { get; }
string Name { get; }
}
}

View File

@@ -0,0 +1,7 @@
namespace ModernKeePass.Interfaces
{
public interface IResource
{
string GetResourceValue(string key);
}
}

View File

@@ -0,0 +1,8 @@
namespace ModernKeePass.Interfaces
{
public interface ISettings
{
T GetSetting<T>(string property);
void PutSetting<T>(string property, T value);
}
}

View File

@@ -113,18 +113,24 @@
<Compile Include="App.xaml.cs">
<DependentUpon>App.xaml</DependentUpon>
</Compile>
<Compile Include="Interfaces\IRecent.cs" />
<Compile Include="Interfaces\IRecentItem.cs" />
<Compile Include="Interfaces\IResource.cs" />
<Compile Include="Pages\MainPageFrames\DonatePage.xaml.cs">
<DependentUpon>DonatePage.xaml</DependentUpon>
</Compile>
<Compile Include="Services\DatabaseService.cs" />
<Compile Include="Services\MessageDialogService.cs" />
<Compile Include="Interfaces\ISettings.cs" />
<Compile Include="Common\MessageDialogHelper.cs" />
<Compile Include="Common\NavigationHelper.cs" />
<Compile Include="Common\NotifyPropertyChangedBase.cs" />
<Compile Include="Common\ObservableDictionary.cs" />
<Compile Include="Common\RelayCommand.cs" />
<Compile Include="Common\SuspensionManager.cs" />
<Compile Include="Services\RecentService.cs" />
<Compile Include="Services\ResourcesService.cs" />
<Compile Include="Services\SettingsService.cs" />
<Compile Include="Services\ToastNotificationService.cs" />
<Compile Include="Common\ToastNotificationHelper.cs" />
<Compile Include="Converters\DiscreteIntToSolidColorBrushConverter.cs" />
<Compile Include="Converters\EmptyStringToVisibilityConverter.cs" />
<Compile Include="Converters\NullToBooleanConverter.cs" />
@@ -218,6 +224,7 @@
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
<PRIResource Include="Strings\en-US\CodeBehind.resw" />
<PRIResource Include="Strings\en-US\Resources.resw" />
</ItemGroup>
<ItemGroup>

View File

@@ -394,11 +394,11 @@
</AppBarButton>
</CommandBar.SecondaryCommands>
<AppBarToggleButton Icon="Edit" Label="Edit" IsChecked="{Binding IsEditMode, Mode=TwoWay}">
<!--<interactivity:Interaction.Behaviors>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=CommandBar}" PropertyName="IsOpen" Value="False" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>-->
</interactivity:Interaction.Behaviors>
</AppBarToggleButton>
<AppBarButton Icon="Undo" Label="Restore" Visibility="{Binding ParentGroup.IsSelected, Converter={StaticResource BooleanToVisibilityConverter}}" IsEnabled="{Binding PreviousGroup, Converter={StaticResource NullToBooleanConverter}}" Click="RestoreButton_Click">
<interactivity:Interaction.Behaviors>

View File

@@ -77,9 +77,9 @@ namespace ModernKeePass.Pages
? "Are you sure you want to send this entry to the recycle bin?"
: "Are you sure you want to delete this entry?";
var text = isRecycleBinEnabled ? "Item moved to the Recycle bin" : "Item permanently removed";
MessageDialogService.ShowActionDialog("Warning", message, "Delete", "Cancel", a =>
MessageDialogHelper.ShowActionDialog("Warning", message, "Delete", "Cancel", a =>
{
ToastNotificationService.ShowMovedToast(Model, "Deleting", text);
ToastNotificationHelper.ShowMovedToast(Model, "Deleting", text);
Model.MarkForDelete();
if (Frame.CanGoBack) Frame.GoBack();
});
@@ -87,7 +87,7 @@ namespace ModernKeePass.Pages
private void RestoreButton_Click(object sender, RoutedEventArgs e)
{
ToastNotificationService.ShowMovedToast(Model, "Restored", "Item returned to its original group");
ToastNotificationHelper.ShowMovedToast(Model, "Restored", "Item returned to its original group");
if (Frame.CanGoBack) Frame.GoBack();
}
@@ -100,7 +100,7 @@ namespace ModernKeePass.Pages
}
catch (Exception ex)
{
MessageDialogService.ShowErrorDialog(ex);
MessageDialogHelper.ShowErrorDialog(ex);
}
}
}

View File

@@ -64,11 +64,11 @@
</Button.Flyout>
</AppBarButton>
<AppBarToggleButton Icon="Edit" Label="Edit" IsChecked="{Binding IsEditMode, Mode=TwoWay}">
<!--<interactivity:Interaction.Behaviors>
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Click">
<core:ChangePropertyAction TargetObject="{Binding ElementName=CommandBar}" PropertyName="IsOpen" Value="False" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>-->
</interactivity:Interaction.Behaviors>
</AppBarToggleButton>
<AppBarButton Icon="Undo" Label="Restore" Visibility="{Binding ShowRestore, Converter={StaticResource BooleanToVisibilityConverter}}" IsEnabled="{Binding PreviousGroup, Converter={StaticResource NullToBooleanConverter}}" Click="RestoreButton_Click">
<interactivity:Interaction.Behaviors>

View File

@@ -115,9 +115,9 @@ namespace ModernKeePass.Pages
? "Are you sure you want to send the whole group and all its entries to the recycle bin?"
: "Are you sure you want to delete the whole group and all its entries?";
var text = isRecycleBinEnabled ? "Item moved to the Recycle bin" : "Item permanently removed";
MessageDialogService.ShowActionDialog("Warning", message, "Delete", "Cancel", a =>
MessageDialogHelper.ShowActionDialog("Warning", message, "Delete", "Cancel", a =>
{
ToastNotificationService.ShowMovedToast(Model, "Deleting", text);
ToastNotificationHelper.ShowMovedToast(Model, "Deleting", text);
Model.MarkForDelete();
if (Frame.CanGoBack) Frame.GoBack();
});
@@ -125,7 +125,7 @@ namespace ModernKeePass.Pages
private void RestoreButton_Click(object sender, RoutedEventArgs e)
{
ToastNotificationService.ShowMovedToast(Model, "Restored", "Item returned to its original group");
ToastNotificationHelper.ShowMovedToast(Model, "Restored", "Item returned to its original group");
if (Frame.CanGoBack) Frame.GoBack();
}

View File

@@ -18,7 +18,7 @@ namespace ModernKeePass.Pages
private void CompositeKeyUserControl_OnValidationChecked(object sender, PasswordEventArgs e)
{
ToastNotificationService.ShowGenericToast("Composite key", "Database successfully updated.");
ToastNotificationHelper.ShowGenericToast("Composite key", "Database successfully updated.");
}
}
}

View File

@@ -26,6 +26,8 @@ namespace ModernKeePass.Services
Opened = 2
}
private readonly PwDatabase _pwDatabase = new PwDatabase();
private readonly ISettings _settings;
private readonly IResource _resource;
private StorageFile _databaseFile;
private GroupVm _recycleBin;
@@ -77,7 +79,15 @@ namespace ModernKeePass.Services
get { return _pwDatabase.KdfParameters; }
set { _pwDatabase.KdfParameters = value; }
}
public DatabaseService() : this(new SettingsService())
{ }
public DatabaseService(ISettings settings)
{
_settings = settings;
}
/// <summary>
/// Open a KeePass database
/// </summary>
@@ -99,8 +109,8 @@ namespace ModernKeePass.Services
_pwDatabase.New(ioConnection, key);
//Get settings default values
if (SettingsService.GetSetting<bool>("Sample")) CreateSampleData();
var fileFormat = SettingsService.GetSetting<string>("DefaultFileFormat");
if (_settings.GetSetting<bool>("Sample")) CreateSampleData();
var fileFormat = _settings.GetSetting<string>("DefaultFileFormat");
switch (fileFormat)
{
case "4":

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using ModernKeePass.Interfaces;
using Windows.Storage;
using Windows.Storage.AccessCache;
using ModernKeePass.ViewModels;
namespace ModernKeePass.Services
{
public class RecentService : IRecent
{
private readonly StorageItemMostRecentlyUsedList _mru = StorageApplicationPermissions.MostRecentlyUsedList;
public int EntryCount => _mru.Entries.Count;
public ObservableCollection<IRecentItem> GetAllFiles(bool removeIfNonExistant = true)
{
var result = new ObservableCollection<IRecentItem>();
foreach (var entry in _mru.Entries)
{
try
{
var file = _mru.GetFileAsync(entry.Token, AccessCacheOptions.SuppressAccessTimeUpdate).GetAwaiter().GetResult();
result.Add(new RecentItemVm(entry.Token, entry.Metadata, file));
}
catch (Exception)
{
if (removeIfNonExistant) _mru.Remove(entry.Token);
}
}
return result;
}
public void Add(IStorageItem file, string metadata)
{
_mru.Add(file, metadata);
}
public async Task<IStorageItem> GetFileAsync(string token)
{
return await _mru.GetFileAsync(token);
}
}
}

View File

@@ -0,0 +1,17 @@
using Windows.ApplicationModel.Resources;
using ModernKeePass.Interfaces;
namespace ModernKeePass.Services
{
public class ResourcesService: IResource
{
private const string ResourceFileName = "CodeBehind";
private readonly ResourceLoader _resourceLoader = ResourceLoader.GetForCurrentView();
public string GetResourceValue(string key)
{
var resource = _resourceLoader.GetString($"/{ResourceFileName}/{key}");
return resource;
}
}
}

View File

@@ -1,15 +1,19 @@
using System;
using Windows.Foundation.Collections;
using Windows.Storage;
using ModernKeePass.Interfaces;
namespace ModernKeePass.Services
{
public class SettingsService
public class SettingsService : ISettings
{
public static T GetSetting<T>(string property)
private readonly IPropertySet _values = ApplicationData.Current.LocalSettings.Values;
public T GetSetting<T>(string property)
{
try
{
return (T)Convert.ChangeType(ApplicationData.Current.LocalSettings.Values[property], typeof(T));
return (T)Convert.ChangeType(_values[property], typeof(T));
}
catch (InvalidCastException)
{
@@ -17,12 +21,11 @@ namespace ModernKeePass.Services
}
}
public static void PutSetting<T>(string property, T value)
public void PutSetting<T>(string property, T value)
{
var localSettings = ApplicationData.Current.LocalSettings;
if (localSettings.Values.ContainsKey(property))
localSettings.Values[property] = value;
else localSettings.Values.Add(property, value);
if (_values.ContainsKey(property))
_values[property] = value;
else _values.Add(property, value);
}
}
}

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CompositeKeyDefaultKeyFile" xml:space="preserve">
<value>Select key file from disk...</value>
</data>
<data name="CompositeKeyErrorOpen" xml:space="preserve">
<value>Error</value>
</data>
<data name="CompositeKeyErrorUserKeyFile" xml:space="preserve">
<value>key file</value>
</data>
<data name="CompositeKeyErrorUserOr" xml:space="preserve">
<value> or </value>
</data>
<data name="CompositeKeyErrorUserPassword" xml:space="preserve">
<value>password</value>
</data>
<data name="CompositeKeyErrorUserStart" xml:space="preserve">
<value>Error: wrong </value>
</data>
<data name="CompositeKeyUpdated" xml:space="preserve">
<value>Database composite key updated.</value>
</data>
<data name="EntryNew" xml:space="preserve">
<value>&lt; New entry &gt;</value>
</data>
<data name="GroupNew" xml:space="preserve">
<value>&lt; New group &gt;</value>
</data>
<data name="MainMenuItemAbout" xml:space="preserve">
<value>About</value>
</data>
<data name="MainMenuItemDonate" xml:space="preserve">
<value>Donate</value>
</data>
<data name="MainMenuItemNew" xml:space="preserve">
<value>New</value>
</data>
<data name="MainMenuItemOpen" xml:space="preserve">
<value>Open</value>
</data>
<data name="MainMenuItemRecent" xml:space="preserve">
<value>Recent</value>
</data>
<data name="MainMenuItemSave" xml:space="preserve">
<value>Save</value>
</data>
<data name="MainMenuItemSettings" xml:space="preserve">
<value>Settings</value>
</data>
<data name="SettingsMenuGroupApplication" xml:space="preserve">
<value>Application</value>
</data>
<data name="SettingsMenuGroupDatabase" xml:space="preserve">
<value>Database</value>
</data>
<data name="SettingsMenuItemGeneral" xml:space="preserve">
<value>General</value>
</data>
<data name="SettingsMenuItemNew" xml:space="preserve">
<value>New</value>
</data>
<data name="SettingsMenuItemSecurity" xml:space="preserve">
<value>Security</value>
</data>
</root>

View File

@@ -29,7 +29,8 @@ namespace ModernKeePass.ViewModels
private string _status;
private StatusTypes _statusType;
private StorageFile _keyFile;
private string _keyFileText = "Select key file from disk...";
private string _keyFileText;
private readonly IResource _resource;
public IDatabase Database { get; set; }
@@ -100,10 +101,12 @@ namespace ModernKeePass.ViewModels
public double PasswordComplexityIndicator => QualityEstimation.EstimatePasswordBits(Password?.ToCharArray());
public CompositeKeyVm() : this((Application.Current as App)?.Database) { }
public CompositeKeyVm() : this((Application.Current as App)?.Database, new ResourcesService()) { }
public CompositeKeyVm(IDatabase database)
public CompositeKeyVm(IDatabase database, IResource resource)
{
_resource = resource;
_keyFileText = _resource.GetResourceValue("CompositeKeyDefaultKeyFile");
Database = database;
}
@@ -116,18 +119,18 @@ namespace ModernKeePass.ViewModels
}
catch (Exception e)
{
error = $"Error: {e.Message}";
error = $"{_resource.GetResourceValue("CompositeKeyErrorOpen")}: {e.Message}";
}
switch ((DatabaseService.DatabaseStatus)Database.Status)
{
case DatabaseService.DatabaseStatus.Opened:
await Task.Run( () => RootGroup = Database.RootGroup);
await Task.Run(() => RootGroup = Database.RootGroup);
return true;
case DatabaseService.DatabaseStatus.CompositeKeyError:
var errorMessage = new StringBuilder("Error: wrong ");
if (HasPassword) errorMessage.Append("password");
if (HasPassword && HasKeyFile) errorMessage.Append(" or ");
if (HasKeyFile) errorMessage.Append("key file");
var errorMessage = new StringBuilder(_resource.GetResourceValue("CompositeKeyErrorUserStart"));
if (HasPassword) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserPassword"));
if (HasPassword && HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserOr"));
if (HasKeyFile) errorMessage.Append(_resource.GetResourceValue("CompositeKeyErrorUserKeyFile"));
UpdateStatus(errorMessage.ToString(), StatusTypes.Error);
break;
case DatabaseService.DatabaseStatus.Error:
@@ -140,7 +143,7 @@ namespace ModernKeePass.ViewModels
public void UpdateKey()
{
Database.UpdateCompositeKey(CreateCompositeKey());
UpdateStatus("Database composite key updated.", StatusTypes.Success);
UpdateStatus(_resource.GetResourceValue("CompositeKeyUpdated"), StatusTypes.Success);
}
public void CreateKeyFile(StorageFile file)

View File

@@ -5,6 +5,7 @@ using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using ModernKeePass.Interfaces;
using ModernKeePass.Mappings;
using ModernKeePass.Services;
using ModernKeePassLib;
using ModernKeePassLib.Cryptography.PasswordGenerator;
using ModernKeePassLib.Security;
@@ -48,8 +49,9 @@ namespace ModernKeePass.ViewModels
{
get
{
var title = GetEntryValue(PwDefs.TitleField);
return title == null ? "< New entry >" : title;
/*var title = GetEntryValue(PwDefs.TitleField);
return title == null ? _resource.GetResourceValue("EntryNew") : title;*/
return GetEntryValue(PwDefs.TitleField);
}
set { SetEntryValue(PwDefs.TitleField, value); }
}

View File

@@ -19,10 +19,10 @@ namespace ModernKeePass.ViewModels
public GroupVm ParentGroup { get; private set; }
public GroupVm PreviousGroup { get; private set; }
public ObservableCollection<EntryVm> Entries { get; set; } = new ObservableCollection<EntryVm>();
public ObservableCollection<GroupVm> Groups { get; set; } = new ObservableCollection<GroupVm>();
public int EntryCount => Entries.Count() - 1;
public int EntryCount => Entries.Count;
// TODO: put in a converter
public FontWeight FontWeight => _pwGroup == null ? FontWeights.Bold : FontWeights.Normal;
public int GroupCount => Groups.Count - 1;
public PwUuid IdUuid => _pwGroup?.Uuid;
@@ -50,7 +50,7 @@ namespace ModernKeePass.ViewModels
public string Name
{
get { return _pwGroup == null ? "< New group >" : _pwGroup.Name; }
get { return _pwGroup == null ? string.Empty : _pwGroup.Name; }
set { _pwGroup.Name = value; }
}
@@ -86,10 +86,9 @@ namespace ModernKeePass.ViewModels
private readonly IDatabase _database;
private bool _isEditMode;
private PwEntry _reorderedEntry;
//private int _reorderedEntryIndex;
public GroupVm() {}
internal GroupVm(PwGroup pwGroup, GroupVm parent, PwUuid recycleBinId = null) : this(pwGroup, parent,
(Application.Current as App)?.Database, recycleBinId)
{ }
@@ -104,7 +103,7 @@ namespace ModernKeePass.ViewModels
Entries = new ObservableCollection<EntryVm>(pwGroup.Entries.Select(e => new EntryVm(e, this)));
Entries.CollectionChanged += Entries_CollectionChanged;
Groups = new ObservableCollection<GroupVm>(pwGroup.Groups.Select(g => new GroupVm(g, this, recycleBinId)));
Groups.Insert(0, new GroupVm ());
Groups.Insert(0, new GroupVm());
}
private void Entries_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
@@ -148,7 +147,6 @@ namespace ModernKeePass.ViewModels
Move(_database.RecycleBinEnabled && !IsSelected ? _database.RecycleBin : null);
}
public void UndoDelete()
{
Move(PreviousGroup);
@@ -192,7 +190,7 @@ namespace ModernKeePass.ViewModels
}
catch (Exception e)
{
MessageDialogService.ShowErrorDialog(e);
MessageDialogHelper.ShowErrorDialog(e);
}
}
@@ -206,7 +204,7 @@ namespace ModernKeePass.ViewModels
}
catch (Exception e)
{
MessageDialogService.ShowErrorDialog(e);
MessageDialogHelper.ShowErrorDialog(e);
}
}

View File

@@ -1,24 +1,15 @@
using System;
using Windows.Storage;
using Windows.Storage;
using ModernKeePass.Common;
using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using ModernKeePass.Interfaces;
using ModernKeePass.Services;
namespace ModernKeePass.ViewModels
{
public class RecentItemVm: NotifyPropertyChangedBase, ISelectableModel
public class RecentItemVm: NotifyPropertyChangedBase, ISelectableModel, IRecentItem
{
private bool _isSelected;
public RecentItemVm() {}
public RecentItemVm(AccessListEntry entry, StorageFile file)
{
Token = entry.Token;
Name = entry.Metadata;
DatabaseFile = file;
}
public StorageFile DatabaseFile { get; }
public string Token { get; }
public string Name { get; }
@@ -30,6 +21,14 @@ namespace ModernKeePass.ViewModels
set { SetProperty(ref _isSelected, value); }
}
public RecentItemVm() {}
public RecentItemVm(string token, string metadata, IStorageItem file)
{
Token = token;
Name = metadata;
DatabaseFile = file as StorageFile;
}
public void OpenDatabaseFile()
{
OpenDatabaseFile((Application.Current as App)?.Database);
@@ -40,10 +39,14 @@ namespace ModernKeePass.ViewModels
database.DatabaseFile = DatabaseFile;
}
public async void UpdateAccessTime()
public void UpdateAccessTime()
{
var mru = StorageApplicationPermissions.MostRecentlyUsedList;
await mru.GetFileAsync(Token);
UpdateAccessTime(new RecentService());
}
public async void UpdateAccessTime(IRecent recent)
{
await recent.GetFileAsync(Token);
}
}
}

View File

@@ -11,6 +11,7 @@ using ModernKeePassLib.Cryptography.KeyDerivation;
namespace ModernKeePass.ViewModels
{
// TODO: implement Kdf settings
public class SettingsDatabaseVm: NotifyPropertyChangedBase, IHasSelectableObject
{
private readonly IDatabase _database;

View File

@@ -1,23 +1,33 @@
using System.Collections.Generic;
using ModernKeePass.Interfaces;
using ModernKeePass.Services;
namespace ModernKeePass.ViewModels
{
public class SettingsNewVm
{
private ISettings _settings;
public SettingsNewVm() : this(new SettingsService())
{ }
public SettingsNewVm(ISettings settings)
{
_settings = settings;
}
public bool IsCreateSample
{
get { return SettingsService.GetSetting<bool>("Sample"); }
set { SettingsService.PutSetting("Sample", value); }
get { return _settings.GetSetting<bool>("Sample"); }
set { _settings.PutSetting("Sample", value); }
}
public IEnumerable<string> FileFormats => new []{"2", "4"};
public string FileFormatVersion
{
get { return SettingsService.GetSetting<string>("DefaultFileFormat"); }
set { SettingsService.PutSetting("DefaultFileFormat", value); }
get { return _settings.GetSetting<string>("DefaultFileFormat"); }
set { _settings.PutSetting("DefaultFileFormat", value); }
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using Windows.ApplicationModel;
using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using ModernKeePass.Common;
@@ -46,40 +45,68 @@ namespace ModernKeePass.ViewModels
public MainVm() {}
internal MainVm(Frame referenceFrame, Frame destinationFrame) : this(referenceFrame, destinationFrame, (Application.Current as App)?.Database) { }
internal MainVm(Frame referenceFrame, Frame destinationFrame) : this(referenceFrame, destinationFrame,
(Application.Current as App)?.Database, new ResourcesService(), new RecentService())
{ }
public MainVm(Frame referenceFrame, Frame destinationFrame, IDatabase database)
public MainVm(Frame referenceFrame, Frame destinationFrame, IDatabase database, IResource resource, IRecent recent)
{
var mru = StorageApplicationPermissions.MostRecentlyUsedList;
var isDatabaseOpen = database != null && database.Status == (int) DatabaseService.DatabaseStatus.Opened;
var mainMenuItems = new ObservableCollection<MainMenuItemVm>
{
new MainMenuItemVm
{
Title = "Open", PageType = typeof(OpenDatabasePage), Destination = destinationFrame, Parameter = referenceFrame, SymbolIcon = Symbol.Page2,
Title = resource.GetResourceValue("MainMenuItemOpen"),
PageType = typeof(OpenDatabasePage),
Destination = destinationFrame,
Parameter = referenceFrame,
SymbolIcon = Symbol.Page2,
IsSelected = database != null && database.Status == (int) DatabaseService.DatabaseStatus.Opening
},
new MainMenuItemVm
{
Title = "New" , PageType = typeof(NewDatabasePage), Destination = destinationFrame, Parameter = referenceFrame, SymbolIcon = Symbol.Add
Title = resource.GetResourceValue("MainMenuItemNew"),
PageType = typeof(NewDatabasePage),
Destination = destinationFrame,
Parameter = referenceFrame,
SymbolIcon = Symbol.Add
},
new MainMenuItemVm
{
Title = "Save" , PageType = typeof(SaveDatabasePage), Destination = destinationFrame, Parameter = referenceFrame, SymbolIcon = Symbol.Save,
IsSelected = isDatabaseOpen, IsEnabled = isDatabaseOpen
},
new MainMenuItemVm {
Title = "Recent" , PageType = typeof(RecentDatabasesPage), Destination = destinationFrame, Parameter = referenceFrame, SymbolIcon = Symbol.Copy,
IsSelected = (database == null || database.Status == (int) DatabaseService.DatabaseStatus.Closed) && mru.Entries.Count > 0, IsEnabled = mru.Entries.Count > 0
Title = resource.GetResourceValue("MainMenuItemSave"),
PageType = typeof(SaveDatabasePage),
Destination = destinationFrame,
Parameter = referenceFrame,
SymbolIcon = Symbol.Save,
IsSelected = isDatabaseOpen,
IsEnabled = isDatabaseOpen
},
new MainMenuItemVm
{
Title = "Settings" , PageType = typeof(SettingsPage), Destination = referenceFrame, SymbolIcon = Symbol.Setting
Title = resource.GetResourceValue("MainMenuItemRecent"),
PageType = typeof(RecentDatabasesPage),
Destination = destinationFrame,
Parameter = referenceFrame,
SymbolIcon = Symbol.Copy,
IsSelected =
(database == null || database.Status == (int) DatabaseService.DatabaseStatus.Closed) &&
recent.EntryCount > 0,
IsEnabled = recent.EntryCount > 0
},
new MainMenuItemVm
{
Title = "About" , PageType = typeof(AboutPage), Destination = destinationFrame, SymbolIcon = Symbol.Help
Title = resource.GetResourceValue("MainMenuItemSettings"),
PageType = typeof(SettingsPage),
Destination = referenceFrame,
SymbolIcon = Symbol.Setting
},
new MainMenuItemVm
{
Title = resource.GetResourceValue("MainMenuItemAbout"),
PageType = typeof(AboutPage),
Destination = destinationFrame,
SymbolIcon = Symbol.Help
}
};
// Auto-select the Recent Items menu item if the conditions are met

View File

@@ -1,5 +1,4 @@
using Windows.Storage;
using Windows.Storage.AccessCache;
using Windows.UI.Xaml;
using ModernKeePass.Common;
using ModernKeePass.Interfaces;
@@ -23,19 +22,23 @@ namespace ModernKeePass.ViewModels
if (database == null || database.Status != (int) DatabaseService.DatabaseStatus.Opening) return;
OpenFile(database.DatabaseFile);
}
public void OpenFile(StorageFile file)
{
OpenFile(file, new RecentService());
}
public void OpenFile(StorageFile file, IRecent recent)
{
_database.DatabaseFile = file;
OnPropertyChanged("Name");
OnPropertyChanged("ShowPasswordBox");
AddToRecentList(file);
AddToRecentList(file, recent);
}
private void AddToRecentList(StorageFile file)
private void AddToRecentList(StorageFile file, IRecent recent)
{
var mru = StorageApplicationPermissions.MostRecentlyUsedList;
mru.Add(file, file.DisplayName);
recent.Add(file, file.DisplayName);
}
}
}

View File

@@ -1,17 +1,16 @@
using System;
using System.Collections.ObjectModel;
using Windows.Storage.AccessCache;
using System.Collections.ObjectModel;
using ModernKeePass.Common;
using ModernKeePass.Interfaces;
using ModernKeePass.Services;
namespace ModernKeePass.ViewModels
{
public class RecentVm : NotifyPropertyChangedBase, IHasSelectableObject
{
private ISelectableModel _selectedItem;
private ObservableCollection<RecentItemVm> _recentItems = new ObservableCollection<RecentItemVm>();
private ObservableCollection<IRecentItem> _recentItems = new ObservableCollection<IRecentItem>();
public ObservableCollection<RecentItemVm> RecentItems
public ObservableCollection<IRecentItem> RecentItems
{
get { return _recentItems; }
set { SetProperty(ref _recentItems, value); }
@@ -35,23 +34,14 @@ namespace ModernKeePass.ViewModels
}
}
public RecentVm()
public RecentVm() : this (new RecentService())
{ }
public RecentVm(IRecent recent)
{
var mru = StorageApplicationPermissions.MostRecentlyUsedList;
foreach (var entry in mru.Entries)
{
try
{
var file = mru.GetFileAsync(entry.Token, AccessCacheOptions.SuppressAccessTimeUpdate).GetAwaiter().GetResult();
RecentItems.Add(new RecentItemVm(entry, file));
}
catch (Exception)
{
mru.Remove(entry.Token);
}
}
RecentItems = recent.GetAllFiles();
if (RecentItems.Count > 0)
SelectedItem = RecentItems[0];
SelectedItem = RecentItems[0] as RecentItemVm;
}
}

View File

@@ -6,6 +6,7 @@ using ModernKeePass.Common;
using ModernKeePass.Interfaces;
using ModernKeePass.Pages;
using ModernKeePass.Pages.SettingsPageFrames;
using ModernKeePass.Services;
namespace ModernKeePass.ViewModels
{
@@ -41,32 +42,32 @@ namespace ModernKeePass.ViewModels
}
}
public SettingsVm() : this((Application.Current as App)?.Database) { }
public SettingsVm() : this((Application.Current as App)?.Database, new ResourcesService()) { }
public SettingsVm(IDatabase database)
public SettingsVm(IDatabase database, IResource resource)
{
var menuItems = new ObservableCollection<ListMenuItemVm>
{
new ListMenuItemVm
{
Title = "New",
Group = "Application",
Title = resource.GetResourceValue("SettingsMenuItemNew"),
Group = resource.GetResourceValue("SettingsMenuGroupApplication"),
SymbolIcon = Symbol.Add,
PageType = typeof(SettingsNewDatabasePage),
IsSelected = true
},
new ListMenuItemVm
{
Title = "General",
Group = "Database",
Title = resource.GetResourceValue("SettingsMenuItemGeneral"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Setting,
PageType = typeof(SettingsDatabasePage),
IsEnabled = database?.Status == 2
},
new ListMenuItemVm
{
Title = "Security",
Group = "Database",
Title = resource.GetResourceValue("SettingsMenuItemSecurity"),
Group = resource.GetResourceValue("SettingsMenuGroupDatabase"),
SymbolIcon = Symbol.Permissions,
PageType = typeof(SettingsSecurityPage),
IsEnabled = database?.Status == 2

View File

@@ -4,13 +4,14 @@ using Windows.Storage;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using ModernKeePass.Services;
using ModernKeePass.ViewModels;
using ModernKeePassApp.Test.Mock;
namespace ModernKeePassApp.Test
{
[TestClass]
public class DatabaseTests
{
private readonly DatabaseService _database = new DatabaseService();
private readonly DatabaseService _database = new DatabaseService(new SettingsServiceMock());
[TestMethod]
public void TestCreate()
@@ -47,12 +48,12 @@ namespace ModernKeePassApp.Test
{
_database.Open(null, createNew);
Assert.AreEqual((int)DatabaseService.DatabaseStatus.NoCompositeKey, _database.Status);
var compositeKey = new CompositeKeyVm(_database)
var compositeKey = new CompositeKeyVm(_database, new ResourceServiceMock())
{
HasPassword = true,
Password = "test"
};
compositeKey.OpenDatabase(createNew);
compositeKey.OpenDatabase(createNew).GetAwaiter().GetResult();
Assert.AreEqual((int)DatabaseService.DatabaseStatus.Opened, _database.Status);
}
}

View File

@@ -8,7 +8,7 @@ using Windows.Storage;
namespace ModernKeePassApp.Test.Mock
{
public class DatabaseHelperMock : IDatabase
public class DatabaseServiceMock : IDatabase
{
public PwCompressionAlgorithm CompressionAlgorithm { get; set; }

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;
using ModernKeePass.Interfaces;
using Windows.Storage;
namespace ModernKeePassApp.Test.Mock
{
class RecentServiceMock : IRecent
{
public int EntryCount => 0;
public void Add(IStorageItem file, string metadata)
{
throw new NotImplementedException();
}
public ObservableCollection<IRecentItem> GetAllFiles(bool removeIfNonExistant = true)
{
throw new NotImplementedException();
}
public Task<IStorageItem> GetFileAsync(string token)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,13 @@
using System;
using ModernKeePass.Interfaces;
namespace ModernKeePassApp.Test.Mock
{
class ResourceServiceMock : IResource
{
public string GetResourceValue(string key)
{
return string.Empty;
}
}
}

View File

@@ -0,0 +1,18 @@
using System;
using ModernKeePass.Interfaces;
namespace ModernKeePassApp.Test.Mock
{
public class SettingsServiceMock : ISettings
{
public T GetSetting<T>(string property)
{
return default(T);
}
public void PutSetting<T>(string property, T value)
{
throw new NotImplementedException();
}
}
}

View File

@@ -120,7 +120,10 @@
</ItemGroup>
<ItemGroup>
<Compile Include="DatabaseTests.cs" />
<Compile Include="Mock\DatabaseHelperMock.cs" />
<Compile Include="Mock\DatabaseServiceMock.cs" />
<Compile Include="Mock\RecentServiceMock.cs" />
<Compile Include="Mock\ResourceServiceMock.cs" />
<Compile Include="Mock\SettingsServiceMock.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewModelsTests.cs" />
</ItemGroup>

View File

@@ -3,6 +3,8 @@ using System.Linq;
using Windows.ApplicationModel;
using Windows.Storage.AccessCache;
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using ModernKeePass.Pages;
using ModernKeePass.Pages.SettingsPageFrames;
using ModernKeePass.ViewModels;
using ModernKeePassApp.Test.Mock;
@@ -11,6 +13,9 @@ namespace ModernKeePassApp.Test
[TestClass]
public class ViewModelsTests
{
private RecentServiceMock _recent = new RecentServiceMock();
private ResourceServiceMock _resource = new ResourceServiceMock();
[TestMethod]
public void TestAboutVm()
{
@@ -21,29 +26,30 @@ namespace ModernKeePassApp.Test
[TestMethod]
public void TestMainVm()
{
var database = new DatabaseHelperMock();
var mainVm = new MainVm(null, null, database);
var database = new DatabaseServiceMock();
var mainVm = new MainVm(null, null, database, _resource, _recent);
Assert.AreEqual(1, mainVm.MainMenuItems.Count());
var firstGroup = mainVm.MainMenuItems.FirstOrDefault();
Assert.AreEqual(6, firstGroup.Count());
database.Status = 1;
mainVm = new MainVm(null, null, database);
mainVm = new MainVm(null, null, database, _resource, _recent);
Assert.IsNotNull(mainVm.SelectedItem);
Assert.AreEqual("Open", ((MainMenuItemVm) mainVm.SelectedItem).Title);
Assert.AreEqual(typeof(OpenDatabasePage), ((MainMenuItemVm) mainVm.SelectedItem).PageType);
database.Status = 2;
mainVm = new MainVm(null, null, database);
mainVm = new MainVm(null, null, database, _resource, _recent);
Assert.IsNotNull(mainVm.SelectedItem);
Assert.AreEqual(2, mainVm.MainMenuItems.Count());
Assert.AreEqual("Save", ((MainMenuItemVm) mainVm.SelectedItem).Title);
Assert.AreEqual(typeof(SaveDatabasePage), ((MainMenuItemVm) mainVm.SelectedItem).PageType);
}
[TestMethod]
public void TestCompositeKeyVm()
{
var database = new DatabaseHelperMock();
var compositeKeyVm = new CompositeKeyVm(database);
var database = new DatabaseServiceMock();
var compositeKeyVm = new CompositeKeyVm(database, _resource);
Assert.IsTrue(compositeKeyVm.OpenDatabase(false).GetAwaiter().GetResult());
compositeKeyVm.StatusType = 1;
compositeKeyVm.Password = "test";
@@ -54,7 +60,7 @@ namespace ModernKeePassApp.Test
[TestMethod]
public void TestOpenVm()
{
var database = new DatabaseHelperMock
var database = new DatabaseServiceMock
{
Status = 1,
DatabaseFile = Package.Current.InstalledLocation.GetFileAsync(@"Databases\TestDatabase.kdbx")
@@ -78,7 +84,7 @@ namespace ModernKeePassApp.Test
.GetAwaiter().GetResult(), "MockDatabase");
var recentVm = new RecentVm();
Assert.IsTrue(recentVm.RecentItems.Count == 1);
recentVm.SelectedItem = recentVm.RecentItems.FirstOrDefault();
recentVm.SelectedItem = recentVm.RecentItems.FirstOrDefault() as RecentItemVm;
Assert.IsTrue(recentVm.SelectedItem.IsSelected);
mru.Clear();
}
@@ -91,13 +97,14 @@ namespace ModernKeePassApp.Test
[TestMethod]
public void TestSettingsVm()
{
var settingsVm = new SettingsVm();
var settingsVm = new SettingsVm(new DatabaseServiceMock(), _resource);
Assert.AreEqual(1, settingsVm.MenuItems.Count());
var firstGroup = settingsVm.MenuItems.FirstOrDefault();
Assert.AreEqual(1, firstGroup.Count());
// All groups have an empty title, so all settings are put inside the empty group
Assert.AreEqual(3, firstGroup.Count());
Assert.IsNotNull(settingsVm.SelectedItem);
var selectedItem = (ListMenuItemVm) settingsVm.SelectedItem;
Assert.AreEqual("New", selectedItem.Title);
Assert.AreEqual(typeof(SettingsNewDatabasePage), selectedItem.PageType);
}
}
}