Skip to content

Commit c49c887

Browse files
authored
Merge pull request #36 from YassinLokhat/27-add-gui-client
27 add wpf gui client
2 parents b8c50ef + 03920b0 commit c49c887

64 files changed

Lines changed: 5270 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

GUI/WPF/App.xaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<Application x:Class="Upsilon.Apps.Passkey.GUI.WPF.App"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:local="clr-namespace:Upsilon.Apps.Passkey.GUI.WPF"
5+
StartupUri="MainWindow.xaml">
6+
<Application.Resources>
7+
<ResourceDictionary>
8+
<ResourceDictionary.MergedDictionaries>
9+
<ResourceDictionary Source="Themes/DarkTheme.xaml"/>
10+
</ResourceDictionary.MergedDictionaries>
11+
</ResourceDictionary>
12+
</Application.Resources>
13+
</Application>

GUI/WPF/App.xaml.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Windows;
2+
3+
namespace Upsilon.Apps.Passkey.GUI.WPF
4+
{
5+
/// <summary>
6+
/// Interaction logic for App.xaml
7+
/// </summary>
8+
public partial class App : Application
9+
{
10+
}
11+
12+
}

GUI/WPF/AssemblyInfo.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System.Windows;
2+
3+
[assembly: ThemeInfo(
4+
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5+
//(used if a resource is not found in the page,
6+
// or application resource dictionaries)
7+
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8+
//(used if a resource is not found in the page,
9+
// app, or any theme specific resource dictionaries)
10+
)]

GUI/WPF/Helper/EnumHelper.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using Upsilon.Apps.Passkey.Core.Utils;
2+
using Upsilon.Apps.Passkey.Interfaces.Enums;
3+
4+
namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
5+
{
6+
internal static class EnumHelper
7+
{
8+
public static string ToReadableString(this ActivityEventType eventType)
9+
{
10+
return eventType switch
11+
{
12+
ActivityEventType.None => "All",
13+
ActivityEventType.MergeAndSaveThenRemoveAutoSaveFile => "Auto-save merged then saved",
14+
ActivityEventType.MergeWithoutSavingAndKeepAutoSaveFile => "Auto-save merged but not saved",
15+
ActivityEventType.DontMergeAndRemoveAutoSaveFile => "Auto-save discarded",
16+
ActivityEventType.DontMergeAndKeepAutoSaveFile => "Auto-save not merged and keeped",
17+
ActivityEventType.DatabaseCreated
18+
or ActivityEventType.DatabaseOpened
19+
or ActivityEventType.DatabaseSaved
20+
or ActivityEventType.DatabaseClosed
21+
or ActivityEventType.LoginSessionTimeoutReached
22+
or ActivityEventType.LoginFailed
23+
or ActivityEventType.UserLoggedIn
24+
or ActivityEventType.UserLoggedOut
25+
or ActivityEventType.ImportingDataStarted
26+
or ActivityEventType.ImportingDataSucceded
27+
or ActivityEventType.ImportingDataFailed
28+
or ActivityEventType.ExportingDataStarted
29+
or ActivityEventType.ExportingDataSucceded
30+
or ActivityEventType.ExportingDataFailed
31+
or ActivityEventType.ItemUpdated
32+
or ActivityEventType.ItemAdded
33+
or ActivityEventType.ItemDeleted => eventType.ToString().ToSentenceCase(),
34+
_ => throw new InvalidOperationException($"'{eventType}' event type not handled"),
35+
};
36+
}
37+
38+
public static ActivityEventType ActivityEventTypeFromReadableString(string readableString)
39+
{
40+
try
41+
{
42+
return Enum.GetValues<ActivityEventType>()
43+
.Cast<ActivityEventType>()
44+
.First(x => x.ToReadableString() == readableString);
45+
}
46+
catch
47+
{
48+
throw new InvalidOperationException($"'{readableString}' event type not handled");
49+
}
50+
}
51+
52+
public static string ToReadableString(this WarningType warningType)
53+
{
54+
return warningType switch
55+
{
56+
WarningType.PasswordUpdateReminderWarning | WarningType.PasswordLeakedWarning => "All",
57+
WarningType.PasswordUpdateReminderWarning => "Expired passwords",
58+
WarningType.PasswordLeakedWarning => "Leaked passwords",
59+
_ => throw new InvalidOperationException($"'{warningType}' warning type not handled"),
60+
};
61+
}
62+
63+
public static WarningType ActivityWarningTypeFromReadableString(string readableString)
64+
{
65+
return readableString switch
66+
{
67+
"All" => WarningType.PasswordUpdateReminderWarning | WarningType.PasswordLeakedWarning,
68+
"Expired passwords" => WarningType.PasswordUpdateReminderWarning,
69+
"Leaked passwords" => WarningType.PasswordLeakedWarning,
70+
_ => throw new InvalidOperationException($"'{readableString}' warning type not handled"),
71+
};
72+
}
73+
}
74+
}

