Skip to content
Merged
20 changes: 8 additions & 12 deletions Core/Models/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public sealed class Database : IDatabase
public IPasswordFactory PasswordFactory { get; private set; }
public IClipboardManager ClipboardManager { get; private set; }

public event EventHandler<WarningDetectedEventArgs>? WarningDetected;
public event EventHandler<WarningsUpdatedEventArgs>? WarningsUpdated;
public event EventHandler<AutoSaveDetectedEventArgs>? AutoSaveDetected;
public event EventHandler? DatabaseSaved;
public event EventHandler<LogoutEventArgs>? DatabaseClosed;
Expand Down Expand Up @@ -465,23 +465,19 @@ private void _lookAtWarnings()
..passwordLeakedWarnings,
..duplicatedPasswordsWarnings];

IWarning[] warnings = [.. Warnings?.Where(x => User is not null && User.WarningsToNotify.HasFlag(x.WarningType)) ?? []];

if (warnings.Length != 0)
{
WarningDetected?.Invoke(this, new WarningDetectedEventArgs(warnings));
}
WarningsUpdated?.Invoke(this, new WarningsUpdatedEventArgs([.. Warnings.Where(x => User.WarningsToNotify.HasFlag(x.WarningType))]));
}
catch { }
}

private Warning[] _lookAtActivityWarnings()
{
return User is null
? throw new NullReferenceException(nameof(User))
: ActivityCenter.Activities is null
? throw new NullReferenceException(nameof(ActivityCenter.Activities))
: [new Warning([.. ActivityCenter.Activities.Where(x => x.NeedsReview).Cast<Activity>()])];
if (User is null) throw new NullReferenceException(nameof(User));
if (ActivityCenter.Activities is null) throw new NullReferenceException(nameof(ActivityCenter.Activities));

IActivity[] activities = [.. ActivityCenter.Activities.Where(x => x.NeedsReview)];

return activities.Length != 0 ? [new Warning([.. activities])] : [];
}

private Warning[] _lookAtPasswordUpdateReminderWarnings()
Expand Down
31 changes: 31 additions & 0 deletions GUI/WPF/Helper/WindowHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Upsilon.Apps.Passkey.GUI.WPF.Themes;

namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
{
Expand All @@ -20,5 +22,34 @@ public static bool GetIsBusy(this UserControl control)
{
return Window.GetWindow(control).GetIsBusy();
}

public static void PostLoadSetup(this Window window)
{
DarkMode.SetDarkMode(window);
ComputeTabIndex(window);
}

public static void ComputeTabIndex(this Window window)
{
int tabIndex = 0;
_computeTabIndex(window, ref tabIndex);
}

private static void _computeTabIndex(DependencyObject depObj, ref int tabIndex)
{
if (depObj == null) return;

for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject ithChild = VisualTreeHelper.GetChild(depObj, i);

if (ithChild is Control control)
{
control.TabIndex = tabIndex++;
}

_computeTabIndex(ithChild, ref tabIndex);
}
}
}
}
12 changes: 12 additions & 0 deletions GUI/WPF/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
ResizeMode="CanMinimize"
WindowStartupLocation="CenterScreen"
Height="140" Width="500">
<Window.CommandBindings>
<CommandBinding Command="Open" Executed="_openDatabase_MenuItem_Click"/>
<CommandBinding Command="New" Executed="_newUser_MenuItem_Click"/>
</Window.CommandBindings>
<Window.InputBindings>
<KeyBinding Key="O" Modifiers="Control" Command="Open"/>
<KeyBinding Key="N" Modifiers="Control" Command="New"/>
</Window.InputBindings>
<StackPanel>
<Menu>
<Menu.ItemsPanel>
Expand All @@ -20,8 +28,12 @@
</ItemsPanelTemplate>
</Menu.ItemsPanel>
<MenuItem Header="_Open database"
Command="Open"
InputGestureText="Ctrl+O"
Click="_openDatabase_MenuItem_Click"/>
<MenuItem Header="_New User"
Command="New"
InputGestureText="Ctrl+N"
Click="_newUser_MenuItem_Click"/>
<MenuItem Header="_Generate random Password"
HorizontalAlignment="Right"
Expand Down
9 changes: 6 additions & 3 deletions GUI/WPF/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using System.Windows.Input;
using System.Windows.Threading;
using Upsilon.Apps.Passkey.Core.Models;
using Upsilon.Apps.Passkey.GUI.WPF.Themes;
using Upsilon.Apps.Passkey.GUI.WPF.Helper;
using Upsilon.Apps.Passkey.GUI.WPF.ViewModels;
using Upsilon.Apps.Passkey.GUI.WPF.Views;

