WIP toast notifications

WIP layout and color changes
This commit is contained in:
2017-10-17 18:46:05 +02:00
committed by BONNEVILLE Geoffroy
parent 19008cdad2
commit 01ed1bc9c1
10 changed files with 211 additions and 28 deletions

View File

@@ -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<IPwEntity> PendingDeleteQueue = new Queue<IPwEntity>();
/// <summary>
/// Initializes the singleton application object. This is the first line of authored code
@@ -38,6 +40,16 @@ namespace ModernKeePass
/// <param name="e">Details about the launch request and process.</param>
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 (e is LaunchActivatedEventArgs)
{
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), e.Arguments);
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();
}

View File

@@ -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();
}
}

View File

@@ -130,6 +130,7 @@
<Compile Include="Converters\ProgressBarLegalValuesConverter.cs" />
<Compile Include="Events\PasswordEventArgs.cs" />
<Compile Include="Interfaces\IIsEnabled.cs" />
<Compile Include="Interfaces\IPwEntity.cs" />
<Compile Include="MainPage.xaml.cs">
<DependentUpon>MainPage.xaml</DependentUpon>
</Compile>
@@ -243,10 +244,18 @@
<HintPath>..\packages\Portable.BouncyCastle.1.8.1.3\lib\netstandard1.0\BouncyCastle.Crypto.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Toolkit.Uwp.Notifications, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Toolkit.Uwp.Notifications.2.0.0\lib\dotnet\Microsoft.Toolkit.Uwp.Notifications.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ModernKeePassLib, Version=2.28.1.4000, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ModernKeePassLib.2.28.4000\lib\netstandard1.2\ModernKeePassLib.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="QueryString.NETCore, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\QueryString.NET.1.0.0\lib\dotnet\QueryString.NETCore.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Splat, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Splat.2.0.0\lib\Portable-Win81+Wpa81\Splat.dll</HintPath>
<Private>True</Private>

View File

@@ -51,23 +51,23 @@
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="GlyphElement"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxButtonPointerOverForegroundThemeBrush}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxButtonPointerOverBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<!--<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement"
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement"
Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxButtonPressedBackgroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>-->
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement"
Storyboard.TargetProperty="BorderBrush">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxButtonPressedBorderThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="GlyphElement"
Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxButtonPointerOverBackgroundThemeBrush}" />
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource TextBoxButtonPointerOverForegroundThemeBrush}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
@@ -406,7 +406,7 @@
<TextBox
Grid.Column="1"
x:Name="TitleTextBox"
Text="{Binding Title, Mode=TwoWay}"
Text="{Binding Name, Mode=TwoWay}"
Style="{StaticResource HeaderTextBoxStyle}"
Foreground="{ThemeResource DefaultTextForegroundThemeBrush}"
IsHitTestVisible="{Binding IsEditMode}"

View File

@@ -68,7 +68,7 @@
<CompositeTransform ScaleX="2" TranslateX="0" TranslateY="0" ScaleY="2"/>
</SymbolIcon.RenderTransform>
</SymbolIcon>
<TextBlock Grid.Column="1" Text="{Binding Title}" FontWeight="Bold" Style="{ThemeResource TitleTextBlockStyle}" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="13,0,0,5"/>
<TextBlock Grid.Column="1" Text="{Binding Name}" FontWeight="Bold" Style="{ThemeResource TitleTextBlockStyle}" TextWrapping="NoWrap" VerticalAlignment="Center" Margin="13,0,0,5"/>
</Grid>
</Border>
</DataTemplate>
@@ -90,7 +90,7 @@
</SymbolIcon>
</Border>
<StackPanel Grid.Column="1" VerticalAlignment="Top" Margin="10,10,0,0" >
<TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}"/>
<TextBlock Text="{Binding Name}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap" Foreground="{Binding ForegroundColor, ConverterParameter={StaticResource TextBoxForegroundThemeBrush}, Converter={StaticResource ColorToBrushConverter}}"/>
<TextBlock Style="{StaticResource CaptionTextBlockStyle}" TextWrapping="NoWrap"/>
<!--<TextBlock Text="{Binding EntryCount, ConverterParameter=entry\,entries, Converter={StaticResource PluralizationConverter}}" Style="{StaticResource BodyTextBlockStyle}" MaxHeight="60" />
<TextBlock Text="{Binding GroupCount, ConverterParameter=group\,groups, Converter={StaticResource PluralizationConverter}}" Style="{StaticResource BodyTextBlockStyle}" MaxHeight="60" />-->
@@ -185,7 +185,7 @@
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<TextBlock Text="{Binding Title}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
<TextBlock Text="{Binding Name}" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -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);
}
}
@@ -164,5 +171,90 @@ 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();
}
}
}
}

View File

@@ -25,7 +25,14 @@
<TextBlock Margin="25,10,0,10" Text="{Binding Name}" />
<local:OpenDatabaseUserControl Password="{Binding Password, Mode=TwoWay}" CreateNew="True" ValidationChecked="PasswordUserControl_PasswordChecked" />
<TextBlock Margin="25,0,0,10">Password complexity</TextBlock>
<ProgressBar Margin="25,0,0,10" Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}, Mode=OneWay}" Maximum="128" Width="300" HorizontalAlignment="Left" />
<ProgressBar Margin="25,0,0,10" Value="{Binding PasswordComplexityIndicator, ConverterParameter=0\,128, Converter={StaticResource ProgressBarLegalValuesConverter}, Mode=OneWay}" Maximum="128" Width="300" HorizontalAlignment="Left" >
<ProgressBar.Background>
<LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
<GradientStop Color="#FFFD0000"/>
<GradientStop Color="#FF2EFF00" Offset="1"/>
</LinearGradientBrush>
</ProgressBar.Background>
</ProgressBar>
</StackPanel>
</Border>
</StackPanel>

View File

@@ -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();
}
}
}

View File

@@ -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<EntryVm> Entries { get; set; } = new ObservableCollection<EntryVm>();
@@ -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<IGrouping<char, EntryVm>> 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<EntryVm>(pwGroup.Entries.Select(e => new EntryVm(e, this)).OrderBy(e => e.Title));
Entries = new ObservableCollection<EntryVm>(pwGroup.Entries.Select(e => new EntryVm(e, this)).OrderBy(e => e.Name));
Entries.Insert(0, new EntryVm ());
Groups = new ObservableCollection<GroupVm>(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);
}
}
}

View File

@@ -2,9 +2,11 @@
<packages>
<package id="Microsoft.NETCore.Platforms" version="2.0.0" targetFramework="win81" />
<package id="Microsoft.NETCore.Portable.Compatibility" version="1.0.2" targetFramework="win81" />
<package id="Microsoft.Toolkit.Uwp.Notifications" version="2.0.0" targetFramework="win81" />
<package id="ModernKeePassLib" version="2.28.4000" targetFramework="win81" />
<package id="NETStandard.Library" version="2.0.0" targetFramework="win81" />
<package id="Portable.BouncyCastle" version="1.8.1.3" targetFramework="win81" />
<package id="QueryString.NET" version="1.0.0" targetFramework="win81" />
<package id="Splat" version="2.0.0" targetFramework="win81" />
<package id="System.Collections" version="4.0.11" targetFramework="win81" />
<package id="System.Diagnostics.Debug" version="4.0.11" targetFramework="win81" />