diff --git a/SinglePass.Language/Properties/Resources.Designer.cs b/SinglePass.Language/Properties/Resources.Designer.cs
index 99845ba..b729006 100644
--- a/SinglePass.Language/Properties/Resources.Designer.cs
+++ b/SinglePass.Language/Properties/Resources.Designer.cs
@@ -690,6 +690,33 @@ public static string TextCopied {
}
}
+ ///
+ /// Looks up a localized string similar to Theme.
+ ///
+ public static string Theme {
+ get {
+ return ResourceManager.GetString("Theme", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to to paste login.
+ ///
+ public static string ToPasteLogin {
+ get {
+ return ResourceManager.GetString("ToPasteLogin", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to to paste password.
+ ///
+ public static string ToPastePassword {
+ get {
+ return ResourceManager.GetString("ToPastePassword", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Upload.
///
diff --git a/SinglePass.Language/Properties/Resources.resx b/SinglePass.Language/Properties/Resources.resx
index 153bdcd..457f190 100644
--- a/SinglePass.Language/Properties/Resources.resx
+++ b/SinglePass.Language/Properties/Resources.resx
@@ -325,4 +325,13 @@
Can't merge credentials
+
+ to paste login
+
+
+ to paste password
+
+
+ Theme
+
\ No newline at end of file
diff --git a/SinglePass.Language/Properties/Resources.ru.resx b/SinglePass.Language/Properties/Resources.ru.resx
index 8e19b14..59c2747 100644
--- a/SinglePass.Language/Properties/Resources.ru.resx
+++ b/SinglePass.Language/Properties/Resources.ru.resx
@@ -345,4 +345,13 @@
Нет изменений
+
+ для вставки логина
+
+
+ для вставки пароля
+
+
+ Тема
+
\ No newline at end of file
diff --git a/SinglePass.WPF/App.xaml.cs b/SinglePass.WPF/App.xaml.cs
index c9374ed..0dd09b9 100644
--- a/SinglePass.WPF/App.xaml.cs
+++ b/SinglePass.WPF/App.xaml.cs
@@ -16,9 +16,11 @@
using SinglePass.WPF.Settings;
using SinglePass.WPF.ViewModels;
using SinglePass.WPF.Views;
+using SinglePass.WPF.Views.Windows;
using System;
using System.Threading;
using System.Windows;
+using System.Windows.Interop;
namespace SinglePass.WPF
{
@@ -85,8 +87,9 @@ private static IServiceProvider ConfigureServices(IConfiguration configuration)
services.AddScoped();
services.AddScoped();
services.AddScoped();
- services.AddScoped();
+ services.AddScoped();
+ // Popup
services.AddTransient();
services.AddTransient();
@@ -132,6 +135,9 @@ private void Application_Startup(object sender, StartupEventArgs e)
var welcomeWindow = new WelcomeWindow();
welcomeWindow.Show();
+ var hiw = new HiddenInterprocessWindow();
+ hiw.InitWithoutShowing();
+
_logger = Services.GetService>();
_logger.LogInformation("Log session started!");
@@ -165,6 +171,7 @@ private void Application_Startup(object sender, StartupEventArgs e)
}
else
{
+ InterprocessHelper.ShowMainWindow();
Shutdown();
}
}
diff --git a/SinglePass.WPF/Authorization/Brokers/BaseAuthorizationBroker.cs b/SinglePass.WPF/Authorization/Brokers/BaseAuthorizationBroker.cs
index f5adc09..1b88637 100644
--- a/SinglePass.WPF/Authorization/Brokers/BaseAuthorizationBroker.cs
+++ b/SinglePass.WPF/Authorization/Brokers/BaseAuthorizationBroker.cs
@@ -1,10 +1,6 @@
using SinglePass.WPF.Authorization.Helpers;
-using SinglePass.WPF.Authorization.Responses;
using SinglePass.WPF.Authorization.TokenHolders;
using System;
-using System.Collections.Specialized;
-using System.Linq;
-using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
@@ -16,6 +12,7 @@ public abstract class BaseAuthorizationBroker : IAuthorizationBroker
{
private readonly IHttpClientFactory _httpClientFactory;
+ protected string RedirectUri { get; set; }
public ITokenHolder TokenHolder { get; }
public BaseAuthorizationBroker(IHttpClientFactory httpClientFactory, ITokenHolder tokenHolder)
@@ -26,24 +23,24 @@ public BaseAuthorizationBroker(IHttpClientFactory httpClientFactory, ITokenHolde
public async Task AuthorizeAsync(CancellationToken cancellationToken)
{
- var redirectUri = BuildRedirectUri();
- var authorizationUri = BuildAuthorizationUri(redirectUri);
- using var listener = OAuthHelper.StartListener(redirectUri);
+ BuildRedirectUri();
+ var authorizationUri = BuildAuthorizationUri();
+ using var listener = OAuthHelper.StartListener(RedirectUri);
OAuthHelper.OpenBrowser(authorizationUri);
- var response = await GetResponseFromListener(listener, cancellationToken);
+ var response = await OAuthHelper.GetResponseFromListener(listener, BuildClosePageResponse(), cancellationToken);
if (string.IsNullOrWhiteSpace(response?.Code))
{
throw new Exception("Code was empty!");
}
- var tokenResponse = await RetrieveToken(response.Code, redirectUri, cancellationToken);
+ var tokenResponse = await RetrieveToken(response.Code, cancellationToken);
await TokenHolder.SetAndSaveToken(tokenResponse, cancellationToken);
}
public async Task RefreshAccessToken(CancellationToken cancellationToken)
{
var client = _httpClientFactory.CreateClient();
- var refreshTokenEndpointUri = BuildRefreshAccessTokenEndpointUri();
- var postData = BuildRequestForRefreshToken();
+ var refreshTokenEndpointUri = BuildRefreshTokenEndpointUri();
+ var postData = BuildRefreshTokenRequest();
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(refreshTokenEndpointUri))
{
Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded")
@@ -58,61 +55,20 @@ public async Task RevokeToken(CancellationToken cancellationToken)
{
await TokenHolder.RemoveToken();
var client = _httpClientFactory.CreateClient();
- var revokeTokenEndpointUri = BuildRevokeTokenEndpointUri();
+ var revokeTokenEndpointUri = BuildTokenRevokeEndpointUri();
var stringContent = new StringContent(string.Empty, Encoding.UTF8, "application/x-www-form-urlencoded");
var response = await client.PostAsync(revokeTokenEndpointUri, stringContent, cancellationToken);
using var content = response.Content;
var json = await content.ReadAsStringAsync(cancellationToken);
}
- private async Task GetResponseFromListener(HttpListener listener, CancellationToken cancellationToken)
- {
- HttpListenerContext context;
- // Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token,
- // the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call.
- using (cancellationToken.Register(listener.Stop))
- {
- // Wait to get the authorization code response.
- try
- {
- context = await listener.GetContextAsync().ConfigureAwait(false);
- }
- catch (Exception) when (cancellationToken.IsCancellationRequested)
- {
- cancellationToken.ThrowIfCancellationRequested();
- // Next line will never be reached because cancellation will always have been requested in this catch block.
- // But it's required to satisfy compiler.
- throw new InvalidOperationException();
- }
- catch
- {
- throw;
- }
- }
- NameValueCollection coll = context.Request.QueryString;
-
- // Write a "close" response.
- var bytes = Encoding.UTF8.GetBytes(BuildClosePageResponse());
- context.Response.ContentLength64 = bytes.Length;
- context.Response.SendChunked = false;
- context.Response.KeepAlive = false;
- var output = context.Response.OutputStream;
- await output.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
- await output.FlushAsync(cancellationToken).ConfigureAwait(false);
- output.Close();
- context.Response.Close();
-
- // Create a new response URL with a dictionary that contains all the response query parameters.
- return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k]));
- }
-
- private async Task RetrieveToken(string code, string redirectUri, CancellationToken cancellationToken)
+ private async Task RetrieveToken(string code, CancellationToken cancellationToken)
{
string result;
var client = _httpClientFactory.CreateClient();
var tokenEndpointUri = BuildTokenEndpointUri();
- var postData = BuildRequestForToken(code, redirectUri);
+ var postData = BuildTokenRequest(code);
var request = new HttpRequestMessage(HttpMethod.Post, new Uri(tokenEndpointUri))
{
Content = new StringContent(postData, Encoding.UTF8, "application/x-www-form-urlencoded")
@@ -130,12 +86,12 @@ protected virtual string BuildClosePageResponse()
return "Authorization success, you can return to application";
}
- protected abstract string BuildRedirectUri();
- protected abstract string BuildAuthorizationUri(string redirectUri);
+ protected abstract void BuildRedirectUri();
+ protected abstract string BuildAuthorizationUri();
protected abstract string BuildTokenEndpointUri();
- protected abstract string BuildRequestForToken(string code, string redirectUri);
- protected abstract string BuildRefreshAccessTokenEndpointUri();
- protected abstract string BuildRequestForRefreshToken();
- protected abstract string BuildRevokeTokenEndpointUri();
+ protected abstract string BuildTokenRequest(string code);
+ protected abstract string BuildRefreshTokenEndpointUri();
+ protected abstract string BuildRefreshTokenRequest();
+ protected abstract string BuildTokenRevokeEndpointUri();
}
}
diff --git a/SinglePass.WPF/Authorization/Brokers/GoogleAuthorizationBroker.cs b/SinglePass.WPF/Authorization/Brokers/GoogleAuthorizationBroker.cs
index 8b9e9d2..ce38719 100644
--- a/SinglePass.WPF/Authorization/Brokers/GoogleAuthorizationBroker.cs
+++ b/SinglePass.WPF/Authorization/Brokers/GoogleAuthorizationBroker.cs
@@ -25,7 +25,7 @@ public GoogleAuthorizationBroker(
_config = options.Value;
}
- protected override string BuildAuthorizationUri(string redirectUri)
+ protected override string BuildAuthorizationUri()
{
return "https://accounts.google.com/o/oauth2/v2/auth?" +
$"scope={HttpUtility.UrlEncode(_config.Scopes)}&" +
@@ -33,22 +33,22 @@ protected override string BuildAuthorizationUri(string redirectUri)
"include_granted_scopes=true&" +
"response_type=code&" +
"state=state_parameter_passthrough_value&" +
- $"redirect_uri={HttpUtility.UrlEncode(redirectUri)}&" +
+ $"redirect_uri={HttpUtility.UrlEncode(RedirectUri)}&" +
$"client_id={_config.ClientId}";
}
- protected override string BuildRedirectUri()
+ protected override void BuildRedirectUri()
{
var unusedPort = OAuthHelper.GetRandomUnusedPort();
- return $"http://localhost:{unusedPort}/";
+ RedirectUri = $"http://localhost:{unusedPort}/";
}
- protected override string BuildRefreshAccessTokenEndpointUri()
+ protected override string BuildRefreshTokenEndpointUri()
{
return "https://oauth2.googleapis.com/token";
}
- protected override string BuildRequestForRefreshToken()
+ protected override string BuildRefreshTokenRequest()
{
return $"client_id={_config.ClientId}&" +
$"client_secret={_config.ClientSecret}&" +
@@ -56,12 +56,12 @@ protected override string BuildRequestForRefreshToken()
$"grant_type=refresh_token";
}
- protected override string BuildRequestForToken(string code, string redirectUri)
+ protected override string BuildTokenRequest(string code)
{
return $"code={code}&" +
$"client_id={_config.ClientId}&" +
$"client_secret={_config.ClientSecret}&" +
- $"redirect_uri={HttpUtility.UrlEncode(redirectUri)}&" +
+ $"redirect_uri={HttpUtility.UrlEncode(RedirectUri)}&" +
$"grant_type=authorization_code";
}
@@ -70,7 +70,7 @@ protected override string BuildTokenEndpointUri()
return "https://oauth2.googleapis.com/token";
}
- protected override string BuildRevokeTokenEndpointUri()
+ protected override string BuildTokenRevokeEndpointUri()
{
return $"https://oauth2.googleapis.com/revoke?token={TokenHolder.Token.RefreshToken}";
}
diff --git a/SinglePass.WPF/Authorization/Brokers/IAuthorizationBroker.cs b/SinglePass.WPF/Authorization/Brokers/IAuthorizationBroker.cs
index c003366..099672e 100644
--- a/SinglePass.WPF/Authorization/Brokers/IAuthorizationBroker.cs
+++ b/SinglePass.WPF/Authorization/Brokers/IAuthorizationBroker.cs
@@ -1,6 +1,6 @@
-using System.Threading;
+using SinglePass.WPF.Authorization.TokenHolders;
+using System.Threading;
using System.Threading.Tasks;
-using SinglePass.WPF.Authorization.TokenHolders;
namespace SinglePass.WPF.Authorization.Brokers
{
diff --git a/SinglePass.WPF/Authorization/Helpers/OAuthHelper.cs b/SinglePass.WPF/Authorization/Helpers/OAuthHelper.cs
index 9820647..1b91771 100644
--- a/SinglePass.WPF/Authorization/Helpers/OAuthHelper.cs
+++ b/SinglePass.WPF/Authorization/Helpers/OAuthHelper.cs
@@ -1,6 +1,13 @@
-using System.Diagnostics;
+using SinglePass.WPF.Authorization.Responses;
+using System;
+using System.Collections.Specialized;
+using System.Diagnostics;
+using System.Linq;
using System.Net;
using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace SinglePass.WPF.Authorization.Helpers
{
@@ -34,5 +41,49 @@ public static void OpenBrowser(string uri)
uri = System.Text.RegularExpressions.Regex.Replace(uri, @"(\\+)$", @"$1$1");
Process.Start(new ProcessStartInfo("cmd", $"/c start \"\" \"{uri}\"") { CreateNoWindow = true });
}
+
+ public static async Task GetResponseFromListener(
+ HttpListener listener,
+ string responseHtmlText,
+ CancellationToken cancellationToken = default)
+ {
+ HttpListenerContext context;
+ // Set up cancellation. HttpListener.GetContextAsync() doesn't accept a cancellation token,
+ // the HttpListener needs to be stopped which immediately aborts the GetContextAsync() call.
+ using (cancellationToken.Register(listener.Stop))
+ {
+ // Wait to get the authorization code response.
+ try
+ {
+ context = await listener.GetContextAsync().ConfigureAwait(false);
+ }
+ catch (Exception) when (cancellationToken.IsCancellationRequested)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ // Next line will never be reached because cancellation will always have been requested in this catch block.
+ // But it's required to satisfy compiler.
+ throw new InvalidOperationException();
+ }
+ catch
+ {
+ throw;
+ }
+ }
+ NameValueCollection coll = context.Request.QueryString;
+
+ // Write a "close" response.
+ var bytes = Encoding.UTF8.GetBytes(responseHtmlText);
+ context.Response.ContentLength64 = bytes.Length;
+ context.Response.SendChunked = false;
+ context.Response.KeepAlive = false;
+ var output = context.Response.OutputStream;
+ await output.WriteAsync(bytes, cancellationToken).ConfigureAwait(false);
+ await output.FlushAsync(cancellationToken).ConfigureAwait(false);
+ output.Close();
+ context.Response.Close();
+
+ // Create a new response URL with a dictionary that contains all the response query parameters.
+ return new AuthorizationCodeResponseUrl(coll.AllKeys.ToDictionary(k => k, k => coll[k]));
+ }
}
}
diff --git a/SinglePass.WPF/Authorization/TokenHolders/ITokenHolder.cs b/SinglePass.WPF/Authorization/TokenHolders/ITokenHolder.cs
index 52cf023..f56a00b 100644
--- a/SinglePass.WPF/Authorization/TokenHolders/ITokenHolder.cs
+++ b/SinglePass.WPF/Authorization/TokenHolders/ITokenHolder.cs
@@ -1,6 +1,6 @@
-using System.Threading;
+using SinglePass.WPF.Authorization.Responses;
+using System.Threading;
using System.Threading.Tasks;
-using SinglePass.WPF.Authorization.Responses;
namespace SinglePass.WPF.Authorization.TokenHolders
{
diff --git a/SinglePass.WPF/Converters/ModeToIsReadonlyConverter.cs b/SinglePass.WPF/Converters/ModeToIsReadonlyConverter.cs
index 3b02cd8..559c0b9 100644
--- a/SinglePass.WPF/Converters/ModeToIsReadonlyConverter.cs
+++ b/SinglePass.WPF/Converters/ModeToIsReadonlyConverter.cs
@@ -5,21 +5,20 @@
namespace SinglePass.WPF.Converters
{
- [ValueConversion(typeof(CredentialsDialogMode), typeof(bool))]
+ [ValueConversion(typeof(CredentialDetailsMode), typeof(bool))]
internal class ModeToIsReadonlyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value is CredentialsDialogMode mode)
+ if (value is CredentialDetailsMode mode)
{
switch (mode)
{
- case CredentialsDialogMode.New:
+ case CredentialDetailsMode.New:
+ case CredentialDetailsMode.Edit:
return false;
- case CredentialsDialogMode.View:
+ case CredentialDetailsMode.View:
return true;
- case CredentialsDialogMode.Edit:
- return false;
default:
break;
}
diff --git a/SinglePass.WPF/Converters/ModeToVisibilityConverter.cs b/SinglePass.WPF/Converters/ModeToVisibilityConverter.cs
index 37755e2..012bfa4 100644
--- a/SinglePass.WPF/Converters/ModeToVisibilityConverter.cs
+++ b/SinglePass.WPF/Converters/ModeToVisibilityConverter.cs
@@ -6,23 +6,22 @@
namespace SinglePass.WPF.Converters
{
- [ValueConversion(typeof(CredentialsDialogMode), typeof(Visibility))]
+ [ValueConversion(typeof(CredentialDetailsMode), typeof(Visibility))]
internal class ModeToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value is CredentialsDialogMode mode)
+ if (value is CredentialDetailsMode mode)
{
var inverse = parameter is string sparam && sparam == "inverse";
switch (mode)
{
- case CredentialsDialogMode.New:
+ case CredentialDetailsMode.New:
+ case CredentialDetailsMode.Edit:
return inverse ? Visibility.Collapsed : Visibility.Visible;
- case CredentialsDialogMode.View:
+ case CredentialDetailsMode.View:
return inverse ? Visibility.Visible : Visibility.Collapsed;
- case CredentialsDialogMode.Edit:
- return inverse ? Visibility.Collapsed : Visibility.Visible;
default:
break;
}
diff --git a/SinglePass.WPF/Enums/CredentialDialogMode.cs b/SinglePass.WPF/Enums/CredentialDetailsMode.cs
similarity index 70%
rename from SinglePass.WPF/Enums/CredentialDialogMode.cs
rename to SinglePass.WPF/Enums/CredentialDetailsMode.cs
index 985e28a..40ec851 100644
--- a/SinglePass.WPF/Enums/CredentialDialogMode.cs
+++ b/SinglePass.WPF/Enums/CredentialDetailsMode.cs
@@ -1,6 +1,6 @@
namespace SinglePass.WPF.Enums
{
- public enum CredentialsDialogMode
+ public enum CredentialDetailsMode
{
Edit,
New,
diff --git a/SinglePass.WPF/Enums/CustomWindowsMessages.cs b/SinglePass.WPF/Enums/CustomWindowsMessages.cs
new file mode 100644
index 0000000..78ed835
--- /dev/null
+++ b/SinglePass.WPF/Enums/CustomWindowsMessages.cs
@@ -0,0 +1,12 @@
+using static SinglePass.WPF.Helpers.WinApiProvider;
+
+namespace SinglePass.WPF.Enums
+{
+ internal enum CustomWindowsMessages : uint
+ {
+ ///
+ /// Shows main window.
+ ///
+ WM_SHOW_MAIN_WINDOW = SystemWindowsMessages.WM_USER + 1,
+ }
+}
diff --git a/SinglePass.WPF/Helpers/Constants.cs b/SinglePass.WPF/Helpers/Constants.cs
index 8837f9f..6a3b804 100644
--- a/SinglePass.WPF/Helpers/Constants.cs
+++ b/SinglePass.WPF/Helpers/Constants.cs
@@ -5,6 +5,8 @@ namespace SinglePass.WPF.Helpers
{
public static class Constants
{
+ public const string InterprocessWindowName = "HiddenInterprocessWindow";
+ public const string ProcessName = "SinglePass.WPF";
public const string AppName = "SinglePass";
public const string PasswordsFileName = "singlePass.dat";
public const string CommonSettingsFileName = "commonSettings.json";
diff --git a/SinglePass.WPF/Helpers/InterprocessHelper.cs b/SinglePass.WPF/Helpers/InterprocessHelper.cs
new file mode 100644
index 0000000..08e71f0
--- /dev/null
+++ b/SinglePass.WPF/Helpers/InterprocessHelper.cs
@@ -0,0 +1,37 @@
+using SinglePass.WPF.Enums;
+using System;
+using System.Diagnostics;
+using System.Linq;
+
+namespace SinglePass.WPF.Helpers
+{
+ internal static class InterprocessHelper
+ {
+ public static void ShowMainWindow()
+ {
+ var currentProcessId = System.Environment.ProcessId;
+ var processes = Process
+ .GetProcessesByName(Constants.ProcessName)
+ .Where(p => !p.Id.Equals(currentProcessId))
+ .ToList();
+ if (processes.Count == 0)
+ return;
+
+ var spProcess = processes[0];
+ var processWindowHandles = WinApiProvider.EnumerateProcessWindowHandles(spProcess.Id);
+ foreach (var processWindowHandle in processWindowHandles)
+ {
+ var windowCaption = WinApiProvider.GetWindowText(processWindowHandle);
+ if (windowCaption.Equals(Constants.InterprocessWindowName))
+ {
+ WinApiProvider.PostMessage(
+ processWindowHandle,
+ (uint)CustomWindowsMessages.WM_SHOW_MAIN_WINDOW,
+ IntPtr.Zero,
+ IntPtr.Zero);
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/SinglePass.WPF/Helpers/WinApiProvider.cs b/SinglePass.WPF/Helpers/WinApiProvider.cs
index 5d97800..1466054 100644
--- a/SinglePass.WPF/Helpers/WinApiProvider.cs
+++ b/SinglePass.WPF/Helpers/WinApiProvider.cs
@@ -1,5 +1,8 @@
using System;
+using System.Collections.Generic;
+using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Text;
namespace SinglePass.WPF.Helpers
{
@@ -80,6 +83,77 @@ public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
+ ///
+ /// Sends the specified message to a window or windows.
+ ///
+ /// A handle to the window whose window procedure will receive the message.
+ /// The message to be sent.
+ /// Additional message-specific information.
+ /// second message parameter
+ ///
+ [DllImport("user32", SetLastError = true)]
+ public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+
+ ///
+ /// Places (posts) a message in the message queue associated with the thread
+ /// that created the specified window and returns without waiting for the
+ /// thread to process the message.
+ ///
+ /// A handle to the window whose window procedure will receive the message.
+ /// The message to be sent.
+ /// Additional message-specific information.
+ /// second message parameter
+ ///
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);
+
+ public delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
+
+ public static IEnumerable EnumerateProcessWindowHandles(int processId)
+ {
+ var handles = new List();
+
+ foreach (ProcessThread thread in Process.GetProcessById(processId).Threads)
+ {
+ EnumThreadWindows(
+ thread.Id,
+ (hWnd, lParam) => { handles.Add(hWnd); return true; },
+ IntPtr.Zero);
+ }
+
+ return handles;
+ }
+
+ ///
+ /// Retrieves the WindowText of the window given by the handle.
+ ///
+ /// The windows handle
+ /// A stringbuilder object wich receives the window text
+ /// The max length of the text to retrieve, usually 260
+ /// Returns the length of chars received.
+ [DllImport("user32", CharSet = CharSet.Unicode)]
+ public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
+
+ ///
+ /// Retrieves the WindowText of the window given by the handle.
+ ///
+ /// The windows handle
+ /// Window text.
+ public static string GetWindowText(IntPtr hWnd)
+ {
+ var windowName = new StringBuilder(260);
+ _ = GetWindowText(hWnd, windowName, 260);
+ return windowName.ToString();
+ }
+
public const uint SWP_NOZORDER = 0x0004;
public const int CHILDID_SELF = 0;
@@ -150,5 +224,104 @@ public static implicit operator POINT(System.Drawing.Point p)
return new POINT(p.X, p.Y);
}
}
+
+ ///
+ /// System windows message codes
+ ///
+ public enum SystemWindowsMessages : uint
+ {
+ ///
+ /// This message is sent when a window is being activated or deactivated. This message is sent first to the window procedure of the top-level window being deactivated; it is then sent to the window procedure of the top-level window being activated.
+ ///
+ WM_ACTIVATE = 0x0006,
+
+ ///
+ /// Sent to a window after it has gained the keyboard focus.
+ ///
+ WM_SETFOCUS = 0x0007,
+
+ ///
+ /// An application sends the WM_COPYDATA message to pass data to another application.
+ ///
+ WM_COPYDATA = 0x004A,
+
+ ///
+ /// Used to define private messages for use by private window classes, usually of the form WM_USER+x, where x is an integer value.
+ ///
+ WM_USER = 0x400,
+ }
+
+ // http://www.pinvoke.net/default.aspx/Enums/ShowWindowCommand.html
+ public enum ShowWindowCommands
+ {
+ ///
+ /// Hides the window and activates another window.
+ ///
+ Hide = 0,
+ ///
+ /// Activates and displays a window. If the window is minimized or
+ /// maximized, the system restores it to its original size and position.
+ /// An application should specify this flag when displaying the window
+ /// for the first time.
+ ///
+ Normal = 1,
+ ///
+ /// Activates the window and displays it as a minimized window.
+ ///
+ ShowMinimized = 2,
+ ///
+ /// Maximizes the specified window.
+ ///
+ Maximize = 3, // is this the right value?
+ ///
+ /// Activates the window and displays it as a maximized window.
+ ///
+ ShowMaximized = 3,
+ ///
+ /// Displays a window in its most recent size and position. This value
+ /// is similar to , except
+ /// the window is not activated.
+ ///
+ ShowNoActivate = 4,
+ ///
+ /// Activates the window and displays it in its current size and position.
+ ///
+ Show = 5,
+ ///
+ /// Minimizes the specified window and activates the next top-level
+ /// window in the Z order.
+ ///
+ Minimize = 6,
+ ///
+ /// Displays the window as a minimized window. This value is similar to
+ /// , except the
+ /// window is not activated.
+ ///
+ ShowMinNoActive = 7,
+ ///
+ /// Displays the window in its current size and position. This value is
+ /// similar to , except the
+ /// window is not activated.
+ ///
+ ShowNA = 8,
+ ///
+ /// Activates and displays the window. If the window is minimized or
+ /// maximized, the system restores it to its original size and position.
+ /// An application should specify this flag when restoring a minimized window.
+ ///
+ Restore = 9,
+ ///
+ /// Sets the show state based on the SW_* value specified in the
+ /// STARTUPINFO structure passed to the CreateProcess function by the
+ /// program that started the application.
+ ///
+ ShowDefault = 10,
+ ///
+ /// Windows 2000/XP: Minimizes a window, even if the thread
+ /// that owns the window is not responding. This flag should only be
+ /// used when minimizing windows from a different thread.
+ ///
+ ForceMinimize = 11
+ }
}
}
diff --git a/SinglePass.WPF/Hotkeys/HotkeyDelegates.cs b/SinglePass.WPF/Hotkeys/HotkeyDelegates.cs
index 7a4acd3..ae6a1e3 100644
--- a/SinglePass.WPF/Hotkeys/HotkeyDelegates.cs
+++ b/SinglePass.WPF/Hotkeys/HotkeyDelegates.cs
@@ -1,5 +1,5 @@
using SinglePass.WPF.Helpers;
-using SinglePass.WPF.Views;
+using SinglePass.WPF.Views.Windows;
using System;
using System.Linq;
using System.Runtime.InteropServices;
diff --git a/SinglePass.WPF/Services/SyncService.cs b/SinglePass.WPF/Services/SyncService.cs
index 54c82b7..c67098a 100644
--- a/SinglePass.WPF/Services/SyncService.cs
+++ b/SinglePass.WPF/Services/SyncService.cs
@@ -128,7 +128,7 @@ public async Task Upload(CloudType cloudType)
SyncStateChanged?.Invoke(SinglePass.Language.Properties.Resources.Uploading);
// Additional lock to ensure file not used by other thread
- using var locker = AsyncDuplicateLock.Lock(Constants.PasswordsFilePath);
+ using var locker = await AsyncDuplicateLock.LockAsync(Constants.PasswordsFilePath);
using var fileStream = File.Open(Constants.PasswordsFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
// Ensure begining
fileStream.Seek(0, SeekOrigin.Begin);
diff --git a/SinglePass.WPF/Settings/AppSettingsService.cs b/SinglePass.WPF/Settings/AppSettingsService.cs
index d8379b5..e5b2723 100644
--- a/SinglePass.WPF/Settings/AppSettingsService.cs
+++ b/SinglePass.WPF/Settings/AppSettingsService.cs
@@ -70,16 +70,13 @@ public AppSettingsService(ILogger logger)
}
}
- public Task Save()
+ public async Task Save()
{
- return Task.Run(() =>
- {
- // Use local lock instead of interprocess lock - only one instance of app will work with this file
- using var locker = AsyncDuplicateLock.Lock(Constants.CommonSettingsFilePath);
- using var fileStream = new FileStream(Constants.CommonSettingsFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
- JsonSerializer.Serialize(fileStream, Settings);
- _logger.LogInformation("Settings saved to file");
- });
+ // Use local lock instead of interprocess lock - only one instance of app will work with this file
+ using var locker = await AsyncDuplicateLock.LockAsync(Constants.CommonSettingsFilePath).ConfigureAwait(false);
+ using var fileStream = new FileStream(Constants.CommonSettingsFilePath, FileMode.Create, FileAccess.Write, FileShare.Read);
+ await JsonSerializer.SerializeAsync(fileStream, Settings).ConfigureAwait(false);
+ _logger.LogInformation("Settings saved to file");
}
}
}
diff --git a/SinglePass.WPF/SinglePass.WPF.csproj b/SinglePass.WPF/SinglePass.WPF.csproj
index 6969315..ebc892f 100644
--- a/SinglePass.WPF/SinglePass.WPF.csproj
+++ b/SinglePass.WPF/SinglePass.WPF.csproj
@@ -26,7 +26,7 @@
-
+
@@ -54,15 +54,4 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/SinglePass.WPF/Themes/DialogHostTemplates.xaml b/SinglePass.WPF/Themes/DialogHostTemplates.xaml
index 8eeb06c..3fedd89 100644
--- a/SinglePass.WPF/Themes/DialogHostTemplates.xaml
+++ b/SinglePass.WPF/Themes/DialogHostTemplates.xaml
@@ -5,7 +5,7 @@
-
+ IsHitTestVisible="False">
-
+
-
\ No newline at end of file
+
diff --git a/SinglePass.WPF/ViewModels/CloudSyncViewModel.cs b/SinglePass.WPF/ViewModels/CloudSyncViewModel.cs
index 2767398..ac6c0b8 100644
--- a/SinglePass.WPF/ViewModels/CloudSyncViewModel.cs
+++ b/SinglePass.WPF/ViewModels/CloudSyncViewModel.cs
@@ -7,9 +7,8 @@
using SinglePass.WPF.Helpers;
using SinglePass.WPF.Services;
using SinglePass.WPF.Settings;
-using SinglePass.WPF.Views;
-using SinglePass.WPF.Views.InputBox;
-using SinglePass.WPF.Views.MessageBox;
+using SinglePass.WPF.ViewModels.Dialogs;
+using SinglePass.WPF.Views.Helpers;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -83,52 +82,55 @@ public CloudSyncViewModel(
[RelayCommand]
private async Task Login(CloudType cloudType)
{
- var windowDialogName = DialogIdentifiers.MainWindowName;
- var authorizing = false;
- switch (cloudType)
+
+ try
{
- case CloudType.GoogleDrive:
- authorizing = !GoogleDriveEnabled;
- break;
+ // Authorize
+ var cloudService = _cloudServiceProvider.GetCloudService(cloudType);
+ _ = ProcessingDialog.Show(
+ SinglePass.Language.Properties.Resources.Authorizing,
+ SinglePass.Language.Properties.Resources.PleaseContinueAuthorizationOrCancelIt,
+ DialogIdentifiers.MainWindowName,
+ out CancellationToken cancellationToken);
+
+ await cloudService.AuthorizationBroker.AuthorizeAsync(cancellationToken);
+ _logger.LogInformation($"Authorization process to {cloudType} has been complete.");
+ GoogleDriveEnabled = true;
+ _ = FetchUserInfoFromCloud(cloudType, CancellationToken.None); // Don't await set user info for now
+ await _appSettingsService.Save();
+ }
+ catch (OperationCanceledException)
+ {
+ _logger.LogWarning($"Authorization process to {cloudType} has been cancelled.");
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, string.Empty);
+ }
+ finally
+ {
+ if (DialogHost.IsDialogOpen(DialogIdentifiers.MainWindowName))
+ DialogHost.Close(DialogIdentifiers.MainWindowName);
}
- var cloudService = _cloudServiceProvider.GetCloudService(cloudType);
+ }
+ [RelayCommand]
+ private async Task Logout(CloudType cloudType)
+ {
try
{
- if (authorizing)
- {
- // Authorize
- var processingControl = new ProcessingControl(
- SinglePass.Language.Properties.Resources.Authorizing,
- SinglePass.Language.Properties.Resources.PleaseContinueAuthorizationOrCancelIt,
- windowDialogName);
- var token = processingControl.ViewModel.CancellationToken;
- _ = DialogHost.Show(processingControl, windowDialogName); // Don't await dialog host
-
- await cloudService.AuthorizationBroker.AuthorizeAsync(token);
- _logger.LogInformation($"Authorization process to {cloudType} has been complete.");
- GoogleDriveEnabled = true;
- await _appSettingsService.Save();
-
- _ = FetchUserInfoFromCloud(cloudType, CancellationToken.None); // Don't await set user info for now
- }
- else
- {
- // Revoke
- var processingControl = new ProcessingControl(
- SinglePass.Language.Properties.Resources.SigningOut,
- SinglePass.Language.Properties.Resources.PleaseWait,
- windowDialogName);
- var token = processingControl.ViewModel.CancellationToken;
- _ = DialogHost.Show(processingControl, windowDialogName); // Don't await dialog host
-
- await cloudService.AuthorizationBroker.RevokeToken(token);
-
- GoogleDriveEnabled = false;
- await _appSettingsService.Save();
-
- ClearUserInfo(cloudType);
- }
+ // Revoke
+ var cloudService = _cloudServiceProvider.GetCloudService(cloudType);
+ _ = ProcessingDialog.Show(
+ SinglePass.Language.Properties.Resources.SigningOut,
+ SinglePass.Language.Properties.Resources.PleaseWait,
+ DialogIdentifiers.MainWindowName,
+ out CancellationToken cancellationToken);
+
+ await cloudService.AuthorizationBroker.RevokeToken(cancellationToken);
+ GoogleDriveEnabled = false;
+ ClearUserInfo(cloudType);
+ await _appSettingsService.Save();
}
catch (OperationCanceledException)
{
@@ -140,8 +142,8 @@ private async Task Login(CloudType cloudType)
}
finally
{
- if (DialogHost.IsDialogOpen(windowDialogName))
- DialogHost.Close(windowDialogName);
+ if (DialogHost.IsDialogOpen(DialogIdentifiers.MainWindowName))
+ DialogHost.Close(DialogIdentifiers.MainWindowName);
}
}
diff --git a/SinglePass.WPF/ViewModels/CredentialsDialogViewModel.cs b/SinglePass.WPF/ViewModels/CredentialsDetailsViewModel.cs
similarity index 83%
rename from SinglePass.WPF/ViewModels/CredentialsDialogViewModel.cs
rename to SinglePass.WPF/ViewModels/CredentialsDetailsViewModel.cs
index ed5f058..4b084cc 100644
--- a/SinglePass.WPF/ViewModels/CredentialsDialogViewModel.cs
+++ b/SinglePass.WPF/ViewModels/CredentialsDetailsViewModel.cs
@@ -10,28 +10,28 @@
namespace SinglePass.WPF.ViewModels
{
- public class CredentialsDialogViewModel : ObservableRecipient
+ public class CredentialsDetailsViewModel : ObservableRecipient
{
#region Design time instance
- private static readonly Lazy _lazy = new(GetDesignTimeVM);
- public static CredentialsDialogViewModel DesignTimeInstance => _lazy.Value;
- private static CredentialsDialogViewModel GetDesignTimeVM()
+ private static readonly Lazy _lazy = new(GetDesignTimeVM);
+ public static CredentialsDetailsViewModel DesignTimeInstance => _lazy.Value;
+ private static CredentialsDetailsViewModel GetDesignTimeVM()
{
var additionalFields = new List() { new PassField() { Name = "Design additional field", Value = "Test value" } };
var model = Credential.CreateNew();
model.AdditionalFields = additionalFields;
var credVm = new CredentialViewModel(model, null);
- var vm = new CredentialsDialogViewModel(null);
+ var vm = new CredentialsDetailsViewModel(null);
vm._credentialViewModel = credVm;
- vm.Mode = CredentialsDialogMode.View;
+ vm.Mode = CredentialDetailsMode.View;
return vm;
}
#endregion
- private readonly ILogger _logger;
+ private readonly ILogger _logger;
- public event Action Accept;
+ public event Action Accept;
public event Action Delete;
public event Action Cancel;
public event Action EnqueueSnackbarMessage;
@@ -43,7 +43,7 @@ private static CredentialsDialogViewModel GetDesignTimeVM()
private RelayCommand _openInBrowserCommand;
private RelayCommand _copyToClipboardCommand;
private CredentialViewModel _credentialViewModel;
- private CredentialsDialogMode _mode = CredentialsDialogMode.View;
+ private CredentialDetailsMode _mode = CredentialDetailsMode.View;
private bool _isPasswordVisible;
private bool _isNameTextBoxFocused;
@@ -66,11 +66,11 @@ public string CaptionText
{
switch (_mode)
{
- case CredentialsDialogMode.Edit:
+ case CredentialDetailsMode.Edit:
return SinglePass.Language.Properties.Resources.Edit;
- case CredentialsDialogMode.New:
+ case CredentialDetailsMode.New:
return SinglePass.Language.Properties.Resources.New;
- case CredentialsDialogMode.View:
+ case CredentialDetailsMode.View:
return SinglePass.Language.Properties.Resources.Details;
default:
break;
@@ -80,7 +80,7 @@ public string CaptionText
}
}
- public CredentialsDialogMode Mode
+ public CredentialDetailsMode Mode
{
get => _mode;
set
@@ -102,7 +102,7 @@ public bool IsNameTextBoxFocused
set => SetProperty(ref _isNameTextBoxFocused, value);
}
- public CredentialsDialogViewModel(ILogger logger)
+ public CredentialsDetailsViewModel(ILogger logger)
{
_logger = logger;
}
@@ -127,7 +127,7 @@ private void EditExecute()
return;
CredentialViewModel = CredentialViewModel.Clone();
- Mode = CredentialsDialogMode.Edit;
+ Mode = CredentialDetailsMode.Edit;
IsPasswordVisible = true;
SetFocus();
}
diff --git a/SinglePass.WPF/Views/InputBox/MaterialInputBoxViewModel.cs b/SinglePass.WPF/ViewModels/Dialogs/MaterialInputBoxViewModel.cs
similarity index 98%
rename from SinglePass.WPF/Views/InputBox/MaterialInputBoxViewModel.cs
rename to SinglePass.WPF/ViewModels/Dialogs/MaterialInputBoxViewModel.cs
index 51df962..75f2ea4 100644
--- a/SinglePass.WPF/Views/InputBox/MaterialInputBoxViewModel.cs
+++ b/SinglePass.WPF/ViewModels/Dialogs/MaterialInputBoxViewModel.cs
@@ -3,7 +3,7 @@
using MaterialDesignThemes.Wpf;
using System;
-namespace SinglePass.WPF.Views.InputBox
+namespace SinglePass.WPF.ViewModels.Dialogs
{
internal class MaterialInputBoxViewModel : ObservableObject
{
diff --git a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxViewModel.cs b/SinglePass.WPF/ViewModels/Dialogs/MaterialMessageBoxViewModel.cs
similarity index 83%
rename from SinglePass.WPF/Views/MessageBox/MaterialMessageBoxViewModel.cs
rename to SinglePass.WPF/ViewModels/Dialogs/MaterialMessageBoxViewModel.cs
index 202411a..b43d1c4 100644
--- a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxViewModel.cs
+++ b/SinglePass.WPF/ViewModels/Dialogs/MaterialMessageBoxViewModel.cs
@@ -4,8 +4,84 @@
using System;
using System.Windows;
-namespace SinglePass.WPF.Views.MessageBox
+namespace SinglePass.WPF.ViewModels.Dialogs
{
+ public enum MaterialMessageBoxButtons
+ {
+ ///
+ /// The message box contains an OK button.
+ ///
+ OK,
+
+ ///
+ /// The message box contains OK and Cancel buttons.
+ ///
+ OKCancel,
+
+ ///
+ /// The message box contains Yes and No buttons.
+ ///
+ YesNo,
+
+ ///
+ /// The message box contains Yes, No, and Cancel buttons.
+ ///
+ YesNoCancel,
+
+ ///
+ /// The message box contains Abort, Retry, and Ignore buttons.
+ ///
+ AbortRetryIgnore,
+
+ ///
+ /// The message box contains Retry and Cancel buttons.
+ ///
+ RetryCancel
+ }
+
+ public enum MaterialDialogResult
+ {
+ ///
+ /// Nothing is returned from the dialog box. This means that the modal dialog continues running.
+ ///
+ None = 0,
+
+ ///
+ /// The dialog box return value is OK (usually sent from a button labeled OK).
+ ///
+ OK = 1,
+
+ ///
+ /// The dialog box return value is Cancel (usually sent from a button labeled Cancel).
+ ///
+ Cancel = 2,
+
+ ///
+ /// The dialog box return value is Abort (usually sent from a button labeled Abort).
+ ///
+ Abort = 3,
+
+ ///
+ /// The dialog box return value is Retry (usually sent from a button labeled Retry).
+ ///
+ Retry = 4,
+
+ ///
+ /// The dialog box return value is Ignore (usually sent from a button labeled Ignore).
+ ///
+ Ignore = 5,
+
+ ///
+ /// The dialog box return value is Yes (usually sent from a button labeled Yes).
+ ///
+ Yes = 6,
+
+ ///
+ /// The dialog box return value is No (usually sent from a button labeled No).
+ ///
+ No = 7
+ }
+
internal class MaterialMessageBoxViewModel : ObservableRecipient
{
#region Design time instance
diff --git a/SinglePass.WPF/ViewModels/ProcessingViewModel.cs b/SinglePass.WPF/ViewModels/Dialogs/ProcessingViewModel.cs
similarity index 97%
rename from SinglePass.WPF/ViewModels/ProcessingViewModel.cs
rename to SinglePass.WPF/ViewModels/Dialogs/ProcessingViewModel.cs
index 8187d88..d250f6d 100644
--- a/SinglePass.WPF/ViewModels/ProcessingViewModel.cs
+++ b/SinglePass.WPF/ViewModels/Dialogs/ProcessingViewModel.cs
@@ -4,7 +4,7 @@
using System;
using System.Threading;
-namespace SinglePass.WPF.ViewModels
+namespace SinglePass.WPF.ViewModels.Dialogs
{
[INotifyPropertyChanged]
public partial class ProcessingViewModel
diff --git a/SinglePass.WPF/ViewModels/MainWindowViewModel.cs b/SinglePass.WPF/ViewModels/MainWindowViewModel.cs
index aa50534..e7d50dc 100644
--- a/SinglePass.WPF/ViewModels/MainWindowViewModel.cs
+++ b/SinglePass.WPF/ViewModels/MainWindowViewModel.cs
@@ -3,6 +3,7 @@
using MaterialDesignThemes.Wpf;
using SinglePass.WPF.Collections;
using SinglePass.WPF.Views;
+using SinglePass.WPF.Views.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -25,6 +26,7 @@ private static MainWindowViewModel GetDesignTimeVM()
#endregion
public event Action CredentialSelected;
+ public event Action ScrollIntoViewRequired;
public PasswordsViewModel PasswordsVM { get; }
public SettingsViewModel SettingsVM { get; }
@@ -57,6 +59,7 @@ public MainWindowViewModel(
SettingsVM = settingsViewModel;
PasswordsVM.CredentialSelected += PasswordsViewModel_CredentialSelected;
+ PasswordsVM.ScrollIntoViewRequired += PasswordsVM_ScrollIntoViewRequired;
CloudSyncVM.SyncCompleted += SettingsViewModel_SyncCompleted;
NavigationItems = new ObservableCollectionDelayed(new List()
@@ -76,6 +79,11 @@ public MainWindowViewModel(
});
}
+ private void PasswordsVM_ScrollIntoViewRequired(CredentialViewModel credVM)
+ {
+ ScrollIntoViewRequired?.Invoke(credVM);
+ }
+
private void SettingsViewModel_SyncCompleted()
{
PasswordsVM.ReloadCredentials();
diff --git a/SinglePass.WPF/ViewModels/PasswordsViewModel.cs b/SinglePass.WPF/ViewModels/PasswordsViewModel.cs
index ff235c4..c0671b8 100644
--- a/SinglePass.WPF/ViewModels/PasswordsViewModel.cs
+++ b/SinglePass.WPF/ViewModels/PasswordsViewModel.cs
@@ -8,7 +8,8 @@
using SinglePass.WPF.Models;
using SinglePass.WPF.Services;
using SinglePass.WPF.Settings;
-using SinglePass.WPF.Views.MessageBox;
+using SinglePass.WPF.ViewModels.Dialogs;
+using SinglePass.WPF.Views.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -46,9 +47,10 @@ private static PasswordsViewModel GetDesignTimeVM()
private readonly CredentialViewModelFactory _credentialViewModelFactory;
public event Action CredentialSelected;
+ public event Action ScrollIntoViewRequired;
public ObservableCollectionDelayed DisplayedCredentials { get; private set; } = new();
- public CredentialsDialogViewModel ActiveCredentialDialogVM { get; }
+ public CredentialsDetailsViewModel ActiveCredentialDialogVM { get; }
private CredentialViewModel _selectedCredential;
public CredentialViewModel SelectedCredential
@@ -57,7 +59,7 @@ public CredentialViewModel SelectedCredential
set
{
SetProperty(ref _selectedCredential, value);
- ActiveCredentialDialogVM.Mode = CredentialsDialogMode.View;
+ ActiveCredentialDialogVM.Mode = CredentialDetailsMode.View;
ActiveCredentialDialogVM.CredentialViewModel = value;
ActiveCredentialDialogVM.IsPasswordVisible = false;
CredentialSelected?.Invoke(value);
@@ -102,7 +104,7 @@ private PasswordsViewModel() { }
public PasswordsViewModel(
CredentialsCryptoService credentialsCryptoService,
ILogger logger,
- CredentialsDialogViewModel credentialsDialogViewModel,
+ CredentialsDetailsViewModel credentialsDialogViewModel,
AppSettingsService appSettingsService,
CredentialViewModelFactory credentialViewModelFactory)
{
@@ -146,22 +148,22 @@ private async void ActiveCredentialDialogVM_Delete(CredentialViewModel credVM)
private void ActiveCredentialDialogVM_Cancel()
{
ActiveCredentialDialogVM.IsPasswordVisible = false;
- ActiveCredentialDialogVM.Mode = CredentialsDialogMode.View;
+ ActiveCredentialDialogVM.Mode = CredentialDetailsMode.View;
ActiveCredentialDialogVM.CredentialViewModel = SelectedCredential;
}
- private async void ActiveCredentialDialogVM_Accept(CredentialViewModel newCredVM, CredentialsDialogMode mode)
+ private async void ActiveCredentialDialogVM_Accept(CredentialViewModel newCredVM, CredentialDetailsMode mode)
{
var dateTimeNow = DateTime.Now;
newCredVM.LastModifiedTime = dateTimeNow;
- if (mode == CredentialsDialogMode.New)
+ if (mode == CredentialDetailsMode.New)
{
newCredVM.CreationTime = dateTimeNow;
await _credentialsCryptoService.AddCredential(newCredVM.Model);
_credentialVMs.Add(newCredVM);
await DisplayCredentialsAsync();
}
- else if (mode == CredentialsDialogMode.Edit)
+ else if (mode == CredentialDetailsMode.Edit)
{
await _credentialsCryptoService.EditCredential(newCredVM.Model);
var staleCredVM = _credentialVMs.FirstOrDefault(c => c.Model.Equals(newCredVM.Model));
@@ -257,7 +259,7 @@ await Task.Run(() =>
private void AddCredential()
{
ActiveCredentialDialogVM.CredentialViewModel = _credentialViewModelFactory.ProvideNew(Credential.CreateNew());
- ActiveCredentialDialogVM.Mode = CredentialsDialogMode.New;
+ ActiveCredentialDialogVM.Mode = CredentialDetailsMode.New;
ActiveCredentialDialogVM.IsPasswordVisible = true;
ActiveCredentialDialogVM.SetFocus();
}
@@ -268,24 +270,30 @@ private void HandleSearchKey(KeyEventArgs args)
if (args is null)
return;
- if (args.Key == Key.Up)
+ switch (args.Key)
{
- // Select previous
- var selectedIndex = DisplayedCredentials.IndexOf(SelectedCredential);
- if (selectedIndex != -1 && selectedIndex > 0)
- {
- SelectedCredential = DisplayedCredentials[selectedIndex - 1];
- }
- }
-
- if (args.Key == Key.Down)
- {
- // Select next
- var selectedIndex = DisplayedCredentials.IndexOf(SelectedCredential);
- if (selectedIndex != -1 && selectedIndex < DisplayedCredentials.Count - 1)
- {
- SelectedCredential = DisplayedCredentials[selectedIndex + 1];
- }
+ case Key.Up:
+ {
+ // Select previous
+ var selectedIndex = DisplayedCredentials.IndexOf(SelectedCredential);
+ if (selectedIndex != -1 && selectedIndex > 0)
+ {
+ SelectedCredential = DisplayedCredentials[selectedIndex - 1];
+ ScrollIntoViewRequired?.Invoke(SelectedCredential);
+ }
+ break;
+ }
+ case Key.Down:
+ {
+ // Select next
+ var selectedIndex = DisplayedCredentials.IndexOf(SelectedCredential);
+ if (selectedIndex != -1 && selectedIndex < DisplayedCredentials.Count - 1)
+ {
+ SelectedCredential = DisplayedCredentials[selectedIndex + 1];
+ ScrollIntoViewRequired?.Invoke(SelectedCredential);
+ }
+ break;
+ }
}
}
diff --git a/SinglePass.WPF/ViewModels/PopupViewModel.cs b/SinglePass.WPF/ViewModels/PopupViewModel.cs
index dc61a49..c0a311a 100644
--- a/SinglePass.WPF/ViewModels/PopupViewModel.cs
+++ b/SinglePass.WPF/ViewModels/PopupViewModel.cs
@@ -34,6 +34,7 @@ private static PopupViewModel GetDesignTimeVM()
private readonly List _credentialVMs = new();
public event Action Accept;
+ public event Action ScrollIntoViewRequired;
public ObservableCollectionDelayed DisplayedCredentials { get; private set; } = new();
@@ -240,6 +241,7 @@ private void HandleSearchKey(KeyEventArgs args)
if (selectedIndex != -1 && selectedIndex > 0)
{
SelectedCredentialVM = DisplayedCredentials[selectedIndex - 1];
+ ScrollIntoViewRequired?.Invoke(SelectedCredentialVM);
}
break;
}
@@ -250,6 +252,7 @@ private void HandleSearchKey(KeyEventArgs args)
if (selectedIndex != -1 && selectedIndex < DisplayedCredentials.Count - 1)
{
SelectedCredentialVM = DisplayedCredentials[selectedIndex + 1];
+ ScrollIntoViewRequired?.Invoke(SelectedCredentialVM);
}
break;
}
diff --git a/SinglePass.WPF/ViewModels/SettingsViewModel.cs b/SinglePass.WPF/ViewModels/SettingsViewModel.cs
index 1b4f281..7a862ac 100644
--- a/SinglePass.WPF/ViewModels/SettingsViewModel.cs
+++ b/SinglePass.WPF/ViewModels/SettingsViewModel.cs
@@ -6,7 +6,8 @@
using SinglePass.WPF.Hotkeys;
using SinglePass.WPF.Services;
using SinglePass.WPF.Settings;
-using SinglePass.WPF.Views.MessageBox;
+using SinglePass.WPF.ViewModels.Dialogs;
+using SinglePass.WPF.Views.Helpers;
using System;
using System.Threading.Tasks;
diff --git a/SinglePass.WPF/ViewModels/TrayIconViewModel.cs b/SinglePass.WPF/ViewModels/TrayIconViewModel.cs
index 2c4afe9..012515f 100644
--- a/SinglePass.WPF/ViewModels/TrayIconViewModel.cs
+++ b/SinglePass.WPF/ViewModels/TrayIconViewModel.cs
@@ -1,7 +1,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using SinglePass.WPF.Extensions;
-using SinglePass.WPF.Views;
+using SinglePass.WPF.Views.Windows;
using System.Linq;
namespace SinglePass.WPF.ViewModels
diff --git a/SinglePass.WPF/Views/CloudSyncControl.xaml b/SinglePass.WPF/Views/Controls/CloudSyncControl.xaml
similarity index 98%
rename from SinglePass.WPF/Views/CloudSyncControl.xaml
rename to SinglePass.WPF/Views/Controls/CloudSyncControl.xaml
index b7c03d7..f9ed07d 100644
--- a/SinglePass.WPF/Views/CloudSyncControl.xaml
+++ b/SinglePass.WPF/Views/Controls/CloudSyncControl.xaml
@@ -1,5 +1,5 @@
diff --git a/SinglePass.WPF/Views/CloudSyncControl.xaml.cs b/SinglePass.WPF/Views/Controls/CloudSyncControl.xaml.cs
similarity index 87%
rename from SinglePass.WPF/Views/CloudSyncControl.xaml.cs
rename to SinglePass.WPF/Views/Controls/CloudSyncControl.xaml.cs
index 4f7ef33..3094ff3 100644
--- a/SinglePass.WPF/Views/CloudSyncControl.xaml.cs
+++ b/SinglePass.WPF/Views/Controls/CloudSyncControl.xaml.cs
@@ -1,6 +1,6 @@
using System.Windows.Controls;
-namespace SinglePass.WPF.Views
+namespace SinglePass.WPF.Views.Controls
{
///
/// Interaction logic for CloudSyncControl.xaml
diff --git a/SinglePass.WPF/Views/CredentialsDialog.xaml b/SinglePass.WPF/Views/Controls/CredentialsDetailsControl.xaml
similarity index 99%
rename from SinglePass.WPF/Views/CredentialsDialog.xaml
rename to SinglePass.WPF/Views/Controls/CredentialsDetailsControl.xaml
index 92cc5f2..667995a 100644
--- a/SinglePass.WPF/Views/CredentialsDialog.xaml
+++ b/SinglePass.WPF/Views/Controls/CredentialsDetailsControl.xaml
@@ -1,5 +1,5 @@
diff --git a/SinglePass.WPF/Views/Controls/CredentialsDetailsControl.xaml.cs b/SinglePass.WPF/Views/Controls/CredentialsDetailsControl.xaml.cs
new file mode 100644
index 0000000..83cea85
--- /dev/null
+++ b/SinglePass.WPF/Views/Controls/CredentialsDetailsControl.xaml.cs
@@ -0,0 +1,15 @@
+using System.Windows.Controls;
+
+namespace SinglePass.WPF.Views.Controls
+{
+ ///
+ /// Interaction logic for CredentialsDetailsControl.xaml
+ ///
+ public partial class CredentialsDetailsControl : UserControl
+ {
+ public CredentialsDetailsControl()
+ {
+ InitializeComponent();
+ }
+ }
+}
diff --git a/SinglePass.WPF/Views/PasswordsControl.xaml b/SinglePass.WPF/Views/Controls/PasswordsControl.xaml
similarity index 97%
rename from SinglePass.WPF/Views/PasswordsControl.xaml
rename to SinglePass.WPF/Views/Controls/PasswordsControl.xaml
index b62b937..92bd237 100644
--- a/SinglePass.WPF/Views/PasswordsControl.xaml
+++ b/SinglePass.WPF/Views/Controls/PasswordsControl.xaml
@@ -1,5 +1,5 @@
-
+
@@ -120,7 +121,6 @@
x:Name="SearchTextBox"
Padding="4"
VerticalAlignment="Center"
- assists:FocusAssist.IsFocused="{Binding SearchTextFocused, UpdateSourceTrigger=PropertyChanged}"
md:HintAssist.Hint="{x:Static properties:Resources.Search}"
md:HintAssist.IsFloating="False"
md:TextFieldAssist.HasClearButton="True"
@@ -241,8 +241,8 @@
Width="4"
Background="Transparent"
ResizeBehavior="PreviousAndNext" />
-
/// Interaction logic for PasswordsControl.xaml
diff --git a/SinglePass.WPF/Views/SettingsControl.xaml b/SinglePass.WPF/Views/Controls/SettingsControl.xaml
similarity index 51%
rename from SinglePass.WPF/Views/SettingsControl.xaml
rename to SinglePass.WPF/Views/Controls/SettingsControl.xaml
index 18fae4c..6ee5d5f 100644
--- a/SinglePass.WPF/Views/SettingsControl.xaml
+++ b/SinglePass.WPF/Views/Controls/SettingsControl.xaml
@@ -1,5 +1,5 @@
-
+
@@ -51,34 +51,48 @@
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -135,30 +149,47 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/SinglePass.WPF/Views/SettingsControl.xaml.cs b/SinglePass.WPF/Views/Controls/SettingsControl.xaml.cs
similarity index 85%
rename from SinglePass.WPF/Views/SettingsControl.xaml.cs
rename to SinglePass.WPF/Views/Controls/SettingsControl.xaml.cs
index b998aa7..d6034cb 100644
--- a/SinglePass.WPF/Views/SettingsControl.xaml.cs
+++ b/SinglePass.WPF/Views/Controls/SettingsControl.xaml.cs
@@ -4,7 +4,7 @@
using System.Windows.Controls;
using System.Windows.Media;
-namespace SinglePass.WPF.Views
+namespace SinglePass.WPF.Views.Controls
{
///
/// Interaction logic for SettingsControl.xaml
@@ -23,13 +23,6 @@ private void UserControl_Loaded(object sender, RoutedEventArgs e)
var darkColor = BaseTheme.Dark.GetBaseTheme().MaterialDesignPaper;
var lightColor = BaseTheme.Light.GetBaseTheme().MaterialDesignPaper;
- var gradCollection = new GradientStopCollection()
- {
- new GradientStop(darkColor, 0.5),
- new GradientStop(lightColor, 0.5),
- };
-
- AutoButton.Background = new LinearGradientBrush(gradCollection, 0);
DarkButton.Background = new SolidColorBrush(darkColor);
LightButton.Background = new SolidColorBrush(lightColor);
}
diff --git a/SinglePass.WPF/Views/CredentialsDialog.xaml.cs b/SinglePass.WPF/Views/CredentialsDialog.xaml.cs
deleted file mode 100644
index 0e783c7..0000000
--- a/SinglePass.WPF/Views/CredentialsDialog.xaml.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System.Windows.Controls;
-
-namespace SinglePass.WPF.Views
-{
- ///
- /// Interaction logic for CredentialsDialog.xaml
- ///
- public partial class CredentialsDialog : UserControl
- {
- public CredentialsDialog()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/SinglePass.WPF/Views/InputBox/MaterialInputBoxContent.xaml b/SinglePass.WPF/Views/Dialogs/MaterialInputBoxContent.xaml
similarity index 90%
rename from SinglePass.WPF/Views/InputBox/MaterialInputBoxContent.xaml
rename to SinglePass.WPF/Views/Dialogs/MaterialInputBoxContent.xaml
index 54eaa02..e059a17 100644
--- a/SinglePass.WPF/Views/InputBox/MaterialInputBoxContent.xaml
+++ b/SinglePass.WPF/Views/Dialogs/MaterialInputBoxContent.xaml
@@ -1,14 +1,15 @@
/// Interaction logic for MaterialInputBoxContent.xaml
diff --git a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxContent.xaml b/SinglePass.WPF/Views/Dialogs/MaterialMessageBoxContent.xaml
similarity index 92%
rename from SinglePass.WPF/Views/MessageBox/MaterialMessageBoxContent.xaml
rename to SinglePass.WPF/Views/Dialogs/MaterialMessageBoxContent.xaml
index 48707ff..76d9aa3 100644
--- a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxContent.xaml
+++ b/SinglePass.WPF/Views/Dialogs/MaterialMessageBoxContent.xaml
@@ -1,14 +1,15 @@
diff --git a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxContent.xaml.cs b/SinglePass.WPF/Views/Dialogs/MaterialMessageBoxContent.xaml.cs
similarity index 88%
rename from SinglePass.WPF/Views/MessageBox/MaterialMessageBoxContent.xaml.cs
rename to SinglePass.WPF/Views/Dialogs/MaterialMessageBoxContent.xaml.cs
index 3347985..fd29a09 100644
--- a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxContent.xaml.cs
+++ b/SinglePass.WPF/Views/Dialogs/MaterialMessageBoxContent.xaml.cs
@@ -1,6 +1,6 @@
using System.Windows.Controls;
-namespace SinglePass.WPF.Views.MessageBox
+namespace SinglePass.WPF.Views.Dialogs
{
///
/// Interaction logic for MaterialMessageBoxContent.xaml
diff --git a/SinglePass.WPF/Views/ProcessingControl.xaml b/SinglePass.WPF/Views/Dialogs/ProcessingDialogContent.xaml
similarity index 85%
rename from SinglePass.WPF/Views/ProcessingControl.xaml
rename to SinglePass.WPF/Views/Dialogs/ProcessingDialogContent.xaml
index d18ddd9..780787b 100644
--- a/SinglePass.WPF/Views/ProcessingControl.xaml
+++ b/SinglePass.WPF/Views/Dialogs/ProcessingDialogContent.xaml
@@ -1,11 +1,12 @@
diff --git a/SinglePass.WPF/Views/ProcessingControl.xaml.cs b/SinglePass.WPF/Views/Dialogs/ProcessingDialogContent.xaml.cs
similarity index 50%
rename from SinglePass.WPF/Views/ProcessingControl.xaml.cs
rename to SinglePass.WPF/Views/Dialogs/ProcessingDialogContent.xaml.cs
index 16127ce..da60ced 100644
--- a/SinglePass.WPF/Views/ProcessingControl.xaml.cs
+++ b/SinglePass.WPF/Views/Dialogs/ProcessingDialogContent.xaml.cs
@@ -1,16 +1,16 @@
-using SinglePass.WPF.ViewModels;
+using SinglePass.WPF.ViewModels.Dialogs;
using System.Windows.Controls;
-namespace SinglePass.WPF.Views
+namespace SinglePass.WPF.Views.Dialogs
{
///
- /// Interaction logic for ProcessingControl.xaml
+ /// Interaction logic for ProcessingDialogContent.xaml
///
- public partial class ProcessingControl : UserControl
+ public partial class ProcessingDialogContent : UserControl
{
public ProcessingViewModel ViewModel => DataContext as ProcessingViewModel;
- public ProcessingControl(string headText, string midText, string dialogIdentifier)
+ public ProcessingDialogContent(string headText, string midText, string dialogIdentifier)
{
InitializeComponent();
diff --git a/SinglePass.WPF/Views/InputBox/MaterialInputBox.cs b/SinglePass.WPF/Views/Helpers/MaterialInputBox.cs
similarity index 92%
rename from SinglePass.WPF/Views/InputBox/MaterialInputBox.cs
rename to SinglePass.WPF/Views/Helpers/MaterialInputBox.cs
index 9d39dc1..c242692 100644
--- a/SinglePass.WPF/Views/InputBox/MaterialInputBox.cs
+++ b/SinglePass.WPF/Views/Helpers/MaterialInputBox.cs
@@ -1,7 +1,9 @@
using MaterialDesignThemes.Wpf;
+using SinglePass.WPF.ViewModels.Dialogs;
+using SinglePass.WPF.Views.Dialogs;
using System.Threading.Tasks;
-namespace SinglePass.WPF.Views.InputBox
+namespace SinglePass.WPF.Views.Helpers
{
public static class MaterialInputBox
{
diff --git a/SinglePass.WPF/Views/MessageBox/MaterialMessageBox.cs b/SinglePass.WPF/Views/Helpers/MaterialMessageBox.cs
similarity index 92%
rename from SinglePass.WPF/Views/MessageBox/MaterialMessageBox.cs
rename to SinglePass.WPF/Views/Helpers/MaterialMessageBox.cs
index 81fc565..0510d3c 100644
--- a/SinglePass.WPF/Views/MessageBox/MaterialMessageBox.cs
+++ b/SinglePass.WPF/Views/Helpers/MaterialMessageBox.cs
@@ -1,7 +1,9 @@
using MaterialDesignThemes.Wpf;
+using SinglePass.WPF.ViewModels.Dialogs;
+using SinglePass.WPF.Views.Dialogs;
using System.Threading.Tasks;
-namespace SinglePass.WPF.Views.MessageBox
+namespace SinglePass.WPF.Views.Helpers
{
public static class MaterialMessageBox
{
diff --git a/SinglePass.WPF/Views/Helpers/ProcessingDialog.cs b/SinglePass.WPF/Views/Helpers/ProcessingDialog.cs
new file mode 100644
index 0000000..4ded431
--- /dev/null
+++ b/SinglePass.WPF/Views/Helpers/ProcessingDialog.cs
@@ -0,0 +1,33 @@
+using MaterialDesignThemes.Wpf;
+using SinglePass.WPF.Views.Dialogs;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SinglePass.WPF.Views.Helpers
+{
+ internal static class ProcessingDialog
+ {
+ ///
+ /// Shows processing dialog content. To return cancellation token to cancel some operation
+ /// don't await this call.
+ ///
+ /// Head text.
+ /// Main text.
+ /// identifier where need to show materialized message box. It's analog of window's HWND.
+ /// Cancellation token that will be cancelled if user clikc Cancel button in dialog.
+ ///
+ public static Task Show(
+ string headText,
+ string midText,
+ string dialogIdentifier,
+ out CancellationToken cancellationToken)
+ {
+ var processingControl = new ProcessingDialogContent(
+ headText,
+ midText,
+ dialogIdentifier);
+ cancellationToken = processingControl.ViewModel.CancellationToken;
+ return DialogHost.Show(processingControl, dialogIdentifier);
+ }
+ }
+}
diff --git a/SinglePass.WPF/Views/MessageBox/MaterialDialogResult.cs b/SinglePass.WPF/Views/MessageBox/MaterialDialogResult.cs
deleted file mode 100644
index ee1bc1f..0000000
--- a/SinglePass.WPF/Views/MessageBox/MaterialDialogResult.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-namespace SinglePass.WPF.Views.MessageBox
-{
- public enum MaterialDialogResult
- {
- ///
- /// Nothing is returned from the dialog box. This means that the modal dialog continues running.
- ///
- None = 0,
-
- ///
- /// The dialog box return value is OK (usually sent from a button labeled OK).
- ///
- OK = 1,
-
- ///
- /// The dialog box return value is Cancel (usually sent from a button labeled Cancel).
- ///
- Cancel = 2,
-
- ///
- /// The dialog box return value is Abort (usually sent from a button labeled Abort).
- ///
- Abort = 3,
-
- ///
- /// The dialog box return value is Retry (usually sent from a button labeled Retry).
- ///
- Retry = 4,
-
- ///
- /// The dialog box return value is Ignore (usually sent from a button labeled Ignore).
- ///
- Ignore = 5,
-
- ///
- /// The dialog box return value is Yes (usually sent from a button labeled Yes).
- ///
- Yes = 6,
-
- ///
- /// The dialog box return value is No (usually sent from a button labeled No).
- ///
- No = 7
- }
-}
diff --git a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxButtons.cs b/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxButtons.cs
deleted file mode 100644
index 466dd45..0000000
--- a/SinglePass.WPF/Views/MessageBox/MaterialMessageBoxButtons.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-namespace SinglePass.WPF.Views.MessageBox
-{
- public enum MaterialMessageBoxButtons
- {
- ///
- /// The message box contains an OK button.
- ///
- OK,
-
- ///
- /// The message box contains OK and Cancel buttons.
- ///
- OKCancel,
-
- ///
- /// The message box contains Yes and No buttons.
- ///
- YesNo,
-
- ///
- /// The message box contains Yes, No, and Cancel buttons.
- ///
- YesNoCancel,
-
- ///
- /// The message box contains Abort, Retry, and Ignore buttons.
- ///
- AbortRetryIgnore,
-
- ///
- /// The message box contains Retry and Cancel buttons.
- ///
- RetryCancel
- }
-}
diff --git a/SinglePass.WPF/Views/Windows/HiddenInterprocessWindow.xaml b/SinglePass.WPF/Views/Windows/HiddenInterprocessWindow.xaml
new file mode 100644
index 0000000..ad8e783
--- /dev/null
+++ b/SinglePass.WPF/Views/Windows/HiddenInterprocessWindow.xaml
@@ -0,0 +1,17 @@
+
+
+
diff --git a/SinglePass.WPF/Views/Windows/HiddenInterprocessWindow.xaml.cs b/SinglePass.WPF/Views/Windows/HiddenInterprocessWindow.xaml.cs
new file mode 100644
index 0000000..783a0e4
--- /dev/null
+++ b/SinglePass.WPF/Views/Windows/HiddenInterprocessWindow.xaml.cs
@@ -0,0 +1,44 @@
+using SinglePass.WPF.Enums;
+using SinglePass.WPF.Extensions;
+using System;
+using System.Linq;
+using System.Windows;
+using System.Windows.Interop;
+
+namespace SinglePass.WPF.Views.Windows
+{
+ ///
+ /// Interaction logic for HiddenInterprocessWindow.xaml
+ ///
+ public partial class HiddenInterprocessWindow : Window
+ {
+ public HiddenInterprocessWindow()
+ {
+ InitializeComponent();
+ }
+
+ public void InitWithoutShowing()
+ {
+ // Like PresentationSource.FromVisual(this) as HwndSource
+ var handle = new WindowInteropHelper(this).EnsureHandle();
+ HwndSource source = HwndSource.FromHwnd(handle);
+ source.AddHook(WndProc);
+ }
+
+ private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
+ {
+ // Handle messages...
+ switch (msg)
+ {
+ case (int)CustomWindowsMessages.WM_SHOW_MAIN_WINDOW:
+ {
+ var mainWindow = Application.Current.Windows.OfType().FirstOrDefault();
+ mainWindow?.BringToFrontAndActivate();
+ break;
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+ }
+}
diff --git a/SinglePass.WPF/Views/LoginWindow.xaml b/SinglePass.WPF/Views/Windows/LoginWindow.xaml
similarity index 95%
rename from SinglePass.WPF/Views/LoginWindow.xaml
rename to SinglePass.WPF/Views/Windows/LoginWindow.xaml
index 220d871..d36eeeb 100644
--- a/SinglePass.WPF/Views/LoginWindow.xaml
+++ b/SinglePass.WPF/Views/Windows/LoginWindow.xaml
@@ -1,5 +1,5 @@
diff --git a/SinglePass.WPF/Views/LoginWindow.xaml.cs b/SinglePass.WPF/Views/Windows/LoginWindow.xaml.cs
similarity index 97%
rename from SinglePass.WPF/Views/LoginWindow.xaml.cs
rename to SinglePass.WPF/Views/Windows/LoginWindow.xaml.cs
index 1bcb8b8..e17cbaf 100644
--- a/SinglePass.WPF/Views/LoginWindow.xaml.cs
+++ b/SinglePass.WPF/Views/Windows/LoginWindow.xaml.cs
@@ -2,7 +2,7 @@
using SinglePass.WPF.ViewModels;
using System.Windows.Controls;
-namespace SinglePass.WPF.Views
+namespace SinglePass.WPF.Views.Windows
{
///
/// Interaction logic for LoginWindow.xaml
diff --git a/SinglePass.WPF/Views/MainWindow.xaml b/SinglePass.WPF/Views/Windows/MainWindow.xaml
similarity index 97%
rename from SinglePass.WPF/Views/MainWindow.xaml
rename to SinglePass.WPF/Views/Windows/MainWindow.xaml
index 00a752e..6e3ad2a 100644
--- a/SinglePass.WPF/Views/MainWindow.xaml
+++ b/SinglePass.WPF/Views/Windows/MainWindow.xaml
@@ -1,5 +1,5 @@
+
diff --git a/SinglePass.WPF/Views/MainWindow.xaml.cs b/SinglePass.WPF/Views/Windows/MainWindow.xaml.cs
similarity index 87%
rename from SinglePass.WPF/Views/MainWindow.xaml.cs
rename to SinglePass.WPF/Views/Windows/MainWindow.xaml.cs
index 8ba83d1..a4b58e8 100644
--- a/SinglePass.WPF/Views/MainWindow.xaml.cs
+++ b/SinglePass.WPF/Views/Windows/MainWindow.xaml.cs
@@ -1,33 +1,29 @@
using SinglePass.WPF.Controls;
using SinglePass.WPF.Helpers;
-using SinglePass.WPF.Hotkeys;
using SinglePass.WPF.Settings;
using SinglePass.WPF.ViewModels;
+using SinglePass.WPF.Views.Controls;
using System;
-using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
-namespace SinglePass.WPF.Views
+namespace SinglePass.WPF.Views.Windows
{
///
/// Interaction logic for MainWindow.xaml
///
public partial class MainWindow : MaterialWindow
{
- private readonly HotkeysService _hotkeyService;
private readonly AppSettingsService _appSettingsService;
private MainWindowViewModel ViewModel => DataContext as MainWindowViewModel;
public MainWindow(
MainWindowViewModel mainViewModel,
- HotkeysService hotkeysService,
AppSettingsService appSettingsService)
{
InitializeComponent();
- _hotkeyService = hotkeysService;
_appSettingsService = appSettingsService;
var windowSettings = _appSettingsService.MainWindowSettings;
@@ -45,11 +41,18 @@ public MainWindow(
}
mainViewModel.CredentialSelected += Vm_CredentialSelected;
+ mainViewModel.ScrollIntoViewRequired += MainViewModel_ScrollIntoViewRequired;
mainViewModel.PasswordsVM.ActiveCredentialDialogVM.EnqueueSnackbarMessage += ActiveCredentialDialogVM_EnqueueSnackbarMessage;
DataContext = mainViewModel;
}
+ private void MainViewModel_ScrollIntoViewRequired(CredentialViewModel credVM)
+ {
+ if (ViewModel.SelectedNavigationItem?.Content is PasswordsControl passwordsControl)
+ passwordsControl.CredentialsListBox.ScrollIntoView(credVM);
+ }
+
private void ActiveCredentialDialogVM_EnqueueSnackbarMessage(string message)
{
if (SnackbarMain.MessageQueue is { } messageQueue)
@@ -63,8 +66,7 @@ private void Vm_CredentialSelected(CredentialViewModel credVM)
var passStringLength = credVM?.PasswordFieldVM?.Value?.Length ?? 0;
if (ViewModel.SelectedNavigationItem?.Content is PasswordsControl passwordsControl)
{
- passwordsControl.CredentialsDialog.PasswordFieldBox.Password = new string('*', passStringLength);
- passwordsControl.CredentialsListBox.ScrollIntoView(credVM);
+ passwordsControl.CredentialsDetailsControl.PasswordFieldBox.Password = new string('*', passStringLength);
}
}
@@ -84,12 +86,11 @@ private void ListBoxItem_PreviewMouseDown(object sender, MouseButtonEventArgs e)
e.Handled = true;
}
- private async void MaterialWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
+ private void MaterialWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is bool visibility && visibility && ViewModel.SelectedNavigationItem?.Content is PasswordsControl passwordsControl)
{
- await Task.Delay(1);
- passwordsControl.SearchTextBox.Focus();
+ Application.Current.Dispatcher.InvokeAsync(() => passwordsControl.SearchTextBox.Focus());
}
}
diff --git a/SinglePass.WPF/Views/PopupWindow.xaml b/SinglePass.WPF/Views/Windows/PopupWindow.xaml
similarity index 95%
rename from SinglePass.WPF/Views/PopupWindow.xaml
rename to SinglePass.WPF/Views/Windows/PopupWindow.xaml
index e3b50a2..95b2f8d 100644
--- a/SinglePass.WPF/Views/PopupWindow.xaml
+++ b/SinglePass.WPF/Views/Windows/PopupWindow.xaml
@@ -1,5 +1,5 @@
@@ -50,7 +51,8 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
Text="{Binding NameFieldVM.Value}"
- TextTrimming="CharacterEllipsis" />
+ TextTrimming="CharacterEllipsis"
+ ToolTip="{Binding NameFieldVM.Value}" />
@@ -110,7 +112,6 @@
x:Name="CredListBox"
Grid.Row="1"
HorizontalContentAlignment="Stretch"
- md:RippleAssist.IsDisabled="True"
ItemContainerStyle="{StaticResource CredentialPopupItemStyle}"
ItemTemplate="{StaticResource CredentialDataTemplate}"
ItemsSource="{Binding DisplayedCredentials}"
diff --git a/SinglePass.WPF/Views/PopupWindow.xaml.cs b/SinglePass.WPF/Views/Windows/PopupWindow.xaml.cs
similarity index 57%
rename from SinglePass.WPF/Views/PopupWindow.xaml.cs
rename to SinglePass.WPF/Views/Windows/PopupWindow.xaml.cs
index 9da3973..02eee4c 100644
--- a/SinglePass.WPF/Views/PopupWindow.xaml.cs
+++ b/SinglePass.WPF/Views/Windows/PopupWindow.xaml.cs
@@ -2,8 +2,9 @@
using SinglePass.WPF.ViewModels;
using System;
using System.Windows;
+using System.Windows.Input;
-namespace SinglePass.WPF.Views
+namespace SinglePass.WPF.Views.Windows
{
///
/// Interaction logic for PopupWindow.xaml
@@ -23,12 +24,18 @@ public IntPtr ForegroundHWND
public PopupWindow(PopupViewModel popupViewModel)
{
InitializeComponent();
-
+
popupViewModel.Accept += PopupViewModel_Accept;
+ popupViewModel.ScrollIntoViewRequired += PopupViewModel_ScrollIntoViewRequired;
DataContext = popupViewModel;
ViewModel = popupViewModel;
}
+ private void PopupViewModel_ScrollIntoViewRequired(CredentialViewModel vm)
+ {
+ CredListBox.ScrollIntoView(vm);
+ }
+
private void PopupViewModel_Accept()
{
Close();
@@ -52,8 +59,31 @@ private void MaterialWindow_Loaded(object sender, System.Windows.RoutedEventArgs
private void MaterialWindow_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
+ var isCtrlDown = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
+ var isEnterDown = e.Key == Key.Enter;
+
+ if (isEnterDown)
+ {
+ if (isCtrlDown)
+ {
+ // This is Ctrl + Enter
+ ViewModel.SetAndCloseCommand.Execute(ViewModel.SelectedCredentialVM.PasswordFieldVM);
+ }
+ else
+ {
+ // This is Enter
+ ViewModel.SetAndCloseCommand.Execute(ViewModel.SelectedCredentialVM.LoginFieldVM);
+ }
+ }
+
if (e.Key == System.Windows.Input.Key.Escape)
Close();
}
+
+ private void MaterialWindow_Closed(object sender, EventArgs e)
+ {
+ ViewModel.Accept -= PopupViewModel_Accept;
+ ViewModel.ScrollIntoViewRequired -= PopupViewModel_ScrollIntoViewRequired;
+ }
}
}
diff --git a/SinglePass.WPF/Views/WelcomeWindow.xaml b/SinglePass.WPF/Views/Windows/WelcomeWindow.xaml
similarity index 95%
rename from SinglePass.WPF/Views/WelcomeWindow.xaml
rename to SinglePass.WPF/Views/Windows/WelcomeWindow.xaml
index bcd76cf..b0a107c 100644
--- a/SinglePass.WPF/Views/WelcomeWindow.xaml
+++ b/SinglePass.WPF/Views/Windows/WelcomeWindow.xaml
@@ -1,5 +1,5 @@
/// Interaction logic for WelcomeWindow.xaml