From dc62cedb74560c22c6986057370e9d3d7f4385b0 Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville Date: Wed, 18 Oct 2017 10:32:51 +0200 Subject: [PATCH] Toast notifications and undo mechanism now works! (for group and entries) --- ModernKeePass/App.xaml.cs | 44 +++++---- .../Common/ToastNotificationHelper.cs | 97 +++++++++++++++++++ ModernKeePass/Interfaces/IPwEntity.cs | 1 + ModernKeePass/ModernKeePass.csproj | 1 + ModernKeePass/Pages/EntryDetailPage.xaml.cs | 4 +- ModernKeePass/Pages/GroupDetailPage.xaml.cs | 88 +---------------- ModernKeePass/ViewModels/EntryVm.cs | 21 ++-- ModernKeePass/ViewModels/GroupVm.cs | 18 +--- 8 files changed, 148 insertions(+), 126 deletions(-) create mode 100644 ModernKeePass/Common/ToastNotificationHelper.cs diff --git a/ModernKeePass/App.xaml.cs b/ModernKeePass/App.xaml.cs index e720fe1..0399725 100644 --- a/ModernKeePass/App.xaml.cs +++ b/ModernKeePass/App.xaml.cs @@ -9,7 +9,6 @@ using Windows.UI.Xaml.Navigation; using Microsoft.QueryStringDotNET; using ModernKeePass.Common; using ModernKeePass.Interfaces; -using ModernKeePass.ViewModels; // The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227 @@ -21,7 +20,7 @@ namespace ModernKeePass sealed partial class App : Application { public DatabaseHelper Database { get; set; } = new DatabaseHelper(); - public Queue PendingDeleteQueue = new Queue(); + public Dictionary PendingDeleteEntities = new Dictionary(); /// /// Initializes the singleton application object. This is the first line of authored code @@ -33,6 +32,7 @@ namespace ModernKeePass Suspending += OnSuspending; } + #region Event Handlers /// /// Invoked when the application is launched normally by the end user. Other entry points /// will be used such as when the application is launched to open a specific file. @@ -90,25 +90,20 @@ namespace ModernKeePass // parameter rootFrame.Navigate(typeof(MainPage), lauchActivatedEventArgs.Arguments); } - } - //if (e is ToastNotificationActivatedEventArgs) - else - { - /*var toastActivationArgs = e as ToastNotificationActivatedEventArgs; - - // Parse the query string (using QueryString.NET) - QueryString args = QueryString.Parse(toastActivationArgs.Argument); - - // See what action is being requested - switch (args["action"]) + else { - }*/ - var entity = PendingDeleteQueue.Dequeue(); - if (entity is GroupVm) - { - entity.ParentGroup.Groups.Add(entity as GroupVm); + // App is "launched" via the Toast Activation event + UndoEntityDelete(lauchActivatedEventArgs.Arguments); } } + // This is only available on Windows 10... + /*else if (e is ToastNotificationActivatedEventArgs) + { + var toastActivationArgs = e as ToastNotificationActivatedEventArgs; + + // Parse the query string (using QueryString.NET) + UndoEntityDelete(QueryString.Parse(toastActivationArgs.Argument)); + }*/ // Ensure the current window is active Window.Current.Activate(); } @@ -138,6 +133,10 @@ namespace ModernKeePass deferral.Complete(); } + /// + /// Invoked when application is launched from opening a file in Windows Explorer + /// + /// Details about the file being opened protected override void OnFileActivated(FileActivatedEventArgs args) { base.OnFileActivated(args); @@ -147,5 +146,14 @@ namespace ModernKeePass Window.Current.Content = rootFrame; Window.Current.Activate(); } + #endregion + + private void UndoEntityDelete(string arguments) + { + var args = QueryString.Parse(arguments); + var entity = PendingDeleteEntities[args["entityId"]]; + PendingDeleteEntities.Remove(args["entityId"]); + entity.UndoDelete(); + } } } diff --git a/ModernKeePass/Common/ToastNotificationHelper.cs b/ModernKeePass/Common/ToastNotificationHelper.cs new file mode 100644 index 0000000..ab8127f --- /dev/null +++ b/ModernKeePass/Common/ToastNotificationHelper.cs @@ -0,0 +1,97 @@ +using System; +using Windows.Data.Xml.Dom; +using Windows.UI.Notifications; +using Windows.UI.Xaml; +using Microsoft.QueryStringDotNET; +using ModernKeePass.Interfaces; + +namespace ModernKeePass.Common +{ + public static class ToastNotificationHelper + { + public static /*async*/ void ShowUndoToast(string entityType, IPwEntity entity) + { + // This is for Windows 10 + // Construct the visuals of the toast + /*var visual = new ToastVisual + { + BindingGeneric = new ToastBindingGeneric + { + Children = + { + new AdaptiveText + { + Text = $"{entityType} {entity.Name} deleted." + } + } + } + }; + + // Construct the actions for the toast (inputs and buttons) + var actions = new ToastActionsCustom + { + Buttons = + { + new ToastButton("Undo", new QueryString + { + { "action", "undo" }, + { "entityType", entityType }, + { "entityId", entity.Id } + + }.ToString()) + } + }; + + // Now we can construct the final toast content + var toastContent = new ToastContent + { + Visual = visual, + Actions = actions, + // Arguments when the user taps body of toast + Launch = new QueryString() + { + { "action", "undo" }, + { "entityType", "group" }, + { "entityId", entity.Id } + + }.ToString() + }; + + // And create the toast notification + var toastXml = new XmlDocument(); + toastXml.LoadXml(toastContent.GetContent()); + + var toast = new ToastNotification(toastXml) {ExpirationTime = DateTime.Now.AddSeconds(5)}; + toast.Dismissed += Toast_Dismissed; + */ + var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02); + var toastElements = notificationXml.GetElementsByTagName("text"); + toastElements[0].AppendChild(notificationXml.CreateTextNode($"{entityType} deleted")); + toastElements[1].AppendChild(notificationXml.CreateTextNode("Click me to undo")); + var toastNode = notificationXml.SelectSingleNode("/toast"); + ((XmlElement)toastNode)?.SetAttribute("launch", new QueryString + { + { "entityType", entityType }, + { "entityId", entity.Id } + + }.ToString()); + + var toast = new ToastNotification(notificationXml) + { + ExpirationTime = DateTime.Now.AddSeconds(5) + }; + toast.Dismissed += Toast_Dismissed; + ToastNotificationManager.CreateToastNotifier().Show(toast); + } + + private static void Toast_Dismissed(ToastNotification sender, ToastDismissedEventArgs args) + { + var toastNode = sender.Content.SelectSingleNode("/toast"); + var launchArguments = QueryString.Parse(((XmlElement)toastNode)?.GetAttribute("launch")); + var app = (App)Application.Current; + var entity = app.PendingDeleteEntities[launchArguments["entityId"]]; + app.PendingDeleteEntities.Remove(launchArguments["entityId"]); + entity.CommitDelete(); + } + } +} diff --git a/ModernKeePass/Interfaces/IPwEntity.cs b/ModernKeePass/Interfaces/IPwEntity.cs index 9ebcea3..48c8730 100644 --- a/ModernKeePass/Interfaces/IPwEntity.cs +++ b/ModernKeePass/Interfaces/IPwEntity.cs @@ -12,5 +12,6 @@ namespace ModernKeePass.Interfaces bool IsEditMode { get; } void CommitDelete(); + void UndoDelete(); } } \ No newline at end of file diff --git a/ModernKeePass/ModernKeePass.csproj b/ModernKeePass/ModernKeePass.csproj index 9553ba4..bde2dd1 100644 --- a/ModernKeePass/ModernKeePass.csproj +++ b/ModernKeePass/ModernKeePass.csproj @@ -117,6 +117,7 @@ + diff --git a/ModernKeePass/Pages/EntryDetailPage.xaml.cs b/ModernKeePass/Pages/EntryDetailPage.xaml.cs index 0605848..f262d2a 100644 --- a/ModernKeePass/Pages/EntryDetailPage.xaml.cs +++ b/ModernKeePass/Pages/EntryDetailPage.xaml.cs @@ -86,8 +86,8 @@ namespace ModernKeePass.Pages // Add commands and set their callbacks; both buttons use the same callback function instead of inline event handlers messageDialog.Commands.Add(new UICommand("Delete", delete => { - var entry = DataContext as EntryVm; - entry?.RemoveEntry(); + ToastNotificationHelper.ShowUndoToast("Entry", Model); + Model.MarkForDelete(); if (Frame.CanGoBack) Frame.GoBack(); })); messageDialog.Commands.Add(new UICommand("Cancel")); diff --git a/ModernKeePass/Pages/GroupDetailPage.xaml.cs b/ModernKeePass/Pages/GroupDetailPage.xaml.cs index 55f01a8..ef9de2b 100644 --- a/ModernKeePass/Pages/GroupDetailPage.xaml.cs +++ b/ModernKeePass/Pages/GroupDetailPage.xaml.cs @@ -128,7 +128,7 @@ namespace ModernKeePass.Pages // Add commands and set their callbacks; both buttons use the same callback function instead of inline event handlers messageDialog.Commands.Add(new UICommand("Delete", delete => { - ShowToast("Group", Model); + ToastNotificationHelper.ShowUndoToast("Group", Model); Model.MarkForDelete(); if (Frame.CanGoBack) Frame.GoBack(); })); @@ -171,91 +171,5 @@ namespace ModernKeePass.Pages #endregion - private async void ShowToast(string entityType, IPwEntity entity) - { - // Construct the visuals of the toast - /*var visual = new ToastVisual - { - BindingGeneric = new ToastBindingGeneric - { - Children = - { - new AdaptiveText - { - Text = $"{entityType} {entity.Name} deleted." - } - } - } - }; - - // Construct the actions for the toast (inputs and buttons) - var actions = new ToastActionsCustom - { - Buttons = - { - new ToastButton("Undo", new QueryString - { - { "action", "undo" }, - { "entityType", entityType }, - { "entityId", entity.Id } - - }.ToString()) - } - }; - - // Now we can construct the final toast content - var toastContent = new ToastContent - { - Visual = visual, - Actions = actions, - // Arguments when the user taps body of toast - Launch = new QueryString() - { - { "action", "undo" }, - { "entityType", "group" }, - { "entityId", entity.Id } - - }.ToString() - }; - - // And create the toast notification - var toastXml = new XmlDocument(); - toastXml.LoadXml(toastContent.GetContent()); - - var visualXml = toastXml.GetElementsByTagName("visual")[0]; - ((XmlElement)visualXml.ChildNodes[0]).SetAttribute("template", "ToastText02"); - - var toast = new ToastNotification(toastXml) {ExpirationTime = DateTime.Now.AddSeconds(5)}; - toast.Dismissed += Toast_Dismissed; - */ - var notificationXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText02); - var toastElements = notificationXml.GetElementsByTagName("text"); - toastElements[0].AppendChild(notificationXml.CreateTextNode($"{entityType} deleted")); - toastElements[1].AppendChild(notificationXml.CreateTextNode("Click me to undo")); - var toastNode = notificationXml.SelectSingleNode("/toast"); - ((XmlElement)toastNode).SetAttribute("launch", new QueryString - { - { "entityType", entityType }, - { "entityId", entity.Id } - - }.ToString()); - - var toast = new ToastNotification(notificationXml) - { - ExpirationTime = DateTime.Now.AddSeconds(5) - }; - ToastNotificationManager.CreateToastNotifier().Show(toast); - } - - private void Toast_Dismissed(ToastNotification sender, ToastDismissedEventArgs args) - { - var app = (App)Application.Current; - if (app.PendingDeleteQueue.Count == 0) return; - var entity = app.PendingDeleteQueue.Dequeue(); - if (entity is GroupVm) - { - entity.CommitDelete(); - } - } } } diff --git a/ModernKeePass/ViewModels/EntryVm.cs b/ModernKeePass/ViewModels/EntryVm.cs index 282b6e4..e133710 100644 --- a/ModernKeePass/ViewModels/EntryVm.cs +++ b/ModernKeePass/ViewModels/EntryVm.cs @@ -6,6 +6,7 @@ using ModernKeePassLib; using ModernKeePassLib.Cryptography.PasswordGenerator; using ModernKeePassLib.Security; using System; +using Windows.UI.Xaml; namespace ModernKeePass.ViewModels { @@ -112,10 +113,6 @@ namespace ModernKeePass.ViewModels ParentGroup = parent; } - public void RemoveEntry() - { - ParentGroup.RemoveEntry(this); - } public void GeneratePassword() { @@ -154,10 +151,22 @@ namespace ModernKeePass.ViewModels { Entry?.Strings.Set(key, new ProtectedString(true, newValue)); } - + + public void MarkForDelete() + { + var app = (App)Application.Current; + app.PendingDeleteEntities.Add(Id, this); + ParentGroup.Entries.Remove(this); + } public void CommitDelete() { - throw new NotImplementedException(); + Entry.ParentGroup.Entries.Remove(Entry); } + + public void UndoDelete() + { + ParentGroup.Entries.Add(this); + } + } } diff --git a/ModernKeePass/ViewModels/GroupVm.cs b/ModernKeePass/ViewModels/GroupVm.cs index bc9d3e8..524f6f7 100644 --- a/ModernKeePass/ViewModels/GroupVm.cs +++ b/ModernKeePass/ViewModels/GroupVm.cs @@ -97,23 +97,11 @@ namespace ModernKeePass.ViewModels Entries.Add(newEntry); return newEntry; } - - public void RemoveGroup() - { - _pwGroup.ParentGroup.Groups.Remove(_pwGroup); - ParentGroup.Groups.Remove(this); - } - public void RemoveEntry(EntryVm entry) - { - _pwGroup.Entries.Remove(entry.Entry); - Entries.Remove(entry); - } - public void MarkForDelete() { var app = (App)Application.Current; - app.PendingDeleteQueue.Enqueue(this); + app.PendingDeleteEntities.Add(Id, this); ParentGroup.Groups.Remove(this); } @@ -121,5 +109,9 @@ namespace ModernKeePass.ViewModels { _pwGroup.ParentGroup.Groups.Remove(_pwGroup); } + public void UndoDelete() + { + ParentGroup.Groups.Add(this); + } } }