GUI/WPF/Helper/HotKeyHelper.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System.Runtime.InteropServices;
2+
using System.Windows;
3+
using System.Windows.Input;
4+
using System.Windows.Interop;
5+
6+
namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
7+
{
8+
public static class HotkeyHelper
9+
{
10+
private static int _id = 0;
11+
12+
public static event EventHandler<HotkeyEventArgs>? HotkeyPressed;
13+
14+
public static int Register(Window window, ModifierKeys modifiers, Key key)
15+
{
16+
int hotkeyId = Interlocked.Increment(ref _id);
17+
uint virtualKey = (uint)KeyInterop.VirtualKeyFromKey(key);
18+
19+
IntPtr hWnd = new WindowInteropHelper(window).Handle;
20+
if (hWnd == IntPtr.Zero)
21+
return -1;
22+
23+
bool success = RegisterHotKey(hWnd, hotkeyId, (uint)modifiers, virtualKey);
24+
if (!success)
25+
return -1;
26+
27+
if (PresentationSource.FromVisual(window) is HwndSource source)
28+
{
29+
source.AddHook((hwnd, msg, wParam, lParam, ref handled) =>
30+
{
31+
if (msg == 0x0312 && (int)wParam == hotkeyId)
32+
{
33+
HotkeyEventArgs e = new(lParam);
34+
HotkeyPressed?.Invoke(window, e);
35+
handled = true;
36+
}
37+
return IntPtr.Zero;
38+
});
39+
}
40+
41+
return hotkeyId;
42+
}
43+
44+
public static bool Unregister(Window window, int hotkeyId)
45+
{
46+
if (window is null)
47+
return false;
48+
49+
IntPtr hWnd = new WindowInteropHelper(window).Handle;
50+
return hWnd != nint.Zero && UnregisterHotKey(hWnd, hotkeyId);
51+
}
52+
53+
public static void Send(ModifierKeys modifiers, Key key)
54+
{
55+
//byte[] modifiers = [];
56+
byte virtualKey = (byte)KeyInterop.VirtualKeyFromKey(key);
57+
58+
keybd_event((byte)modifiers, 0, 0, UIntPtr.Zero);
59+
60+
keybd_event(virtualKey, 0, 0, UIntPtr.Zero);
61+
keybd_event(virtualKey, 0, 0x0002, UIntPtr.Zero);
62+
63+
keybd_event((byte)modifiers, 0, 0x0002, UIntPtr.Zero);
64+
}
65+
66+
[DllImport("user32.dll")]
67+
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
68+
69+
[DllImport("user32.dll")]
70+
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
71+
72+
[DllImport("user32.dll")]
73+
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
74+
75+
[DllImport("user32.dll")]
76+
private static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo);
77+
}
78+
79+
public class HotkeyEventArgs : EventArgs
80+
{
81+
public readonly Key Key;
82+
public readonly ModifierKeys Modifiers;
83+
84+
internal HotkeyEventArgs(IntPtr hotKeyParam)
85+
{
86+
uint param = (uint)hotKeyParam.ToInt64();
87+
int virtualKey = (int)((param & 0xffff0000) >> 16);
88+
Key = KeyInterop.KeyFromVirtualKey(virtualKey);
89+
Modifiers = (ModifierKeys)(param & 0x0000ffff);
90+
}
91+
}
92+
}

