From 01ed1bc9c1e0ca203697622bf66fdae12d3d2370 Mon Sep 17 00:00:00 2001 From: Geoffroy Bonneville Date: Tue, 17 Oct 2017 18:46:05 +0200 Subject: [PATCH] WIP toast notifications WIP layout and color changes --- ModernKeePass/App.xaml.cs | 52 ++++++++-- ModernKeePass/Interfaces/IPwEntity.cs | 16 +++ ModernKeePass/ModernKeePass.csproj | 9 ++ ModernKeePass/Pages/EntryDetailPage.xaml | 10 +- ModernKeePass/Pages/GroupDetailPage.xaml | 6 +- ModernKeePass/Pages/GroupDetailPage.xaml.cs | 102 +++++++++++++++++++- ModernKeePass/Pages/NewDatabasePage.xaml | 9 +- ModernKeePass/ViewModels/EntryVm.cs | 11 ++- ModernKeePass/ViewModels/GroupVm.cs | 22 ++++- ModernKeePass/packages.config | 2 + 10 files changed, 211 insertions(+), 28 deletions(-) create mode 100644 ModernKeePass/Interfaces/IPwEntity.cs diff --git a/ModernKeePass/App.xaml.cs b/ModernKeePass/App.xaml.cs index 01b53e8..e720fe1 100644 --- a/ModernKeePass/App.xaml.cs +++ b/ModernKeePass/App.xaml.cs @@ -1,14 +1,15 @@ using System; +using System.Collections.Generic; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; -using Windows.ApplicationModel.Search; -using Windows.Foundation; using Windows.Storage; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; 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 @@ -20,6 +21,7 @@ namespace ModernKeePass sealed partial class App : Application { public DatabaseHelper Database { get; set; } = new DatabaseHelper(); + public Queue PendingDeleteQueue = new Queue(); /// /// Initializes the singleton application object. This is the first line of authored code @@ -38,6 +40,16 @@ namespace ModernKeePass /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs e) { + OnLaunchOrActivated(e); + } + + protected override void OnActivated(IActivatedEventArgs args) + { + OnLaunchOrActivated(args); + } + + private void OnLaunchOrActivated(IActivatedEventArgs e) + { #if DEBUG if (System.Diagnostics.Debugger.IsAttached) @@ -68,12 +80,34 @@ namespace ModernKeePass Window.Current.Content = rootFrame; } - if (rootFrame.Content == null) + if (e is LaunchActivatedEventArgs) { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - rootFrame.Navigate(typeof(MainPage), e.Arguments); + var lauchActivatedEventArgs = e as LaunchActivatedEventArgs; + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // 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"]) + { + }*/ + var entity = PendingDeleteQueue.Dequeue(); + if (entity is GroupVm) + { + entity.ParentGroup.Groups.Add(entity as GroupVm); + } } // Ensure the current window is active Window.Current.Activate(); @@ -100,7 +134,7 @@ namespace ModernKeePass { var deferral = e.SuspendingOperation.GetDeferral(); //TODO: Save application state and stop any background activity - //Database.Save(); + Database.Save(); deferral.Complete(); } diff --git a/ModernKeePass/Interfaces/IPwEntity.cs b/ModernKeePass/Interfaces/IPwEntity.cs new file mode 100644 index 0000000..9ebcea3 --- /dev/null +++ b/ModernKeePass/Interfaces/IPwEntity.cs @@ -0,0 +1,16 @@ +using Windows.UI.Xaml.Controls; +using ModernKeePass.ViewModels; + +namespace ModernKeePass.Interfaces +{ + public interface IPwEntity + { + GroupVm ParentGroup { get; } + Symbol IconSymbol { get; } + string Id { get; } + string Name { get; set; } + bool IsEditMode { get; } + + void CommitDelete(); + } +} \ No newline at end of file diff --git a/ModernKeePass/ModernKeePass.csproj b/ModernKeePass/ModernKeePass.csproj index 4ebea25..9553ba4 100644 --- a/ModernKeePass/ModernKeePass.csproj +++ b/ModernKeePass/ModernKeePass.csproj @@ -130,6 +130,7 @@ + MainPage.xaml @@ -243,10 +244,18 @@ ..\packages\Portable.BouncyCastle.1.8.1.3\lib\netstandard1.0\BouncyCastle.Crypto.dll True + + ..\packages\Microsoft.Toolkit.Uwp.Notifications.2.0.0\lib\dotnet\Microsoft.Toolkit.Uwp.Notifications.dll + True + ..\packages\ModernKeePassLib.2.28.4000\lib\netstandard1.2\ModernKeePassLib.dll True + + ..\packages\QueryString.NET.1.0.0\lib\dotnet\QueryString.NETCore.dll + True + ..\packages\Splat.2.0.0\lib\Portable-Win81+Wpa81\Splat.dll True diff --git a/ModernKeePass/Pages/EntryDetailPage.xaml b/ModernKeePass/Pages/EntryDetailPage.xaml index 4f2aa01..ff44f20 100644 --- a/ModernKeePass/Pages/EntryDetailPage.xaml +++ b/ModernKeePass/Pages/EntryDetailPage.xaml @@ -51,23 +51,23 @@ - + - + - + @@ -406,7 +406,7 @@ - + @@ -90,7 +90,7 @@ - + @@ -185,7 +185,7 @@ - + diff --git a/ModernKeePass/Pages/GroupDetailPage.xaml.cs b/ModernKeePass/Pages/GroupDetailPage.xaml.cs index dbfe124..c37a551 100644 --- a/ModernKeePass/Pages/GroupDetailPage.xaml.cs +++ b/ModernKeePass/Pages/GroupDetailPage.xaml.cs @@ -1,13 +1,19 @@ using System; using System.Linq; using System.Threading.Tasks; +using Windows.ApplicationModel.Background; +using Windows.Data.Xml.Dom; using Windows.Storage.Streams; using Windows.UI.Core; +using Windows.UI.Notifications; using Windows.UI.Popups; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Navigation; +using Microsoft.QueryStringDotNET; +using Microsoft.Toolkit.Uwp.Notifications; using ModernKeePass.Common; +using ModernKeePass.Interfaces; using ModernKeePass.ViewModels; // The Group Detail Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234229 @@ -114,7 +120,7 @@ namespace ModernKeePass.Pages Frame.Navigate(typeof(EntryDetailPage), entry); } - private async void DeleteButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) + private async void DeleteButton_Click(object sender, RoutedEventArgs e) { // Create the message dialog and set its content var messageDialog = new MessageDialog("Are you sure you want to delete the whole group and all its entries?"); @@ -122,7 +128,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 => { - Model.RemoveGroup(); + ShowToast("Group", Model); + Model.MarkForDelete(); if (Frame.CanGoBack) Frame.GoBack(); })); messageDialog.Commands.Add(new UICommand("Cancel")); @@ -149,10 +156,10 @@ namespace ModernKeePass.Pages private void SearchBox_OnSuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args) { var imageUri = RandomAccessStreamReference.CreateFromUri(new Uri("ms-appx://Assets/Logo.scale-80.png")); - var results = Model.Entries.Skip(1).Where(e => e.Title.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5); + var results = Model.Entries.Skip(1).Where(e => e.Name.IndexOf(args.QueryText, StringComparison.OrdinalIgnoreCase) >= 0).Take(5); foreach (var result in results) { - args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Title, result.ParentGroup.Name, result.Id, imageUri, string.Empty); + args.Request.SearchSuggestionCollection.AppendResultSuggestion(result.Name, result.ParentGroup.Name, result.Id, imageUri, string.Empty); } } @@ -163,6 +170,91 @@ 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." + } + }/*, + + AppLogoOverride = new ToastGenericAppLogo() + { + Source = logo, + HintCrop = ToastGenericAppLogoCrop.Circle + }*/ + } + }; + + // 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 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/Pages/NewDatabasePage.xaml b/ModernKeePass/Pages/NewDatabasePage.xaml index ca5b38d..175f1ed 100644 --- a/ModernKeePass/Pages/NewDatabasePage.xaml +++ b/ModernKeePass/Pages/NewDatabasePage.xaml @@ -25,7 +25,14 @@ Password complexity - + + + + + + + + diff --git a/ModernKeePass/ViewModels/EntryVm.cs b/ModernKeePass/ViewModels/EntryVm.cs index b934617..282b6e4 100644 --- a/ModernKeePass/ViewModels/EntryVm.cs +++ b/ModernKeePass/ViewModels/EntryVm.cs @@ -1,13 +1,15 @@ using System.ComponentModel; using Windows.UI.Xaml.Controls; +using ModernKeePass.Interfaces; using ModernKeePass.Mappings; using ModernKeePassLib; using ModernKeePassLib.Cryptography.PasswordGenerator; using ModernKeePassLib.Security; +using System; namespace ModernKeePass.ViewModels { - public class EntryVm : INotifyPropertyChanged + public class EntryVm : INotifyPropertyChanged, IPwEntity { public GroupVm ParentGroup { get; } public PwEntry Entry { get; } @@ -27,7 +29,7 @@ namespace ModernKeePass.ViewModels public bool BracketsPatternSelected { get; set; } public string CustomChars { get; set; } = string.Empty; - public string Title + public string Name { get { @@ -152,5 +154,10 @@ namespace ModernKeePass.ViewModels { Entry?.Strings.Set(key, new ProtectedString(true, newValue)); } + + public void CommitDelete() + { + throw new NotImplementedException(); + } } } diff --git a/ModernKeePass/ViewModels/GroupVm.cs b/ModernKeePass/ViewModels/GroupVm.cs index 9d5f73b..bc9d3e8 100644 --- a/ModernKeePass/ViewModels/GroupVm.cs +++ b/ModernKeePass/ViewModels/GroupVm.cs @@ -1,14 +1,17 @@ using System.Collections.ObjectModel; using System.Linq; using Windows.UI.Text; +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using ModernKeePass.Common; +using ModernKeePass.Interfaces; using ModernKeePass.Mappings; using ModernKeePassLib; +using System; namespace ModernKeePass.ViewModels { - public class GroupVm : NotifyPropertyChangedBase + public class GroupVm : NotifyPropertyChangedBase, IPwEntity { public GroupVm ParentGroup { get; } public ObservableCollection Entries { get; set; } = new ObservableCollection(); @@ -19,6 +22,7 @@ namespace ModernKeePass.ViewModels public int GroupCount => Groups.Count - 1; public bool IsNotRoot => ParentGroup != null; public FontWeight FontWeight => _pwGroup == null ? FontWeights.Bold : FontWeights.Normal; + public string Id => _pwGroup.Uuid.ToHexString(); public IOrderedEnumerable> EntriesZoomedOut { @@ -26,7 +30,7 @@ namespace ModernKeePass.ViewModels { return from e in Entries where e.Entry != null - group e by e.Title.FirstOrDefault() into grp + group e by e.Name.FirstOrDefault() into grp orderby grp.Key select grp; } @@ -70,7 +74,7 @@ namespace ModernKeePass.ViewModels { _pwGroup = pwGroup; ParentGroup = parent; - Entries = new ObservableCollection(pwGroup.Entries.Select(e => new EntryVm(e, this)).OrderBy(e => e.Title)); + Entries = new ObservableCollection(pwGroup.Entries.Select(e => new EntryVm(e, this)).OrderBy(e => e.Name)); Entries.Insert(0, new EntryVm ()); Groups = new ObservableCollection(pwGroup.Groups.Select(g => new GroupVm(g, this)).OrderBy(g => g.Name)); Groups.Insert(0, new GroupVm ()); @@ -105,5 +109,17 @@ namespace ModernKeePass.ViewModels _pwGroup.Entries.Remove(entry.Entry); Entries.Remove(entry); } + + public void MarkForDelete() + { + var app = (App)Application.Current; + app.PendingDeleteQueue.Enqueue(this); + ParentGroup.Groups.Remove(this); + } + + public void CommitDelete() + { + _pwGroup.ParentGroup.Groups.Remove(_pwGroup); + } } } diff --git a/ModernKeePass/packages.config b/ModernKeePass/packages.config index 9b8a54b..6e62dc2 100644 --- a/ModernKeePass/packages.config +++ b/ModernKeePass/packages.config @@ -2,9 +2,11 @@ + +