mirror of
https://github.com/wismna/ModernKeePass.git
synced 2025-10-04 08:00:16 -04:00
Toast notifications and undo mechanism now works! (for group and entries)
This commit is contained in:
@@ -9,7 +9,6 @@ using Windows.UI.Xaml.Navigation;
|
|||||||
using Microsoft.QueryStringDotNET;
|
using Microsoft.QueryStringDotNET;
|
||||||
using ModernKeePass.Common;
|
using ModernKeePass.Common;
|
||||||
using ModernKeePass.Interfaces;
|
using ModernKeePass.Interfaces;
|
||||||
using ModernKeePass.ViewModels;
|
|
||||||
|
|
||||||
// The Blank Application template is documented at http://go.microsoft.com/fwlink/?LinkId=234227
|
// 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
|
sealed partial class App : Application
|
||||||
{
|
{
|
||||||
public DatabaseHelper Database { get; set; } = new DatabaseHelper();
|
public DatabaseHelper Database { get; set; } = new DatabaseHelper();
|
||||||
public Queue<IPwEntity> PendingDeleteQueue = new Queue<IPwEntity>();
|
public Dictionary<string, IPwEntity> PendingDeleteEntities = new Dictionary<string, IPwEntity>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes the singleton application object. This is the first line of authored code
|
/// Initializes the singleton application object. This is the first line of authored code
|
||||||
@@ -33,6 +32,7 @@ namespace ModernKeePass
|
|||||||
Suspending += OnSuspending;
|
Suspending += OnSuspending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when the application is launched normally by the end user. Other entry points
|
/// 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.
|
/// will be used such as when the application is launched to open a specific file.
|
||||||
@@ -90,25 +90,20 @@ namespace ModernKeePass
|
|||||||
// parameter
|
// parameter
|
||||||
rootFrame.Navigate(typeof(MainPage), lauchActivatedEventArgs.Arguments);
|
rootFrame.Navigate(typeof(MainPage), lauchActivatedEventArgs.Arguments);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
//if (e is ToastNotificationActivatedEventArgs)
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/*var toastActivationArgs = e as ToastNotificationActivatedEventArgs;
|
// 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)
|
// Parse the query string (using QueryString.NET)
|
||||||
QueryString args = QueryString.Parse(toastActivationArgs.Argument);
|
UndoEntityDelete(QueryString.Parse(toastActivationArgs.Argument));
|
||||||
|
|
||||||
// See what action is being requested
|
|
||||||
switch (args["action"])
|
|
||||||
{
|
|
||||||
}*/
|
}*/
|
||||||
var entity = PendingDeleteQueue.Dequeue();
|
|
||||||
if (entity is GroupVm)
|
|
||||||
{
|
|
||||||
entity.ParentGroup.Groups.Add(entity as GroupVm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ensure the current window is active
|
// Ensure the current window is active
|
||||||
Window.Current.Activate();
|
Window.Current.Activate();
|
||||||
}
|
}
|
||||||
@@ -138,6 +133,10 @@ namespace ModernKeePass
|
|||||||
deferral.Complete();
|
deferral.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked when application is launched from opening a file in Windows Explorer
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="args">Details about the file being opened</param>
|
||||||
protected override void OnFileActivated(FileActivatedEventArgs args)
|
protected override void OnFileActivated(FileActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
base.OnFileActivated(args);
|
base.OnFileActivated(args);
|
||||||
@@ -147,5 +146,14 @@ namespace ModernKeePass
|
|||||||
Window.Current.Content = rootFrame;
|
Window.Current.Content = rootFrame;
|
||||||
Window.Current.Activate();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
97
ModernKeePass/Common/ToastNotificationHelper.cs
Normal file
97
ModernKeePass/Common/ToastNotificationHelper.cs
Normal file
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -12,5 +12,6 @@ namespace ModernKeePass.Interfaces
|
|||||||
bool IsEditMode { get; }
|
bool IsEditMode { get; }
|
||||||
|
|
||||||
void CommitDelete();
|
void CommitDelete();
|
||||||
|
void UndoDelete();
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -117,6 +117,7 @@
|
|||||||
<Compile Include="Common\ObservableDictionary.cs" />
|
<Compile Include="Common\ObservableDictionary.cs" />
|
||||||
<Compile Include="Common\RelayCommand.cs" />
|
<Compile Include="Common\RelayCommand.cs" />
|
||||||
<Compile Include="Common\SuspensionManager.cs" />
|
<Compile Include="Common\SuspensionManager.cs" />
|
||||||
|
<Compile Include="Common\ToastNotificationHelper.cs" />
|
||||||
<Compile Include="Controls\FirstItemDataTemplateSelector.cs" />
|
<Compile Include="Controls\FirstItemDataTemplateSelector.cs" />
|
||||||
<Compile Include="Controls\ListViewWithDisable.cs" />
|
<Compile Include="Controls\ListViewWithDisable.cs" />
|
||||||
<Compile Include="Controls\OpenDatabaseUserControl.xaml.cs">
|
<Compile Include="Controls\OpenDatabaseUserControl.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
|
// 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 =>
|
messageDialog.Commands.Add(new UICommand("Delete", delete =>
|
||||||
{
|
{
|
||||||
var entry = DataContext as EntryVm;
|
ToastNotificationHelper.ShowUndoToast("Entry", Model);
|
||||||
entry?.RemoveEntry();
|
Model.MarkForDelete();
|
||||||
if (Frame.CanGoBack) Frame.GoBack();
|
if (Frame.CanGoBack) Frame.GoBack();
|
||||||
}));
|
}));
|
||||||
messageDialog.Commands.Add(new UICommand("Cancel"));
|
messageDialog.Commands.Add(new UICommand("Cancel"));
|
||||||
|
@@ -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
|
// 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 =>
|
messageDialog.Commands.Add(new UICommand("Delete", delete =>
|
||||||
{
|
{
|
||||||
ShowToast("Group", Model);
|
ToastNotificationHelper.ShowUndoToast("Group", Model);
|
||||||
Model.MarkForDelete();
|
Model.MarkForDelete();
|
||||||
if (Frame.CanGoBack) Frame.GoBack();
|
if (Frame.CanGoBack) Frame.GoBack();
|
||||||
}));
|
}));
|
||||||
@@ -171,91 +171,5 @@ namespace ModernKeePass.Pages
|
|||||||
|
|
||||||
#endregion
|
#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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ using ModernKeePassLib;
|
|||||||
using ModernKeePassLib.Cryptography.PasswordGenerator;
|
using ModernKeePassLib.Cryptography.PasswordGenerator;
|
||||||
using ModernKeePassLib.Security;
|
using ModernKeePassLib.Security;
|
||||||
using System;
|
using System;
|
||||||
|
using Windows.UI.Xaml;
|
||||||
|
|
||||||
namespace ModernKeePass.ViewModels
|
namespace ModernKeePass.ViewModels
|
||||||
{
|
{
|
||||||
@@ -112,10 +113,6 @@ namespace ModernKeePass.ViewModels
|
|||||||
ParentGroup = parent;
|
ParentGroup = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveEntry()
|
|
||||||
{
|
|
||||||
ParentGroup.RemoveEntry(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void GeneratePassword()
|
public void GeneratePassword()
|
||||||
{
|
{
|
||||||
@@ -155,9 +152,21 @@ namespace ModernKeePass.ViewModels
|
|||||||
Entry?.Strings.Set(key, new ProtectedString(true, newValue));
|
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()
|
public void CommitDelete()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
Entry.ParentGroup.Entries.Remove(Entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UndoDelete()
|
||||||
|
{
|
||||||
|
ParentGroup.Entries.Add(this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -98,22 +98,10 @@ namespace ModernKeePass.ViewModels
|
|||||||
return 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()
|
public void MarkForDelete()
|
||||||
{
|
{
|
||||||
var app = (App)Application.Current;
|
var app = (App)Application.Current;
|
||||||
app.PendingDeleteQueue.Enqueue(this);
|
app.PendingDeleteEntities.Add(Id, this);
|
||||||
ParentGroup.Groups.Remove(this);
|
ParentGroup.Groups.Remove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,5 +109,9 @@ namespace ModernKeePass.ViewModels
|
|||||||
{
|
{
|
||||||
_pwGroup.ParentGroup.Groups.Remove(_pwGroup);
|
_pwGroup.ParentGroup.Groups.Remove(_pwGroup);
|
||||||
}
|
}
|
||||||
|
public void UndoDelete()
|
||||||
|
{
|
||||||
|
ParentGroup.Groups.Add(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user