GUI/WPF/Helper/IItemHelper.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using Upsilon.Apps.Passkey.Interfaces.Models;
2+
3+
namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
4+
{
5+
public static class IItemHelper
6+
{
7+
public static void Shake(this IUser user)
8+
{
9+
_ = user.ItemId;
10+
}
11+
12+
public static bool MeetsFilterConditions(this IService service, string serviceFilter, string identifierFilter, string globalTextFilter, bool changedItemsOnly)
13+
{
14+
serviceFilter = serviceFilter.ToLower().Trim();
15+
identifierFilter = identifierFilter.ToLower().Trim();
16+
globalTextFilter = globalTextFilter.ToLower().Trim();
17+
18+
string serviceId = service.ItemId.Replace(service.User.ItemId, string.Empty).ToLower().Trim();
19+
string serviceName = service.ServiceName.ToLower().Trim();
20+
string url = service.Url.ToLower().Trim();
21+
string notes = service.Notes.ToLower().Trim();
22+
23+
return (!string.IsNullOrWhiteSpace(globalTextFilter)
24+
? serviceId == globalTextFilter
25+
|| serviceName.Contains(globalTextFilter)
26+
|| url.Contains(globalTextFilter)
27+
|| notes.Contains(globalTextFilter)
28+
|| service.Accounts.Any(x => x.MeetsFilterConditions(string.Empty, globalTextFilter, changedItemsOnly))
29+
: (string.IsNullOrWhiteSpace(serviceFilter)
30+
|| (!string.IsNullOrWhiteSpace(serviceFilter) && serviceName.Contains(serviceFilter)))
31+
&& (string.IsNullOrWhiteSpace(identifierFilter)
32+
|| service.Accounts.Any(x => x.MeetsFilterConditions(identifierFilter, globalTextFilter, changedItemsOnly))))
33+
&& (!changedItemsOnly || service.HasChanged());
34+
}
35+
36+
public static bool MeetsFilterConditions(this IAccount account, string identifierFilter, string globalTextFilter, bool changedItemsOnly)
37+
{
38+
identifierFilter = identifierFilter.ToLower().Trim();
39+
globalTextFilter = globalTextFilter.ToLower().Trim();
40+
41+
string accountId = account.ItemId.Replace(account.Service.ItemId, string.Empty).ToLower().Trim();
42+
string label = account.Label.ToLower().Trim();
43+
string notes = account.Notes.ToLower().Trim();
44+
string identifiers = string.Join("\n", account.Identifiers.Select(x => x.ToLower().Trim()));
45+
46+
return (!string.IsNullOrWhiteSpace(globalTextFilter)
47+
? accountId == globalTextFilter
48+
|| identifiers.Contains(globalTextFilter)
49+
|| label.ToLower().Contains(globalTextFilter)
50+
|| notes.ToLower().Contains(globalTextFilter)
51+
: string.IsNullOrWhiteSpace(identifierFilter)
52+
|| identifiers.Contains(identifierFilter)
53+
|| label.Contains(identifierFilter))
54+
&& (!changedItemsOnly || account.HasChanged());
55+
}
56+
}
57+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System.Text.RegularExpressions;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Input;
5+
6+
namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
7+
{
8+
public static class NumericTextBoxHelper
9+
{
10+
private static readonly Regex _regex = new("[^0-9]+"); //regex that matches disallowed text
11+
12+
private static bool _isTextAllowed(string text)
13+
{
14+
bool isValid = !_regex.IsMatch(text);
15+
16+
if (isValid)
17+
{
18+
isValid = int.TryParse(text, out _);
19+
}
20+
21+
return isValid;
22+
}
23+
24+
public static void TextChanged(object sender, TextChangedEventArgs e)
25+
{
26+
TextBox textBox = (TextBox)sender;
27+
28+
e.Handled = !_isTextAllowed(textBox.Text);
29+
30+
if (e.Handled)
31+
{
32+
textBox.Text = textBox.Text.Replace(" ", "");
33+
}
34+
}
35+
36+
public static void PreviewTextInput(object sender, TextCompositionEventArgs e)
37+
{
38+
e.Handled = !_isTextAllowed(e.Text);
39+
}
40+
41+
public static void Pasting(object sender, DataObjectPastingEventArgs e)
42+
{
43+
if (e.DataObject.GetDataPresent(typeof(string)))
44+
{
45+
string text = (string)e.DataObject.GetData(typeof(string));
46+
if (!_isTextAllowed(text))
47+
{
48+
e.CancelCommand();
49+
}
50+
}
51+
else
52+
{
53+
e.CancelCommand();
54+
}
55+
}
56+
}
57+
}

GUI/WPF/Helper/PropertyHelper.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.ComponentModel;
2+
using System.Runtime.CompilerServices;
3+
4+
namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
5+
{
6+
public static class PropertyHelper
7+
{
8+
public static bool SetProperty<T>(ref T field,
9+
T newValue,
10+
INotifyPropertyChanged sender,
11+
PropertyChangedEventHandler? PropertyChanged,
12+
[CallerMemberName] string? propertyName = null)
13+
{
14+
if (!Equals(field, newValue))
15+
{
16+
field = newValue;
17+
PropertyChanged?.Invoke(sender, new PropertyChangedEventArgs(propertyName));
18+
19+
return true;
20+
}
21+
22+
return false;
23+
}
24+
}
25+
}

GUI/WPF/Helper/WindowHelper.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using System.Windows;
2+
using System.Windows.Controls;
3+
using System.Windows.Input;
4+
5+
namespace Upsilon.Apps.Passkey.GUI.WPF.Helper
6+
{
7+
public static class WindowHelper
8+
{
9+
public static bool GetIsBusy(this Window window)
10+
{
11+
return window.Cursor == Cursors.Wait;
12+
}
13+
14+
public static void SetIsBusy(this Window window, bool isBusy)
15+
{
16+
window.Cursor = isBusy ? Cursors.Wait : Cursors.Arrow;
17+
}
18+
19+
public static bool GetIsBusy(this UserControl control)
20+
{
21+
return Window.GetWindow(control).GetIsBusy();
22+
}
23+
}
24+
}

0 commit comments

Comments
 (0)