Expand All @@ -30,7 +30,6 @@ public MainWindow()
};

_resetCredentials();
MainViewModel.Database = null;

try
{
Expand All @@ -46,12 +45,13 @@ public MainWindow()
_username_TB.KeyUp += _credential_TB_KeyUp;
_password_PB.KeyUp += _credential_TB_KeyUp;
_timer.Tick += _timer_Elapsed;
Loaded += (s, e) => DarkMode.SetDarkMode(this);
Loaded += (s, e) => this.PostLoadSetup();
}

private void _timer_Elapsed(object? sender, EventArgs e)
{
_resetCredentials();
MainViewModel.Database?.Close();
MainViewModel.Database = null;
}

Expand Down Expand Up @@ -143,6 +143,7 @@ private void _credential_TB_KeyUp(object sender, KeyEventArgs e)
else if (e.Key == Key.Escape)
{
_resetCredentials();
MainViewModel.Database?.Close();
MainViewModel.Database = null;
}
else
Expand All @@ -154,6 +155,8 @@ private void _credential_TB_KeyUp(object sender, KeyEventArgs e)

private void _database_AutoSaveDetected(object? sender, Interfaces.Events.AutoSaveDetectedEventArgs e)
{
Hide();

MessageBoxResult result = MessageBox.Show("Unsaved changes have been detected.\nClick Yes to apply these changes.\nClick No to discard them.\nClick Cancel to ignore and keep the save file.", "Autosave detected", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);

e.MergeBehavior = result switch
Expand Down
8 changes: 8 additions & 0 deletions GUI/WPF/ViewModels/Controls/AccountViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,14 @@ public bool PasswordLeaked
&& MainViewModel.Database.Warnings.Any(x => x.WarningType == WarningType.PasswordLeakedWarning
&& x.Accounts.Contains(Account));

public string[] IdentifierAutoCompleteList => MainViewModel.User?.Services
.SelectMany(x => x.Accounts)
.SelectMany(x => x.Identifiers)
.Distinct()
.Where(x => !string.IsNullOrEmpty(x))
.OrderBy(x => x)
.ToArray() ?? [];

public event PropertyChangedEventHandler? PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
Expand Down
6 changes: 0 additions & 6 deletions GUI/WPF/ViewModels/Controls/IdentifiantViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,6 @@ public class IdentifierViewModel : INotifyPropertyChanged
private readonly IAccount _account;

public Brush IdentifierBackground => _account.HasChanged("Identifiers") ? DarkMode.ChangedBrush : DarkMode.UnchangedBrush2;
public string[] IdentifierAutoCompleteList => _account.Database.User?.Services
.SelectMany(x => x.Accounts)
.SelectMany(x => x.Identifiers)
.Distinct()
.OrderBy(x => x)
.ToArray() ?? [];

public string Identifier
{
Expand Down
46 changes: 46 additions & 0 deletions GUI/WPF/ViewModels/InsertIdentifierViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
using Upsilon.Apps.Passkey.GUI.WPF.Helper;

namespace Upsilon.Apps.Passkey.GUI.WPF.ViewModels
{
internal class InsertIdentifierViewModel(IEnumerable<string> identifiers, string identifier) : INotifyPropertyChanged
{
private readonly string[] _identifiers = [.. identifiers];

public ObservableCollection<string> Identifiers = [.. identifiers.Where(x => x.StartsWith(identifier.Trim(), StringComparison.CurrentCultureIgnoreCase)),
.. identifiers.Where(x => x.Contains(identifier.Trim(), StringComparison.CurrentCultureIgnoreCase)
&& !x.StartsWith(identifier.Trim(), StringComparison.CurrentCultureIgnoreCase))];

public string Identifier
{
get => field.Trim();
set
{
PropertyHelper.SetProperty(ref field, value.Trim(), this, PropertyChanged);
_refreshFilter();
}
} = identifier;

public event PropertyChangedEventHandler? PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

private void _refreshFilter()
{
Identifiers.Clear();

string[] identifiers = [.. _identifiers.Where(x => x.StartsWith(Identifier, StringComparison.CurrentCultureIgnoreCase)),
.. _identifiers.Where(x => x.Contains(Identifier, StringComparison.CurrentCultureIgnoreCase)
&& !x.StartsWith(Identifier, StringComparison.CurrentCultureIgnoreCase))];

foreach (string identifier in identifiers)
{
Identifiers.Add(identifier);
}
}
}
}
2 changes: 1 addition & 1 deletion GUI/WPF/ViewModels/MainViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static string AppTitle
get
{
System.Reflection.AssemblyName package = System.Reflection.Assembly.GetExecutingAssembly().GetName();
string? packageVersion = package.Version?.ToString(2);
string? packageVersion = package.Version?.ToString(3);

return $"{package.Name} v{packageVersion}";
}
Expand Down
9 changes: 9 additions & 0 deletions GUI/WPF/ViewModels/UserActivitiesViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,25 @@ public UserActivitiesViewModel()
RefreshFilters();
}

private bool _locked = false;
public void ClearFilters()
{
_locked = true;

FromDateFilter = ToDateFilter = DateTime.Now.Date.AddDays(1);
EventType = ActivityEventType.None;
Message = string.Empty;
NeedsReview = false;

_locked = false;

RefreshFilters();
}

public void RefreshFilters(string itemId = "")
{
if (_locked) return;

Activities.Clear();

if (MainViewModel.Database?.Activities is null) return;
Expand Down
3 changes: 1 addition & 2 deletions GUI/WPF/Views/AccountPasswordsWarningView.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Windows;
using Upsilon.Apps.Passkey.GUI.WPF.Helper;
using Upsilon.Apps.Passkey.GUI.WPF.Themes;
using Upsilon.Apps.Passkey.GUI.WPF.ViewModels;
using Upsilon.Apps.Passkey.Interfaces.Enums;

Expand Down Expand Up @@ -28,7 +27,7 @@ internal AccountPasswordsWarningView(WarningType warningType)

_warnings_DGV.ItemsSource = _viewModel.Warnings;

Loaded += (s, e) => DarkMode.SetDarkMode(this);
Loaded += (s, e) => this.PostLoadSetup();
}

private void _filterClear_Button_Click(object sender, RoutedEventArgs e)
Expand Down
6 changes: 3 additions & 3 deletions GUI/WPF/Views/Controls/AccountView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@
Name="_identifiers_LB">
<ListBox.ItemTemplate>
<DataTemplate>
<local:AutoCompleteTextBox ItemsSource="{Binding IdentifierAutoCompleteList}"
Background="{Binding IdentifierBackground}"
Text="{Binding Identifier, Mode=TwoWay}"/>
<TextBox Background="{Binding IdentifierBackground}"
Text="{Binding Identifier, Mode=TwoWay}"
KeyUp="_identifier_TextBox_KeyUp"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Expand Down
19 changes: 19 additions & 0 deletions GUI/WPF/Views/Controls/AccountView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public partial class AccountView : UserControl
{
private AccountViewModel? _viewModel;

internal string? GetAccountId() => _viewModel?.Account.ItemId;

public AccountView()
{
InitializeComponent();
Expand Down Expand Up @@ -255,5 +257,22 @@ private void _viewActivities_Button_Click(object sender, RoutedEventArgs e)
MainViewModel.UserActivitiesView.ViewModel.RefreshFilters(_viewModel.Account.ItemId);
MainViewModel.UserActivitiesView.Show();
}

private void _identifier_TextBox_KeyUp(object sender, KeyEventArgs e)
{
if (this.GetIsBusy()) return;

if (sender is not TextBox identifier_TB) return;

if (e.Key is Key.Enter
or Key.Insert)
{
string? identifier = InsertIdentifierView.InsertIdentifierDialog(_viewModel?.IdentifierAutoCompleteList ?? [], identifier_TB.Text);

if (string.IsNullOrEmpty(identifier)) return;

identifier_TB.Text = identifier;
}
}
}
}
18 changes: 0 additions & 18 deletions GUI/WPF/Views/Controls/AutoCompleteTextBox.xaml

This file was deleted.

Loading
Loading