mirror of
https://github.com/wismna/ModernKeePass.git
synced 2025-10-03 15:40:18 -04:00
Create a shared project with all Win App common files (8.1 and 10)
Finally use the dependency injected Resource Service
This commit is contained in:
15
WinAppCommon/Common/Constants.cs
Normal file
15
WinAppCommon/Common/Constants.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace ModernKeePass.Common
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public class File
|
||||
{
|
||||
public static int OneMegaByte => 1048576;
|
||||
}
|
||||
|
||||
public class Settings
|
||||
{
|
||||
public static string SaveSuspend => nameof(SaveSuspend);
|
||||
}
|
||||
}
|
||||
}
|
56
WinAppCommon/Common/MessageDialogHelper.cs
Normal file
56
WinAppCommon/Common/MessageDialogHelper.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
432
WinAppCommon/Common/NavigationHelper.cs
Normal file
432
WinAppCommon/Common/NavigationHelper.cs
Normal file
@@ -0,0 +1,432 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Windows.System;
|
||||
using Windows.UI.Core;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
using Windows.UI.Xaml.Navigation;
|
||||
|
||||
namespace ModernKeePass.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// NavigationHelper aids in navigation between pages. It provides commands used to
|
||||
/// navigate back and forward as well as registers for standard mouse and keyboard
|
||||
/// shortcuts used to go back and forward in Windows and the hardware back button in
|
||||
/// Windows Phone. In addition it integrates SuspensionManger to handle process lifetime
|
||||
/// management and state management when navigating between pages.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// To make use of NavigationHelper, follow these two steps or
|
||||
/// start with a BasicPage or any other Page item template other than BlankPage.
|
||||
///
|
||||
/// 1) Create an instance of the NavigationHelper somewhere such as in the
|
||||
/// constructor for the page and register a callback for the LoadState and
|
||||
/// SaveState events.
|
||||
/// <code>
|
||||
/// public MyPage()
|
||||
/// {
|
||||
/// this.InitializeComponent();
|
||||
/// var navigationHelper = new NavigationHelper(this);
|
||||
/// this.navigationHelper.LoadState += navigationHelper_LoadState;
|
||||
/// this.navigationHelper.SaveState += navigationHelper_SaveState;
|
||||
/// }
|
||||
///
|
||||
/// private async void navigationHelper_LoadState(object sender, LoadStateEventArgs e)
|
||||
/// { }
|
||||
/// private async void navigationHelper_SaveState(object sender, LoadStateEventArgs e)
|
||||
/// { }
|
||||
/// </code>
|
||||
///
|
||||
/// 2) Register the page to call into the NavigationHelper whenever the page participates
|
||||
/// in navigation by overriding the <see cref="Windows.UI.Xaml.Controls.Page.OnNavigatedTo"/>
|
||||
/// and <see cref="Windows.UI.Xaml.Controls.Page.OnNavigatedFrom"/> events.
|
||||
/// <code>
|
||||
/// protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
/// {
|
||||
/// navigationHelper.OnNavigatedTo(e);
|
||||
/// }
|
||||
///
|
||||
/// protected override void OnNavigatedFrom(NavigationEventArgs e)
|
||||
/// {
|
||||
/// navigationHelper.OnNavigatedFrom(e);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
[Windows.Foundation.Metadata.WebHostHidden]
|
||||
public class NavigationHelper : DependencyObject
|
||||
{
|
||||
private Page Page { get; set; }
|
||||
private Frame Frame { get { return this.Page.Frame; } }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NavigationHelper"/> class.
|
||||
/// </summary>
|
||||
/// <param name="page">A reference to the current page used for navigation.
|
||||
/// This reference allows for frame manipulation and to ensure that keyboard
|
||||
/// navigation requests only occur when the page is occupying the entire window.</param>
|
||||
public NavigationHelper(Page page)
|
||||
{
|
||||
this.Page = page;
|
||||
|
||||
// When this page is part of the visual tree make two changes:
|
||||
// 1) Map application view state to visual state for the page
|
||||
// 2) Handle hardware navigation requests
|
||||
this.Page.Loaded += (sender, e) =>
|
||||
{
|
||||
#if WINDOWS_PHONE_APP
|
||||
Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;
|
||||
#else
|
||||
// Keyboard and mouse navigation only apply when occupying the entire window
|
||||
if (this.Page.ActualHeight == Window.Current.Bounds.Height &&
|
||||
this.Page.ActualWidth == Window.Current.Bounds.Width)
|
||||
{
|
||||
// Listen to the window directly so focus isn't required
|
||||
Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated +=
|
||||
CoreDispatcher_AcceleratorKeyActivated;
|
||||
Window.Current.CoreWindow.PointerPressed +=
|
||||
this.CoreWindow_PointerPressed;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
// Undo the same changes when the page is no longer visible
|
||||
this.Page.Unloaded += (sender, e) =>
|
||||
{
|
||||
#if WINDOWS_PHONE_APP
|
||||
Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed;
|
||||
#else
|
||||
Window.Current.CoreWindow.Dispatcher.AcceleratorKeyActivated -=
|
||||
CoreDispatcher_AcceleratorKeyActivated;
|
||||
Window.Current.CoreWindow.PointerPressed -=
|
||||
this.CoreWindow_PointerPressed;
|
||||
#endif
|
||||
};
|
||||
}
|
||||
|
||||
#region Navigation support
|
||||
|
||||
RelayCommand _goBackCommand;
|
||||
RelayCommand _goForwardCommand;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="RelayCommand"/> used to bind to the back Button's Command property
|
||||
/// for navigating to the most recent item in back navigation history, if a Frame
|
||||
/// manages its own navigation history.
|
||||
///
|
||||
/// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoBack"/>
|
||||
/// as the Execute Action and <see cref="CanGoBack"/> for CanExecute.
|
||||
/// </summary>
|
||||
public RelayCommand GoBackCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_goBackCommand == null)
|
||||
{
|
||||
_goBackCommand = new RelayCommand(
|
||||
() => this.GoBack(),
|
||||
() => this.CanGoBack());
|
||||
}
|
||||
return _goBackCommand;
|
||||
}
|
||||
set
|
||||
{
|
||||
_goBackCommand = value;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// <see cref="RelayCommand"/> used for navigating to the most recent item in
|
||||
/// the forward navigation history, if a Frame manages its own navigation history.
|
||||
///
|
||||
/// The <see cref="RelayCommand"/> is set up to use the virtual method <see cref="GoForward"/>
|
||||
/// as the Execute Action and <see cref="CanGoForward"/> for CanExecute.
|
||||
/// </summary>
|
||||
public RelayCommand GoForwardCommand
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_goForwardCommand == null)
|
||||
{
|
||||
_goForwardCommand = new RelayCommand(
|
||||
() => this.GoForward(),
|
||||
() => this.CanGoForward());
|
||||
}
|
||||
return _goForwardCommand;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Virtual method used by the <see cref="GoBackCommand"/> property
|
||||
/// to determine if the <see cref="Frame"/> can go back.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the <see cref="Frame"/> has at least one entry
|
||||
/// in the back navigation history.
|
||||
/// </returns>
|
||||
public virtual bool CanGoBack()
|
||||
{
|
||||
return this.Frame != null && this.Frame.CanGoBack;
|
||||
}
|
||||
/// <summary>
|
||||
/// Virtual method used by the <see cref="GoForwardCommand"/> property
|
||||
/// to determine if the <see cref="Frame"/> can go forward.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the <see cref="Frame"/> has at least one entry
|
||||
/// in the forward navigation history.
|
||||
/// </returns>
|
||||
public virtual bool CanGoForward()
|
||||
{
|
||||
return this.Frame != null && this.Frame.CanGoForward;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Virtual method used by the <see cref="GoBackCommand"/> property
|
||||
/// to invoke the <see cref="Windows.UI.Xaml.Controls.Frame.GoBack"/> method.
|
||||
/// </summary>
|
||||
public virtual void GoBack()
|
||||
{
|
||||
if (this.Frame != null && this.Frame.CanGoBack) this.Frame.GoBack();
|
||||
}
|
||||
/// <summary>
|
||||
/// Virtual method used by the <see cref="GoForwardCommand"/> property
|
||||
/// to invoke the <see cref="Windows.UI.Xaml.Controls.Frame.GoForward"/> method.
|
||||
/// </summary>
|
||||
public virtual void GoForward()
|
||||
{
|
||||
if (this.Frame != null && this.Frame.CanGoForward) this.Frame.GoForward();
|
||||
}
|
||||
|
||||
#if WINDOWS_PHONE_APP
|
||||
/// <summary>
|
||||
/// Invoked when the hardware back button is pressed. For Windows Phone only.
|
||||
/// </summary>
|
||||
/// <param name="sender">Instance that triggered the event.</param>
|
||||
/// <param name="e">Event data describing the conditions that led to the event.</param>
|
||||
private void HardwareButtons_BackPressed(object sender, Windows.Phone.UI.Input.BackPressedEventArgs e)
|
||||
{
|
||||
if (this.GoBackCommand.CanExecute(null))
|
||||
{
|
||||
e.Handled = true;
|
||||
this.GoBackCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
#else
|
||||
/// <summary>
|
||||
/// Invoked on every keystroke, including system keys such as Alt key combinations, when
|
||||
/// this page is active and occupies the entire window. Used to detect keyboard navigation
|
||||
/// between pages even when the page itself doesn't have focus.
|
||||
/// </summary>
|
||||
/// <param name="sender">Instance that triggered the event.</param>
|
||||
/// <param name="e">Event data describing the conditions that led to the event.</param>
|
||||
private void CoreDispatcher_AcceleratorKeyActivated(CoreDispatcher sender,
|
||||
AcceleratorKeyEventArgs e)
|
||||
{
|
||||
var virtualKey = e.VirtualKey;
|
||||
|
||||
// Only investigate further when Left, Right, or the dedicated Previous or Next keys
|
||||
// are pressed
|
||||
if ((e.EventType == CoreAcceleratorKeyEventType.SystemKeyDown ||
|
||||
e.EventType == CoreAcceleratorKeyEventType.KeyDown) &&
|
||||
(virtualKey == VirtualKey.Left || virtualKey == VirtualKey.Right ||
|
||||
(int)virtualKey == 166 || (int)virtualKey == 167))
|
||||
{
|
||||
var coreWindow = Window.Current.CoreWindow;
|
||||
var downState = CoreVirtualKeyStates.Down;
|
||||
bool menuKey = (coreWindow.GetKeyState(VirtualKey.Menu) & downState) == downState;
|
||||
bool controlKey = (coreWindow.GetKeyState(VirtualKey.Control) & downState) == downState;
|
||||
bool shiftKey = (coreWindow.GetKeyState(VirtualKey.Shift) & downState) == downState;
|
||||
bool noModifiers = !menuKey && !controlKey && !shiftKey;
|
||||
bool onlyAlt = menuKey && !controlKey && !shiftKey;
|
||||
|
||||
if (((int)virtualKey == 166 && noModifiers) ||
|
||||
(virtualKey == VirtualKey.Left && onlyAlt))
|
||||
{
|
||||
// When the previous key or Alt+Left are pressed navigate back
|
||||
e.Handled = true;
|
||||
this.GoBackCommand.Execute(null);
|
||||
}
|
||||
else if (((int)virtualKey == 167 && noModifiers) ||
|
||||
(virtualKey == VirtualKey.Right && onlyAlt))
|
||||
{
|
||||
// When the next key or Alt+Right are pressed navigate forward
|
||||
e.Handled = true;
|
||||
this.GoForwardCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked on every mouse click, touch screen tap, or equivalent interaction when this
|
||||
/// page is active and occupies the entire window. Used to detect browser-style next and
|
||||
/// previous mouse button clicks to navigate between pages.
|
||||
/// </summary>
|
||||
/// <param name="sender">Instance that triggered the event.</param>
|
||||
/// <param name="e">Event data describing the conditions that led to the event.</param>
|
||||
private void CoreWindow_PointerPressed(CoreWindow sender,
|
||||
PointerEventArgs e)
|
||||
{
|
||||
var properties = e.CurrentPoint.Properties;
|
||||
|
||||
// Ignore button chords with the left, right, and middle buttons
|
||||
if (properties.IsLeftButtonPressed || properties.IsRightButtonPressed ||
|
||||
properties.IsMiddleButtonPressed) return;
|
||||
|
||||
// If back or foward are pressed (but not both) navigate appropriately
|
||||
bool backPressed = properties.IsXButton1Pressed;
|
||||
bool forwardPressed = properties.IsXButton2Pressed;
|
||||
if (backPressed ^ forwardPressed)
|
||||
{
|
||||
e.Handled = true;
|
||||
if (backPressed) this.GoBackCommand.Execute(null);
|
||||
if (forwardPressed) this.GoForwardCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
#region Process lifetime management
|
||||
|
||||
private String _pageKey;
|
||||
|
||||
/// <summary>
|
||||
/// Register this event on the current page to populate the page
|
||||
/// with content passed during navigation as well as any saved
|
||||
/// state provided when recreating a page from a prior session.
|
||||
/// </summary>
|
||||
public event LoadStateEventHandler LoadState;
|
||||
/// <summary>
|
||||
/// Register this event on the current page to preserve
|
||||
/// state associated with the current page in case the
|
||||
/// application is suspended or the page is discarded from
|
||||
/// the navigaqtion cache.
|
||||
/// </summary>
|
||||
public event SaveStateEventHandler SaveState;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this page is about to be displayed in a Frame.
|
||||
/// This method calls <see cref="LoadState"/>, where all page specific
|
||||
/// navigation and process lifetime management logic should be placed.
|
||||
/// </summary>
|
||||
/// <param name="e">Event data that describes how this page was reached. The Parameter
|
||||
/// property provides the group to be displayed.</param>
|
||||
public void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
|
||||
this._pageKey = "Page-" + this.Frame.BackStackDepth;
|
||||
|
||||
if (e.NavigationMode == NavigationMode.New)
|
||||
{
|
||||
// Clear existing state for forward navigation when adding a new page to the
|
||||
// navigation stack
|
||||
var nextPageKey = this._pageKey;
|
||||
int nextPageIndex = this.Frame.BackStackDepth;
|
||||
while (frameState.Remove(nextPageKey))
|
||||
{
|
||||
nextPageIndex++;
|
||||
nextPageKey = "Page-" + nextPageIndex;
|
||||
}
|
||||
|
||||
// Pass the navigation parameter to the new page
|
||||
if (this.LoadState != null)
|
||||
{
|
||||
this.LoadState(this, new LoadStateEventArgs(e.Parameter, null));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Pass the navigation parameter and preserved page state to the page, using
|
||||
// the same strategy for loading suspended state and recreating pages discarded
|
||||
// from cache
|
||||
if (this.LoadState != null)
|
||||
{
|
||||
this.LoadState(this, new LoadStateEventArgs(e.Parameter, (Dictionary<String, Object>)frameState[this._pageKey]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when this page will no longer be displayed in a Frame.
|
||||
/// This method calls <see cref="SaveState"/>, where all page specific
|
||||
/// navigation and process lifetime management logic should be placed.
|
||||
/// </summary>
|
||||
/// <param name="e">Event data that describes how this page was reached. The Parameter
|
||||
/// property provides the group to be displayed.</param>
|
||||
public void OnNavigatedFrom(NavigationEventArgs e)
|
||||
{
|
||||
var frameState = SuspensionManager.SessionStateForFrame(this.Frame);
|
||||
var pageState = new Dictionary<String, Object>();
|
||||
if (this.SaveState != null)
|
||||
{
|
||||
this.SaveState(this, new SaveStateEventArgs(pageState));
|
||||
}
|
||||
frameState[_pageKey] = pageState;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the <see cref="NavigationHelper.LoadState"/>event
|
||||
/// </summary>
|
||||
public delegate void LoadStateEventHandler(object sender, LoadStateEventArgs e);
|
||||
/// <summary>
|
||||
/// Represents the method that will handle the <see cref="NavigationHelper.SaveState"/>event
|
||||
/// </summary>
|
||||
public delegate void SaveStateEventHandler(object sender, SaveStateEventArgs e);
|
||||
|
||||
/// <summary>
|
||||
/// Class used to hold the event data required when a page attempts to load state.
|
||||
/// </summary>
|
||||
public class LoadStateEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// The parameter value passed to <see cref="Frame.Navigate(Type, Object)"/>
|
||||
/// when this page was initially requested.
|
||||
/// </summary>
|
||||
public Object NavigationParameter { get; private set; }
|
||||
/// <summary>
|
||||
/// A dictionary of state preserved by this page during an earlier
|
||||
/// session. This will be null the first time a page is visited.
|
||||
/// </summary>
|
||||
public Dictionary<string, Object> PageState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LoadStateEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="navigationParameter">
|
||||
/// The parameter value passed to <see cref="Frame.Navigate(Type, Object)"/>
|
||||
/// when this page was initially requested.
|
||||
/// </param>
|
||||
/// <param name="pageState">
|
||||
/// A dictionary of state preserved by this page during an earlier
|
||||
/// session. This will be null the first time a page is visited.
|
||||
/// </param>
|
||||
public LoadStateEventArgs(Object navigationParameter, Dictionary<string, Object> pageState)
|
||||
: base()
|
||||
{
|
||||
this.NavigationParameter = navigationParameter;
|
||||
this.PageState = pageState;
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Class used to hold the event data required when a page attempts to save state.
|
||||
/// </summary>
|
||||
public class SaveStateEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty dictionary to be populated with serializable state.
|
||||
/// </summary>
|
||||
public Dictionary<string, Object> PageState { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="SaveStateEventArgs"/> class.
|
||||
/// </summary>
|
||||
/// <param name="pageState">An empty dictionary to be populated with serializable state.</param>
|
||||
public SaveStateEventArgs(Dictionary<string, Object> pageState)
|
||||
: base()
|
||||
{
|
||||
this.PageState = pageState;
|
||||
}
|
||||
}
|
||||
}
|
156
WinAppCommon/Common/ObservableDictionary.cs
Normal file
156
WinAppCommon/Common/ObservableDictionary.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
namespace ModernKeePass.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Implementation of IObservableMap that supports reentrancy for use as a default view
|
||||
/// model.
|
||||
/// </summary>
|
||||
public class ObservableDictionary : IObservableMap<string, object>
|
||||
{
|
||||
private class ObservableDictionaryChangedEventArgs : IMapChangedEventArgs<string>
|
||||
{
|
||||
public ObservableDictionaryChangedEventArgs(CollectionChange change, string key)
|
||||
{
|
||||
CollectionChange = change;
|
||||
Key = key;
|
||||
}
|
||||
|
||||
public CollectionChange CollectionChange { get; private set; }
|
||||
public string Key { get; private set; }
|
||||
}
|
||||
|
||||
private readonly Dictionary<string, object> _dictionary = new Dictionary<string, object>();
|
||||
public event MapChangedEventHandler<string, object> MapChanged;
|
||||
|
||||
private void InvokeMapChanged(CollectionChange change, string key)
|
||||
{
|
||||
var eventHandler = MapChanged;
|
||||
if (eventHandler != null)
|
||||
{
|
||||
eventHandler(this, new ObservableDictionaryChangedEventArgs(change, key));
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(string key, object value)
|
||||
{
|
||||
_dictionary.Add(key, value);
|
||||
InvokeMapChanged(CollectionChange.ItemInserted, key);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<string, object> item)
|
||||
{
|
||||
Add(item.Key, item.Value);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<KeyValuePair<string, object>> values)
|
||||
{
|
||||
foreach (var value in values)
|
||||
{
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Remove(string key)
|
||||
{
|
||||
if (_dictionary.Remove(key))
|
||||
{
|
||||
InvokeMapChanged(CollectionChange.ItemRemoved, key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Remove(KeyValuePair<string, object> item)
|
||||
{
|
||||
object currentValue;
|
||||
if (_dictionary.TryGetValue(item.Key, out currentValue) &&
|
||||
Equals(item.Value, currentValue) && _dictionary.Remove(item.Key))
|
||||
{
|
||||
InvokeMapChanged(CollectionChange.ItemRemoved, item.Key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return _dictionary[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
_dictionary[key] = value;
|
||||
InvokeMapChanged(CollectionChange.ItemChanged, key);
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
var priorKeys = _dictionary.Keys.ToArray();
|
||||
_dictionary.Clear();
|
||||
foreach (var key in priorKeys)
|
||||
{
|
||||
InvokeMapChanged(CollectionChange.ItemRemoved, key);
|
||||
}
|
||||
}
|
||||
|
||||
public ICollection<string> Keys
|
||||
{
|
||||
get { return _dictionary.Keys; }
|
||||
}
|
||||
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(string key, out object value)
|
||||
{
|
||||
return _dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public ICollection<object> Values
|
||||
{
|
||||
get { return _dictionary.Values; }
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<string, object> item)
|
||||
{
|
||||
return _dictionary.Contains(item);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _dictionary.Count; }
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
|
||||
{
|
||||
return _dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
||||
{
|
||||
return _dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
|
||||
{
|
||||
int arraySize = array.Length;
|
||||
foreach (var pair in _dictionary)
|
||||
{
|
||||
if (arrayIndex >= arraySize) break;
|
||||
array[arrayIndex++] = pair;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
78
WinAppCommon/Common/RelayCommand.cs
Normal file
78
WinAppCommon/Common/RelayCommand.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace ModernKeePass.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// A command whose sole purpose is to relay its functionality
|
||||
/// to other objects by invoking delegates.
|
||||
/// The default return value for the CanExecute method is 'true'.
|
||||
/// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
|
||||
/// <see cref="CanExecute"/> is expected to return a different value.
|
||||
/// </summary>
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action _execute;
|
||||
private readonly Func<bool> _canExecute;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when RaiseCanExecuteChanged is called.
|
||||
/// </summary>
|
||||
public event EventHandler CanExecuteChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command that can always execute.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
public RelayCommand(Action execute)
|
||||
: this(execute, null)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new command.
|
||||
/// </summary>
|
||||
/// <param name="execute">The execution logic.</param>
|
||||
/// <param name="canExecute">The execution status logic.</param>
|
||||
public RelayCommand(Action execute, Func<bool> canExecute)
|
||||
{
|
||||
if (execute == null)
|
||||
throw new ArgumentNullException(nameof(execute));
|
||||
_execute = execute;
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
|
||||
/// </param>
|
||||
/// <returns>true if this command can be executed; otherwise, false.</returns>
|
||||
public bool CanExecute(object parameter)
|
||||
{
|
||||
return _canExecute?.Invoke() ?? true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the <see cref="RelayCommand"/> on the current command target.
|
||||
/// </summary>
|
||||
/// <param name="parameter">
|
||||
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
|
||||
/// </param>
|
||||
public void Execute(object parameter)
|
||||
{
|
||||
_execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method used to raise the <see cref="CanExecuteChanged"/> event
|
||||
/// to indicate that the return value of the <see cref="CanExecute"/>
|
||||
/// method has changed.
|
||||
/// </summary>
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
260
WinAppCommon/Common/SuspensionManager.cs
Normal file
260
WinAppCommon/Common/SuspensionManager.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI.Xaml;
|
||||
using Windows.UI.Xaml.Controls;
|
||||
|
||||
namespace ModernKeePass.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// SuspensionManager captures global session state to simplify process lifetime management
|
||||
/// for an application. Note that session state will be automatically cleared under a variety
|
||||
/// of conditions and should only be used to store information that would be convenient to
|
||||
/// carry across sessions, but that should be discarded when an application crashes or is
|
||||
/// upgraded.
|
||||
/// </summary>
|
||||
internal static class SuspensionManager
|
||||
{
|
||||
private static Dictionary<string, object> _sessionState = new Dictionary<string, object>();
|
||||
private static readonly List<Type> _knownTypes = new List<Type>();
|
||||
private const string sessionStateFilename = "_sessionState.xml";
|
||||
|
||||
/// <summary>
|
||||
/// Provides access to global session state for the current session. This state is
|
||||
/// serialized by <see cref="SaveAsync"/> and restored by
|
||||
/// <see cref="RestoreAsync"/>, so values must be serializable by
|
||||
/// <see cref="DataContractSerializer"/> and should be as compact as possible. Strings
|
||||
/// and other self-contained data types are strongly recommended.
|
||||
/// </summary>
|
||||
public static Dictionary<string, object> SessionState => _sessionState;
|
||||
|
||||
/// <summary>
|
||||
/// List of custom types provided to the <see cref="DataContractSerializer"/> when
|
||||
/// reading and writing session state. Initially empty, additional types may be
|
||||
/// added to customize the serialization process.
|
||||
/// </summary>
|
||||
public static List<Type> KnownTypes => _knownTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Save the current <see cref="SessionState"/>. Any <see cref="Frame"/> instances
|
||||
/// registered with <see cref="RegisterFrame"/> will also preserve their current
|
||||
/// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
|
||||
/// to save its state.
|
||||
/// </summary>
|
||||
/// <returns>An asynchronous task that reflects when session state has been saved.</returns>
|
||||
public static async Task SaveAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Save the navigation state for all registered frames
|
||||
foreach (var weakFrameReference in _registeredFrames)
|
||||
{
|
||||
Frame frame;
|
||||
if (weakFrameReference.TryGetTarget(out frame))
|
||||
{
|
||||
SaveFrameNavigationState(frame);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the session state synchronously to avoid asynchronous access to shared
|
||||
// state
|
||||
MemoryStream sessionData = new MemoryStream();
|
||||
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
|
||||
serializer.WriteObject(sessionData, _sessionState);
|
||||
|
||||
// Get an output stream for the SessionState file and write the state asynchronously
|
||||
StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(sessionStateFilename, CreationCollisionOption.ReplaceExisting);
|
||||
using (Stream fileStream = await file.OpenStreamForWriteAsync())
|
||||
{
|
||||
sessionData.Seek(0, SeekOrigin.Begin);
|
||||
await sessionData.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SuspensionManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores previously saved <see cref="SessionState"/>. Any <see cref="Frame"/> instances
|
||||
/// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
|
||||
/// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
|
||||
/// state.
|
||||
/// </summary>
|
||||
/// <param name="sessionBaseKey">An optional key that identifies the type of session.
|
||||
/// This can be used to distinguish between multiple application launch scenarios.</param>
|
||||
/// <returns>An asynchronous task that reflects when session state has been read. The
|
||||
/// content of <see cref="SessionState"/> should not be relied upon until this task
|
||||
/// completes.</returns>
|
||||
public static async Task RestoreAsync(String sessionBaseKey = null)
|
||||
{
|
||||
_sessionState = new Dictionary<String, Object>();
|
||||
|
||||
try
|
||||
{
|
||||
// Get the input stream for the SessionState file
|
||||
StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename);
|
||||
using (IInputStream inStream = await file.OpenSequentialReadAsync())
|
||||
{
|
||||
// Deserialize the Session State
|
||||
DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary<string, object>), _knownTypes);
|
||||
_sessionState = (Dictionary<string, object>)serializer.ReadObject(inStream.AsStreamForRead());
|
||||
}
|
||||
|
||||
// Restore any registered frames to their saved state
|
||||
foreach (var weakFrameReference in _registeredFrames)
|
||||
{
|
||||
Frame frame;
|
||||
if (weakFrameReference.TryGetTarget(out frame) && (string)frame.GetValue(FrameSessionBaseKeyProperty) == sessionBaseKey)
|
||||
{
|
||||
frame.ClearValue(FrameSessionStateProperty);
|
||||
RestoreFrameNavigationState(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new SuspensionManagerException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static DependencyProperty FrameSessionStateKeyProperty =
|
||||
DependencyProperty.RegisterAttached("_FrameSessionStateKey", typeof(String), typeof(SuspensionManager), null);
|
||||
private static DependencyProperty FrameSessionBaseKeyProperty =
|
||||
DependencyProperty.RegisterAttached("_FrameSessionBaseKeyParams", typeof(String), typeof(SuspensionManager), null);
|
||||
private static DependencyProperty FrameSessionStateProperty =
|
||||
DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary<String, Object>), typeof(SuspensionManager), null);
|
||||
private static List<WeakReference<Frame>> _registeredFrames = new List<WeakReference<Frame>>();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
|
||||
/// and restored from <see cref="SessionState"/>. Frames should be registered once
|
||||
/// immediately after creation if they will participate in session state management. Upon
|
||||
/// registration if state has already been restored for the specified key
|
||||
/// the navigation history will immediately be restored. Subsequent invocations of
|
||||
/// <see cref="RestoreAsync"/> will also restore navigation history.
|
||||
/// </summary>
|
||||
/// <param name="frame">An instance whose navigation history should be managed by
|
||||
/// <see cref="SuspensionManager"/></param>
|
||||
/// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
|
||||
/// store navigation-related information.</param>
|
||||
/// <param name="sessionBaseKey">An optional key that identifies the type of session.
|
||||
/// This can be used to distinguish between multiple application launch scenarios.</param>
|
||||
public static void RegisterFrame(Frame frame, String sessionStateKey, String sessionBaseKey = null)
|
||||
{
|
||||
if (frame.GetValue(FrameSessionStateKeyProperty) != null)
|
||||
{
|
||||
throw new InvalidOperationException("Frames can only be registered to one session state key");
|
||||
}
|
||||
|
||||
if (frame.GetValue(FrameSessionStateProperty) != null)
|
||||
{
|
||||
throw new InvalidOperationException("Frames must be either be registered before accessing frame session state, or not registered at all");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(sessionBaseKey))
|
||||
{
|
||||
frame.SetValue(FrameSessionBaseKeyProperty, sessionBaseKey);
|
||||
sessionStateKey = sessionBaseKey + "_" + sessionStateKey;
|
||||
}
|
||||
|
||||
// Use a dependency property to associate the session key with a frame, and keep a list of frames whose
|
||||
// navigation state should be managed
|
||||
frame.SetValue(FrameSessionStateKeyProperty, sessionStateKey);
|
||||
_registeredFrames.Add(new WeakReference<Frame>(frame));
|
||||
|
||||
// Check to see if navigation state can be restored
|
||||
RestoreFrameNavigationState(frame);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
|
||||
/// from <see cref="SessionState"/>. Any navigation state previously captured will be
|
||||
/// removed.
|
||||
/// </summary>
|
||||
/// <param name="frame">An instance whose navigation history should no longer be
|
||||
/// managed.</param>
|
||||
public static void UnregisterFrame(Frame frame)
|
||||
{
|
||||
// Remove session state and remove the frame from the list of frames whose navigation
|
||||
// state will be saved (along with any weak references that are no longer reachable)
|
||||
SessionState.Remove((String)frame.GetValue(FrameSessionStateKeyProperty));
|
||||
_registeredFrames.RemoveAll((weakFrameReference) =>
|
||||
{
|
||||
Frame testFrame;
|
||||
return !weakFrameReference.TryGetTarget(out testFrame) || testFrame == frame;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides storage for session state associated with the specified <see cref="Frame"/>.
|
||||
/// Frames that have been previously registered with <see cref="RegisterFrame"/> have
|
||||
/// their session state saved and restored automatically as a part of the global
|
||||
/// <see cref="SessionState"/>. Frames that are not registered have transient state
|
||||
/// that can still be useful when restoring pages that have been discarded from the
|
||||
/// navigation cache.
|
||||
/// </summary>
|
||||
/// <remarks>Apps may choose to rely on <see cref="NavigationHelper"/> to manage
|
||||
/// page-specific state instead of working with frame session state directly.</remarks>
|
||||
/// <param name="frame">The instance for which session state is desired.</param>
|
||||
/// <returns>A collection of state subject to the same serialization mechanism as
|
||||
/// <see cref="SessionState"/>.</returns>
|
||||
public static Dictionary<String, Object> SessionStateForFrame(Frame frame)
|
||||
{
|
||||
var frameState = (Dictionary<String, Object>)frame.GetValue(FrameSessionStateProperty);
|
||||
|
||||
if (frameState == null)
|
||||
{
|
||||
var frameSessionKey = (String)frame.GetValue(FrameSessionStateKeyProperty);
|
||||
if (frameSessionKey != null)
|
||||
{
|
||||
// Registered frames reflect the corresponding session state
|
||||
if (!_sessionState.ContainsKey(frameSessionKey))
|
||||
{
|
||||
_sessionState[frameSessionKey] = new Dictionary<String, Object>();
|
||||
}
|
||||
frameState = (Dictionary<String, Object>)_sessionState[frameSessionKey];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Frames that aren't registered have transient state
|
||||
frameState = new Dictionary<String, Object>();
|
||||
}
|
||||
frame.SetValue(FrameSessionStateProperty, frameState);
|
||||
}
|
||||
return frameState;
|
||||
}
|
||||
|
||||
private static void RestoreFrameNavigationState(Frame frame)
|
||||
{
|
||||
var frameState = SessionStateForFrame(frame);
|
||||
if (frameState.ContainsKey("Navigation"))
|
||||
{
|
||||
frame.SetNavigationState((String)frameState["Navigation"]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveFrameNavigationState(Frame frame)
|
||||
{
|
||||
var frameState = SessionStateForFrame(frame);
|
||||
frameState["Navigation"] = frame.GetNavigationState();
|
||||
}
|
||||
}
|
||||
public class SuspensionManagerException : Exception
|
||||
{
|
||||
public SuspensionManagerException()
|
||||
{
|
||||
}
|
||||
|
||||
public SuspensionManagerException(Exception e)
|
||||
: base("SuspensionManager failed", e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
53
WinAppCommon/Common/ToastNotificationHelper.cs
Normal file
53
WinAppCommon/Common/ToastNotificationHelper.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using Windows.Data.Json;
|
||||
using Windows.Data.Xml.Dom;
|
||||
using Windows.UI.Notifications;
|
||||
using ModernKeePass.Interfaces;
|
||||
|
||||
namespace ModernKeePass.Common
|
||||
{
|
||||
public static class ToastNotificationHelper
|
||||
{
|
||||
public static void ShowMovedToast(IVmEntity entity, string action, string text)
|
||||
{
|
||||
var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
|
||||
var toastElements = notificationXml.GetElementsByTagName("text");
|
||||
toastElements[0].AppendChild(notificationXml.CreateTextNode($"{action} {entity.Title}"));
|
||||
toastElements[1].AppendChild(notificationXml.CreateTextNode(text));
|
||||
var toastNode = notificationXml.SelectSingleNode("/toast");
|
||||
|
||||
// This is useful only for Windows 10 UWP
|
||||
var launch = new JsonObject
|
||||
{
|
||||
{"entityType", JsonValue.CreateStringValue(entity.GetType().Name)},
|
||||
{"entityId", JsonValue.CreateStringValue(entity.Id)}
|
||||
};
|
||||
((XmlElement)toastNode)?.SetAttribute("launch", launch.Stringify());
|
||||
|
||||
var toast = new ToastNotification(notificationXml)
|
||||
{
|
||||
ExpirationTime = DateTime.Now.AddSeconds(5)
|
||||
};
|
||||
ToastNotificationManager.CreateToastNotifier().Show(toast);
|
||||
}
|
||||
|
||||
public static void ShowGenericToast(string title, string text)
|
||||
{
|
||||
var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02);
|
||||
var toastElements = notificationXml.GetElementsByTagName("text");
|
||||
toastElements[0].AppendChild(notificationXml.CreateTextNode(title));
|
||||
toastElements[1].AppendChild(notificationXml.CreateTextNode(text));
|
||||
|
||||
var toast = new ToastNotification(notificationXml)
|
||||
{
|
||||
ExpirationTime = DateTime.Now.AddSeconds(5)
|
||||
};
|
||||
ToastNotificationManager.CreateToastNotifier().Show(toast);
|
||||
}
|
||||
|
||||
public static void ShowErrorToast(Exception exception)
|
||||
{
|
||||
ShowGenericToast(exception.Source, exception.Message);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user