From 50d63c289cc55cc18f2ebc5de8be439e567d12c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Dubouchet?= Date: Thu, 18 Nov 2021 16:04:17 +0100 Subject: [PATCH 01/16] Fix warnings --- src/ProtonVPN.App/Core/AutoConnect.cs | 2 +- src/ProtonVPN.App/Core/LanguageProvider.cs | 8 ++-- .../Core/Service/Vpn/VpnServiceManager.cs | 2 + .../Settings/AppSettingsStorage.cs | 2 +- src/ProtonVPN.App/Windows/AppWindow.xaml.cs | 2 +- .../OS/Registry/SafeStartupRecord.cs | 3 +- .../OS/Registry/SafeSystemProxy.cs | 5 +- src/ProtonVPN.Common/Vpn/VpnHost.cs | 16 +++++-- src/ProtonVPN.Core/Profiles/SyncProfiles.cs | 47 ++++++++++--------- src/ProtonVPN.Core/Storage/CachedSettings.cs | 2 +- .../Storage/EnumAsStringSettings.cs | 8 ++-- .../Storage/EnumerableAsJsonSettings.cs | 2 +- src/ProtonVPN.Service/VpnConnectionHandler.cs | 3 +- .../Servers/ServerLoadUpdaterTest.cs | 4 +- 14 files changed, 57 insertions(+), 49 deletions(-) diff --git a/src/ProtonVPN.App/Core/AutoConnect.cs b/src/ProtonVPN.App/Core/AutoConnect.cs index dff070fb6..f7edf6243 100644 --- a/src/ProtonVPN.App/Core/AutoConnect.cs +++ b/src/ProtonVPN.App/Core/AutoConnect.cs @@ -54,7 +54,7 @@ public async Task Load(bool autoLogin) return; try { - var profile = await _profileManager.GetProfileById(_appSettings.AutoConnect); + Profile profile = await _profileManager.GetProfileById(_appSettings.AutoConnect); if (profile == null) { diff --git a/src/ProtonVPN.App/Core/LanguageProvider.cs b/src/ProtonVPN.App/Core/LanguageProvider.cs index bd6d83820..aeaa51517 100644 --- a/src/ProtonVPN.App/Core/LanguageProvider.cs +++ b/src/ProtonVPN.App/Core/LanguageProvider.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using ProtonVPN.Common.Extensions; @@ -29,7 +29,7 @@ public List GetAll() } catch (Exception e) when (e.IsFileAccessException()) { - _logger.Error(e); + _logger.Error("Couldn't get language file.", e); return new List{ _defaultLocale }; } } @@ -37,9 +37,9 @@ public List GetAll() private List InternalGetAll() { var langs = new List { _defaultLocale }; - var files = Directory.GetFiles(_translationsFolder, ResourceFile, SearchOption.AllDirectories); + string[] files = Directory.GetFiles(_translationsFolder, ResourceFile, SearchOption.AllDirectories); - foreach (var file in files) + foreach (string file in files) { var dirInfo = new DirectoryInfo(file); if (dirInfo.Parent != null) diff --git a/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceManager.cs b/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceManager.cs index e5f439c09..17822bbb8 100644 --- a/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceManager.cs +++ b/src/ProtonVPN.App/Core/Service/Vpn/VpnServiceManager.cs @@ -174,6 +174,7 @@ private static VpnProtocolContract Map(VpnProtocol protocol) VpnProtocol.OpenVpnTcp => VpnProtocolContract.OpenVpnTcp, VpnProtocol.WireGuard => VpnProtocolContract.WireGuard, VpnProtocol.Smart => VpnProtocolContract.Smart, + _ => throw new NotImplementedException("VpnProtocol has an unknown value.") }; } @@ -186,6 +187,7 @@ private static VpnProtocol Map(VpnProtocolContract protocol) VpnProtocolContract.OpenVpnTcp => VpnProtocol.OpenVpnTcp, VpnProtocolContract.WireGuard => VpnProtocol.WireGuard, VpnProtocolContract.Smart => VpnProtocol.Smart, + _ => throw new NotImplementedException("VpnProtocol has an unknown value.") }; } diff --git a/src/ProtonVPN.App/Settings/AppSettingsStorage.cs b/src/ProtonVPN.App/Settings/AppSettingsStorage.cs index fc731b1aa..4566c9eb3 100644 --- a/src/ProtonVPN.App/Settings/AppSettingsStorage.cs +++ b/src/ProtonVPN.App/Settings/AppSettingsStorage.cs @@ -118,7 +118,7 @@ private void Migrate() private void ExecuteCustomMigrations() { - foreach (var migration in _migrations.OrderBy(m => m.ToVersion)) + foreach (IMigration migration in _migrations.OrderBy(m => m.ToVersion)) { migration.Apply(); } diff --git a/src/ProtonVPN.App/Windows/AppWindow.xaml.cs b/src/ProtonVPN.App/Windows/AppWindow.xaml.cs index 2712891e3..b944f6111 100644 --- a/src/ProtonVPN.App/Windows/AppWindow.xaml.cs +++ b/src/ProtonVPN.App/Windows/AppWindow.xaml.cs @@ -148,7 +148,7 @@ public void OnStepChanged(int step) ResizeMode = step > 0 ? ResizeMode.NoResize : ResizeMode.CanResize; } - protected override async void OnStateChanged(EventArgs e) + protected override void OnStateChanged(EventArgs e) { base.OnStateChanged(e); diff --git a/src/ProtonVPN.Common/OS/Registry/SafeStartupRecord.cs b/src/ProtonVPN.Common/OS/Registry/SafeStartupRecord.cs index 9860064a9..02972f592 100644 --- a/src/ProtonVPN.Common/OS/Registry/SafeStartupRecord.cs +++ b/src/ProtonVPN.Common/OS/Registry/SafeStartupRecord.cs @@ -71,8 +71,7 @@ private TResult HandleExceptions(Func function, TResult defaul } catch (Exception ex) when (ex.IsRegistryAccessException()) { - _logger.Error($"Can't {actionName} auto start record in Windows registry"); - _logger.Error(ex); + _logger.Error($"Can't {actionName} auto start record in Windows registry", ex); } return defaultResult; diff --git a/src/ProtonVPN.Common/OS/Registry/SafeSystemProxy.cs b/src/ProtonVPN.Common/OS/Registry/SafeSystemProxy.cs index be9659ab1..ac2b94042 100644 --- a/src/ProtonVPN.Common/OS/Registry/SafeSystemProxy.cs +++ b/src/ProtonVPN.Common/OS/Registry/SafeSystemProxy.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2020 Proton Technologies AG * * This file is part of ProtonVPN. @@ -42,8 +42,7 @@ public bool Enabled() } catch (Exception e) when (e.IsRegistryAccessException()) { - _logger.Error("Can not access system proxy settings"); - _logger.Error(e); + _logger.Error("Can't access system proxy settings", e); return false; } } diff --git a/src/ProtonVPN.Common/Vpn/VpnHost.cs b/src/ProtonVPN.Common/Vpn/VpnHost.cs index 9fc311121..3df4e0b30 100644 --- a/src/ProtonVPN.Common/Vpn/VpnHost.cs +++ b/src/ProtonVPN.Common/Vpn/VpnHost.cs @@ -70,18 +70,24 @@ private static void AssertIpAddressIsValid(string ip) public static bool operator ==(VpnHost h1, VpnHost h2) { - return AreEqual(h1, h2); + return h1.Equals(h2); } - private static bool AreEqual(VpnHost h1, VpnHost h2) + public override bool Equals(object o) { - return h1.Ip == h2.Ip && - (h1.Label == h2.Label || (string.IsNullOrEmpty(h1.Label) && string.IsNullOrEmpty(h2.Label))); + VpnHost vpnHost = (VpnHost)o; + return vpnHost != null && Ip == vpnHost.Ip && + (Label == vpnHost.Label || (string.IsNullOrEmpty(Label) && string.IsNullOrEmpty(vpnHost.Label))); + } + + public override int GetHashCode() + { + return Tuple.Create(Ip, Label).GetHashCode(); } public static bool operator !=(VpnHost h1, VpnHost h2) { - return !AreEqual(h1, h2); + return !h1.Equals(h2); } } } \ No newline at end of file diff --git a/src/ProtonVPN.Core/Profiles/SyncProfiles.cs b/src/ProtonVPN.Core/Profiles/SyncProfiles.cs index 3de76864e..f85516cbb 100644 --- a/src/ProtonVPN.Core/Profiles/SyncProfiles.cs +++ b/src/ProtonVPN.Core/Profiles/SyncProfiles.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2020 Proton Technologies AG * * This file is part of ProtonVPN. @@ -100,7 +100,7 @@ public async Task Create(Profile profile) { // Toggle profile on QuickConnectViewModel is not checking for profile name duplicates. // Ensure new profile name shown to the user is initially adjusted for uniqueness. - var p = _syncProfile.WithUniqueName(profile); + Profile p = _syncProfile.WithUniqueName(profile); await _profiles.Create(p); Sync(); @@ -169,7 +169,7 @@ await Retry(async () => private bool ContainsNotSyncedData() { - using (var cached = _cachedProfiles.ProfileData()) + using (CachedProfileData cached = _cachedProfiles.ProfileData()) { return cached.Sync.Any(); } @@ -180,11 +180,11 @@ private void OnSyncCompleted(object sender, TaskCompletedEventArgs e) if (e.Task.IsFaulted) { OnSyncStatusChanged(ProfileSyncStatus.Failed); - _logger.Error(e.Task.Exception); + _logger.Error("Task exception after syncing profiles.", e.Task.Exception); } else { - var status =_syncAction.Running + ProfileSyncStatus status =_syncAction.Running ? ProfileSyncStatus.InProgress : _syncFailed ? ProfileSyncStatus.Failed @@ -214,24 +214,25 @@ private async Task MergeApiToExternal() private async Task MergeApiToExternal(IReadOnlyList profiles) { - using (var cached = await _cachedProfiles.LockedProfileData()) + using (CachedProfileData cached = await _cachedProfiles.LockedProfileData()) { - var external = cached.External; + CachedProfileList external = cached.External; - foreach (var profile in profiles) + foreach (Profile profile in profiles) { - var candidate = profile.WithStatus(ProfileStatus.Synced); + Profile candidate = profile.WithStatus(ProfileStatus.Synced); candidate.ModifiedAt = DateTime.UtcNow; - var existing = external.FirstOrDefault(p => ProfileByExternalIdEqualityComparer.Equals(p, profile)); + Profile existing = external.FirstOrDefault(p => ProfileByExternalIdEqualityComparer.Equals(p, profile)); if (existing == null) { - var notSynced = cached.Local.FirstOrDefault(p => - p.Status == ProfileStatus.Created && - ProfileByEssentialPropertiesEqualityComparer.Equals(p, profile)) ?? - cached.Sync.FirstOrDefault(p => - p.Status == ProfileStatus.Created && - ProfileByEssentialPropertiesEqualityComparer.Equals(p, profile)); + Profile notSynced = + cached.Local.FirstOrDefault(p => + p.Status == ProfileStatus.Created && + ProfileByEssentialPropertiesEqualityComparer.Equals(p, profile)) ?? + cached.Sync.FirstOrDefault(p => + p.Status == ProfileStatus.Created && + ProfileByEssentialPropertiesEqualityComparer.Equals(p, profile)); if (notSynced != null) { @@ -268,16 +269,16 @@ private async Task MergeLocalToSync() return; // First checking existence of local to avoid unnecessary locking of profile data - using (var cached = _cachedProfiles.ProfileData()) + using (CachedProfileData cached = _cachedProfiles.ProfileData()) { if (!cached.Local.Any()) return; } - using (var cached = await _cachedProfiles.LockedProfileData()) + using (CachedProfileData cached = await _cachedProfiles.LockedProfileData()) { - var local = cached.Local; - var sync = cached.Sync; + CachedProfileList local = cached.Local; + CachedProfileList sync = cached.Sync; local.ForEach(p => sync.AddOrReplace(p.WithStatusMergedFrom(sync.Get(p)))); local.Clear(); @@ -290,10 +291,10 @@ private async Task MergeSyncToApi() if (_syncFailed) return; - using (var cached = _cachedProfiles.ProfileData()) + using (CachedProfileData cached = _cachedProfiles.ProfileData()) { var profiles = cached.Sync.OrderBy(p => p.ModifiedAt).ToList(); - foreach (var profile in profiles) + foreach (Profile profile in profiles) { await Sync(profile); @@ -318,7 +319,7 @@ private async Task Retry(Func action, Func retryRequired, int number { if (numberOfRetries < 1) throw new ArgumentOutOfRangeException(nameof(numberOfRetries)); - var i = numberOfRetries; + int i = numberOfRetries; do { await action(); diff --git a/src/ProtonVPN.Core/Storage/CachedSettings.cs b/src/ProtonVPN.Core/Storage/CachedSettings.cs index 93be99f73..1f177ec96 100644 --- a/src/ProtonVPN.Core/Storage/CachedSettings.cs +++ b/src/ProtonVPN.Core/Storage/CachedSettings.cs @@ -36,7 +36,7 @@ public T Get(string key) if (_cache.TryGetValue(key, out object cachedValue)) return cachedValue is T result ? result : default; - var value = _storage.Get(key); + T value = _storage.Get(key); _cache[key] = value; return value; } diff --git a/src/ProtonVPN.Core/Storage/EnumAsStringSettings.cs b/src/ProtonVPN.Core/Storage/EnumAsStringSettings.cs index 525dac2de..c45bc9336 100644 --- a/src/ProtonVPN.Core/Storage/EnumAsStringSettings.cs +++ b/src/ProtonVPN.Core/Storage/EnumAsStringSettings.cs @@ -32,10 +32,10 @@ public EnumAsStringSettings(ISettingsStorage storage) public T Get(string key) { - var toType = UnwrapNullable(typeof(T)); + Type toType = UnwrapNullable(typeof(T)); if (toType.IsEnum) { - var stringValue = _storage.Get(key); + string stringValue = _storage.Get(key); TryParseEnum(stringValue, out T result); return result; } @@ -45,12 +45,12 @@ public T Get(string key) public void Set(string key, T value) { - var fromType = UnwrapNullable(typeof(T)); + Type fromType = UnwrapNullable(typeof(T)); if (fromType.IsEnum) { if (value != null) { - var intValue = Convert.ChangeType(value, Enum.GetUnderlyingType(fromType)); + object intValue = Convert.ChangeType(value, Enum.GetUnderlyingType(fromType)); _storage.Set(key, intValue.ToString()); return; } diff --git a/src/ProtonVPN.Core/Storage/EnumerableAsJsonSettings.cs b/src/ProtonVPN.Core/Storage/EnumerableAsJsonSettings.cs index 3a2ce9e35..eed8431f0 100644 --- a/src/ProtonVPN.Core/Storage/EnumerableAsJsonSettings.cs +++ b/src/ProtonVPN.Core/Storage/EnumerableAsJsonSettings.cs @@ -47,7 +47,7 @@ public void Set(string key, T value) { if (IsEnumerableType(typeof(T))) { - var stringValue = string.Empty; + string stringValue = string.Empty; if (value != null) { stringValue = JsonConvert.SerializeObject(value); diff --git a/src/ProtonVPN.Service/VpnConnectionHandler.cs b/src/ProtonVPN.Service/VpnConnectionHandler.cs index 4332f8efc..064d1deae 100644 --- a/src/ProtonVPN.Service/VpnConnectionHandler.cs +++ b/src/ProtonVPN.Service/VpnConnectionHandler.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright (c) 2021 Proton Technologies AG * * This file is part of ProtonVPN. @@ -302,6 +302,7 @@ private VpnProtocolContract Map(VpnProtocol protocol) VpnProtocol.OpenVpnUdp => VpnProtocolContract.OpenVpnUdp, VpnProtocol.WireGuard => VpnProtocolContract.WireGuard, VpnProtocol.Smart => VpnProtocolContract.Smart, + _ => throw new NotImplementedException("VpnProtocol has an unknown value."), }; } diff --git a/test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs b/test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs index 642068ae0..741cefcf3 100644 --- a/test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs +++ b/test/ProtonVPN.Core.Test/Servers/ServerLoadUpdaterTest.cs @@ -74,7 +74,7 @@ public void Cleanup() public void ItShouldStartUpdatingServerLoads() { // Arrange - var sut = GetServerLoadUpdater(DateTime.Now.Subtract(TimeSpan.FromHours(1))); + ServerLoadUpdater sut = GetServerLoadUpdater(DateTime.Now.Subtract(TimeSpan.FromHours(1))); // Act sut.Handle(new WindowStateMessage(true)); @@ -87,7 +87,7 @@ public void ItShouldStartUpdatingServerLoads() public void ItShouldNotStartUpdatingServerLoads() { // Arrange - var sut = GetServerLoadUpdater(DateTime.Now); + ServerLoadUpdater sut = GetServerLoadUpdater(DateTime.Now); // Act sut.Handle(new WindowStateMessage(true)); From 94892fa19cc347ced1ebfb86c1284e516785fa0b Mon Sep 17 00:00:00 2001 From: Mindaugas Veblauskas Date: Wed, 10 Nov 2021 15:50:03 +0200 Subject: [PATCH 02/16] Add assign VPN connections modal [VPNWIN-925] --- src/ProtonVPN.App/Config/Url/ActiveUrls.cs | 1 + src/ProtonVPN.App/Config/Url/IActiveUrls.cs | 1 + src/ProtonVPN.App/Core/Bootstraper.cs | 9 +- .../Login/ViewModels/LoginViewModel.cs | 29 ++-- .../Modals/AssignVpnConnectionsModalView.xaml | 73 +++++++++ .../AssignVpnConnectionsModalView.xaml.cs | 29 ++++ .../AssignVpnConnectionsModalViewModel.cs | 43 ++++++ src/ProtonVPN.App/ProtonVPN.App.csproj | 8 + .../Configuration/Source/DefaultConfig.cs | 1 + .../Configuration/UrlConfig.cs | 3 + src/ProtonVPN.Core/Api/ApiClient.cs | 2 +- src/ProtonVPN.Core/Api/ResponseCodes.cs | 5 +- src/ProtonVPN.Core/Auth/AuthError.cs | 29 ++++ src/ProtonVPN.Core/Auth/AuthResult.cs | 65 ++++++++ src/ProtonVPN.Core/Auth/UserAuth.cs | 125 ++++++++-------- src/ProtonVPN.Core/Auth/UserValidator.cs | 29 ++-- src/ProtonVPN.Core/ProtonVPN.Core.csproj | 2 + .../Icons/AssignVpnConnections.xaml | 140 ++++++++++++++++++ .../Icons/AssignVpnConnections.xaml.cs | 29 ++++ .../ProtonVPN.Resource.csproj | 7 + .../Properties/Resources.Designer.cs | 47 +++++- .../Properties/Resources.resx | 15 ++ 22 files changed, 589 insertions(+), 103 deletions(-) create mode 100644 src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.xaml create mode 100644 src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.xaml.cs create mode 100644 src/ProtonVPN.App/Modals/AssignVpnConnectionsModalViewModel.cs create mode 100644 src/ProtonVPN.Core/Auth/AuthError.cs create mode 100644 src/ProtonVPN.Core/Auth/AuthResult.cs create mode 100644 src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml create mode 100644 src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml.cs diff --git a/src/ProtonVPN.App/Config/Url/ActiveUrls.cs b/src/ProtonVPN.App/Config/Url/ActiveUrls.cs index 1f210c816..ba85ad80d 100644 --- a/src/ProtonVPN.App/Config/Url/ActiveUrls.cs +++ b/src/ProtonVPN.App/Config/Url/ActiveUrls.cs @@ -65,6 +65,7 @@ public ActiveUrls(Common.Configuration.Config config, IOsProcesses processes) public IActiveUrl TorUrl => Url(_config.TorUrl); public IActiveUrl AboutSmartProtocolUrl => Url(_config.AboutSmartProtocolUrl); public IActiveUrl IncorrectSystemTimeArticleUrl => Url(_config.IncorrectSystemTimeArticleUrl); + public IActiveUrl AssignVpnConnectionsUrl => Url(_config.AssignVpnConnectionsUrl); private ActiveUrl Url(string url) { diff --git a/src/ProtonVPN.App/Config/Url/IActiveUrls.cs b/src/ProtonVPN.App/Config/Url/IActiveUrls.cs index 791d263fd..ceb5ca727 100644 --- a/src/ProtonVPN.App/Config/Url/IActiveUrls.cs +++ b/src/ProtonVPN.App/Config/Url/IActiveUrls.cs @@ -53,5 +53,6 @@ public interface IActiveUrls IActiveUrl TorUrl { get; } IActiveUrl AboutSmartProtocolUrl { get; } IActiveUrl IncorrectSystemTimeArticleUrl { get; } + IActiveUrl AssignVpnConnectionsUrl { get; } } } \ No newline at end of file diff --git a/src/ProtonVPN.App/Core/Bootstraper.cs b/src/ProtonVPN.App/Core/Bootstraper.cs index 5113567a4..1731464c9 100644 --- a/src/ProtonVPN.App/Core/Bootstraper.cs +++ b/src/ProtonVPN.App/Core/Bootstraper.cs @@ -200,19 +200,20 @@ private void LoadServersFromCache() private async Task IsUserValid() { + LoginViewModel loginViewModel = Resolve(); try { - ApiResponseResult validateResult = await Resolve().GetValidateResult(); - if (validateResult.Failure) + AuthResult result = await Resolve().GetValidateResult(); + if (result.Failure) { - Resolve().SetError(validateResult.Error); + loginViewModel.HandleAuthFailure(result); ShowLoginForm(); return false; } } catch (HttpRequestException ex) { - Resolve().SetError(ex.Message); + loginViewModel.HandleAuthFailure(AuthResult.Fail(ex.Message)); ShowLoginForm(); return false; } diff --git a/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs b/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs index 9cb5fb814..45abfa509 100644 --- a/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs +++ b/src/ProtonVPN.App/Login/ViewModels/LoginViewModel.cs @@ -18,7 +18,6 @@ */ using System.ComponentModel; -using System.Net; using System.Net.Http; using System.Security; using System.Threading.Tasks; @@ -29,7 +28,6 @@ using ProtonVPN.Common.Vpn; using ProtonVPN.Config.Url; using ProtonVPN.Core.Api; -using ProtonVPN.Core.Api.Contracts; using ProtonVPN.Core.Auth; using ProtonVPN.Core.Modals; using ProtonVPN.Core.MVVM; @@ -255,7 +253,7 @@ private async void LoginAction() LoginErrorViewModel.ClearError(); - ApiResponseResult loginResult = await _userAuth.LoginUserAsync(username, Password); + AuthResult loginResult = await _userAuth.LoginUserAsync(username, Password); await HandleLoginResultAsync(loginResult); } catch (HttpRequestException ex) @@ -271,31 +269,34 @@ private async void LoginAction() } } - private async Task HandleLoginResultAsync(ApiResponseResult loginResult) + private async Task HandleLoginResultAsync(AuthResult result) { - if (loginResult.Success) + if (result.Success) { AfterLogin(); } else { - await HandleLoginFailureAsync(loginResult); + await HandleLoginFailureAsync(result); } } - private async Task HandleLoginFailureAsync(ApiResponseResult loginResult) + public void HandleAuthFailure(AuthResult result) { - if (loginResult.Actions.IsNullOrEmpty()) // If Actions exist, it should be handled by ActionableFailureApiResultEventHandler + if (!result.Error.IsNullOrEmpty()) { - string error = loginResult.Error; - if (loginResult.StatusCode == HttpStatusCode.Unauthorized) - { - error = Translation.Get("Login_Error_msg_Unauthorized"); - } + LoginErrorViewModel.SetError(result.Error); + } - LoginErrorViewModel.SetError(error); + if (result.Value == AuthError.NoVpnAccess) + { + _modals.Show(); } + } + private async Task HandleLoginFailureAsync(AuthResult result) + { + HandleAuthFailure(result); Password = new SecureString(); ShowLoginForm(); await DisableGuestHole(); diff --git a/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.xaml b/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.xaml new file mode 100644 index 000000000..94d9fd8a3 --- /dev/null +++ b/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + - + \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/FailureView.xaml.cs b/src/ProtonVPN.App/BugReporting/Screens/FailureView.xaml.cs similarity index 90% rename from src/ProtonVPN.App/BugReporting/FailureView.xaml.cs rename to src/ProtonVPN.App/BugReporting/Screens/FailureView.xaml.cs index 724316931..71f46274e 100644 --- a/src/ProtonVPN.App/BugReporting/FailureView.xaml.cs +++ b/src/ProtonVPN.App/BugReporting/Screens/FailureView.xaml.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Proton Technologies AG + * Copyright (c) 2021 Proton Technologies AG * * This file is part of ProtonVPN. * @@ -17,7 +17,7 @@ * along with ProtonVPN. If not, see . */ -namespace ProtonVPN.BugReporting +namespace ProtonVPN.BugReporting.Screens { public partial class FailureView { @@ -26,4 +26,4 @@ public FailureView() InitializeComponent(); } } -} +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/FailureViewModel.cs b/src/ProtonVPN.App/BugReporting/Screens/FailureViewModel.cs similarity index 60% rename from src/ProtonVPN.App/BugReporting/FailureViewModel.cs rename to src/ProtonVPN.App/BugReporting/Screens/FailureViewModel.cs index 3192db755..69401418d 100644 --- a/src/ProtonVPN.App/BugReporting/FailureViewModel.cs +++ b/src/ProtonVPN.App/BugReporting/Screens/FailureViewModel.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Proton Technologies AG + * Copyright (c) 2021 Proton Technologies AG * * This file is part of ProtonVPN. * @@ -18,25 +18,29 @@ */ using System.Windows.Input; +using Caliburn.Micro; using GalaSoft.MvvmLight.Command; +using ProtonVPN.BugReporting.Actions; using ProtonVPN.Core.Modals; -using ProtonVPN.Core.MVVM; using ProtonVPN.Modals; -namespace ProtonVPN.BugReporting +namespace ProtonVPN.BugReporting.Screens { - public class FailureViewModel : ViewModel + public class FailureViewModel : Screen { + private string _error; private readonly IModals _modals; + private readonly IEventAggregator _eventAggregator; - public FailureViewModel(IModals modals) + public FailureViewModel(IModals modals, IEventAggregator eventAggregator) { _modals = modals; + _eventAggregator = eventAggregator; TroubleshootCommand = new RelayCommand(TroubleshootAction); + RetryCommand = new RelayCommand(RetryAction); + BackCommand = new RelayCommand(BackAction); } - private string _error; - public string Error { get => _error; @@ -44,10 +48,22 @@ public string Error } public ICommand TroubleshootCommand { get; set; } + public ICommand RetryCommand { get; set; } + public ICommand BackCommand { get; set; } private void TroubleshootAction() { _modals.Show(); } + + private void RetryAction() + { + _eventAggregator.PublishOnUIThread(new RetryAction()); + } + + private void BackAction() + { + _eventAggregator.PublishOnUIThread(new GoBackAfterFailureAction()); + } } -} +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/SendingView.xaml b/src/ProtonVPN.App/BugReporting/Screens/SendingView.xaml similarity index 76% rename from src/ProtonVPN.App/BugReporting/SendingView.xaml rename to src/ProtonVPN.App/BugReporting/Screens/SendingView.xaml index dc4a07602..768d343d7 100644 --- a/src/ProtonVPN.App/BugReporting/SendingView.xaml +++ b/src/ProtonVPN.App/BugReporting/Screens/SendingView.xaml @@ -1,5 +1,5 @@  -. xmlns:controls="clr-namespace:ProtonVPN.Resource.Controls;assembly=ProtonVPN.Resource" mc:Ignorable="d"> - - + + + Margin="0,20,0,0" + FontSize="16" + HorizontalAlignment="Center" + Text="{translations:Loc BugReport_msg_Sending}" /> - + \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/SendingView.xaml.cs b/src/ProtonVPN.App/BugReporting/Screens/SendingView.xaml.cs similarity index 90% rename from src/ProtonVPN.App/BugReporting/SendingView.xaml.cs rename to src/ProtonVPN.App/BugReporting/Screens/SendingView.xaml.cs index 54e0135fe..91d9054ad 100644 --- a/src/ProtonVPN.App/BugReporting/SendingView.xaml.cs +++ b/src/ProtonVPN.App/BugReporting/Screens/SendingView.xaml.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Proton Technologies AG + * Copyright (c) 2021 Proton Technologies AG * * This file is part of ProtonVPN. * @@ -17,7 +17,7 @@ * along with ProtonVPN. If not, see . */ -namespace ProtonVPN.BugReporting +namespace ProtonVPN.BugReporting.Screens { public partial class SendingView { @@ -26,4 +26,4 @@ public SendingView() InitializeComponent(); } } -} +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/SendingViewModel.cs b/src/ProtonVPN.App/BugReporting/Screens/SendingViewModel.cs similarity index 82% rename from src/ProtonVPN.App/BugReporting/SendingViewModel.cs rename to src/ProtonVPN.App/BugReporting/Screens/SendingViewModel.cs index 32e6e0a1a..a8d52c561 100644 --- a/src/ProtonVPN.App/BugReporting/SendingViewModel.cs +++ b/src/ProtonVPN.App/BugReporting/Screens/SendingViewModel.cs @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Proton Technologies AG + * Copyright (c) 2021 Proton Technologies AG * * This file is part of ProtonVPN. * @@ -17,11 +17,11 @@ * along with ProtonVPN. If not, see . */ -using ProtonVPN.Core.MVVM; +using Caliburn.Micro; -namespace ProtonVPN.BugReporting +namespace ProtonVPN.BugReporting.Screens { - public class SendingViewModel : ViewModel + public class SendingViewModel : Screen { } -} +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/SentView.xaml b/src/ProtonVPN.App/BugReporting/Screens/SentView.xaml similarity index 50% rename from src/ProtonVPN.App/BugReporting/SentView.xaml rename to src/ProtonVPN.App/BugReporting/Screens/SentView.xaml index 7503f2a57..8da6db96e 100644 --- a/src/ProtonVPN.App/BugReporting/SentView.xaml +++ b/src/ProtonVPN.App/BugReporting/Screens/SentView.xaml @@ -1,5 +1,5 @@  - + mc:Ignorable="d" + d:DataContext="{d:DesignInstance screens:SentViewModel}"> - - - - - - + \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionView.xaml.cs b/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionView.xaml.cs new file mode 100644 index 000000000..537c8213f --- /dev/null +++ b/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionView.xaml.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonVPN. If not, see . + */ + +namespace ProtonVPN.BugReporting.Steps +{ + public partial class CategorySelectionView + { + public CategorySelectionView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionViewModel.cs b/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionViewModel.cs new file mode 100644 index 000000000..3be4ce8d1 --- /dev/null +++ b/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionViewModel.cs @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonVPN. If not, see . + */ + +using System.Collections.Generic; +using System.Windows.Input; +using Caliburn.Micro; +using GalaSoft.MvvmLight.CommandWpf; +using ProtonVPN.BugReporting.Actions; +using ProtonVPN.Core.Api.Contracts.ReportAnIssue; +using ProtonVPN.Core.ReportAnIssue; + +namespace ProtonVPN.BugReporting.Steps +{ + public class CategorySelectionViewModel : Screen + { + private readonly IEventAggregator _eventAggregator; + private readonly IReportAnIssueFormDataProvider _reportAnIssueFormDataProvider; + + public CategorySelectionViewModel(IEventAggregator eventAggregator, IReportAnIssueFormDataProvider reportAnIssueFormDataProvider) + { + _eventAggregator = eventAggregator; + _reportAnIssueFormDataProvider = reportAnIssueFormDataProvider; + SelectCategoryCommand = new RelayCommand(SelectCategoryAction); + } + + public List Categories => _reportAnIssueFormDataProvider.GetCategories(); + + public ICommand SelectCategoryCommand { get; } + + public void SelectCategoryAction(string category) + { + _eventAggregator.PublishOnUIThread(new SelectCategoryAction(category)); + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml b/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml new file mode 100644 index 000000000..9c78311fe --- /dev/null +++ b/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml.cs b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml.cs new file mode 100644 index 000000000..8f930e093 --- /dev/null +++ b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml.cs @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonVPN. If not, see . + */ + +namespace ProtonVPN.BugReporting.Steps +{ + public partial class StepsContainerView + { + public StepsContainerView() + { + InitializeComponent(); + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/Steps/StepsContainerViewModel.cs b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerViewModel.cs new file mode 100644 index 000000000..c0a226a40 --- /dev/null +++ b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerViewModel.cs @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2021 Proton Technologies AG + * + * This file is part of ProtonVPN. + * + * ProtonVPN is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * ProtonVPN is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ProtonVPN. If not, see . + */ + +using System.Windows.Input; +using Caliburn.Micro; +using GalaSoft.MvvmLight.Command; +using ProtonVPN.About; +using ProtonVPN.BugReporting.Actions; +using ProtonVPN.Common.Extensions; +using ProtonVPN.Core.Auth; +using ProtonVPN.Core.ReportAnIssue; + +namespace ProtonVPN.BugReporting.Steps +{ + public class StepsContainerViewModel : Screen, + ILogoutAware, + IHandle, + IHandle, + IHandle + { + private readonly IEventAggregator _eventAggregator; + private readonly IReportAnIssueFormDataProvider _reportAnIssueFormDataProvider; + private readonly CategorySelectionViewModel _categorySelectionViewModel; + private readonly SolutionsViewModel _solutionsViewModel; + private readonly FormViewModel _formViewModel; + + private Screen _screenViewModel; + private int _step = 1; + private string _category; + + public ICommand GoBackCommand { get; } + + public bool IsToShowBackButton => Step > 1; + + public Screen ScreenViewModel + { + get => _screenViewModel; + set => Set(ref _screenViewModel, value); + } + + public UpdateViewModel UpdateViewModel { get; } + + public int Step + { + get => _step; + set + { + Set(ref _step, value); + ScreenViewModel = GetScreen(); + NotifyOfPropertyChange(nameof(IsToShowBackButton)); + } + } + + public StepsContainerViewModel(IEventAggregator eventAggregator, + IReportAnIssueFormDataProvider reportAnIssueFormDataProvider, + UpdateViewModel updateViewModel, + CategorySelectionViewModel categorySelectionViewModel, + SolutionsViewModel solutionsViewModel, + FormViewModel formViewModel) + { + eventAggregator.Subscribe(this); + + _eventAggregator = eventAggregator; + _reportAnIssueFormDataProvider = reportAnIssueFormDataProvider; + _categorySelectionViewModel = categorySelectionViewModel; + _solutionsViewModel = solutionsViewModel; + _formViewModel = formViewModel; + + UpdateViewModel = updateViewModel; + ScreenViewModel = categorySelectionViewModel; + GoBackCommand = new RelayCommand(GoBackAction); + } + + private Screen GetScreen() + { + switch (Step) + { + case 1: + return _categorySelectionViewModel; + case 2: + return _solutionsViewModel; + case 3: + return _formViewModel; + default: + return _categorySelectionViewModel; + } + } + + public void OnUserLoggedOut() + { + ShowFirstStep(); + } + + public void Handle(FormStateChange message) + { + if (message.State == FormState.Sent) + { + ShowFirstStep(); + } + } + + public void Handle(SelectCategoryAction message) + { + _category = message.Category; + + if (HasSuggestions(message.Category)) + { + Step = 2; + } + else + { + _eventAggregator.PublishOnUIThread(new FillTheFormAction(message.Category)); + } + } + + private bool HasSuggestions(string category) + { + return !_reportAnIssueFormDataProvider.GetSuggestions(category).IsNullOrEmpty(); + } + + public void Handle(FillTheFormAction message) + { + Step = 3; + } + + private void GoBackAction() + { + switch (Step) + { + case <= 1: + return; + case 3 when !HasSuggestions(_category): + Step = 1; + break; + default: + Step--; + break; + } + } + + private void ShowFirstStep() + { + Step = 1; + } + } +} \ No newline at end of file diff --git a/src/ProtonVPN.App/Core/AppSettings.cs b/src/ProtonVPN.App/Core/AppSettings.cs index 451b72412..6426adb6a 100644 --- a/src/ProtonVPN.App/Core/AppSettings.cs +++ b/src/ProtonVPN.App/Core/AppSettings.cs @@ -27,7 +27,7 @@ using ProtonVPN.Common.Extensions; using ProtonVPN.Common.KillSwitch; using ProtonVPN.Common.Networking; -using ProtonVPN.Core.Announcements; +using ProtonVPN.Core.Api.Contracts.ReportAnIssue; using ProtonVPN.Core.Auth; using ProtonVPN.Core.Models; using ProtonVPN.Core.Native.Structures; @@ -37,6 +37,7 @@ using ProtonVPN.Core.Settings.Contracts; using ProtonVPN.Core.Storage; using ProtonVPN.Settings; +using Announcement = ProtonVPN.Core.Announcements.Announcement; namespace ProtonVPN.Core { @@ -45,7 +46,7 @@ internal class AppSettings : IAppSettings, INotifyPropertyChanged, ILoggedInAwar private readonly ISettingsStorage _storage; private readonly UserSettings _userSettings; private readonly Common.Configuration.Config _config; - private readonly HashSet _accessedPerUserProperties = new HashSet(); + private readonly HashSet _accessedPerUserProperties = new(); public AppSettings(ISettingsStorage storage, UserSettings userSettings, Common.Configuration.Config config) { @@ -68,6 +69,12 @@ public IReadOnlyList Announcements set => SetPerUser(value); } + public List ReportAnIssueFormData + { + get => GetPerUser>() ?? new List(); + set => SetPerUser(value); + } + public DateTime ProfileChangesSyncedAt { get => GetPerUser(); diff --git a/src/ProtonVPN.App/Core/Bootstraper.cs b/src/ProtonVPN.App/Core/Bootstraper.cs index 1731464c9..dd6cf34c3 100644 --- a/src/ProtonVPN.App/Core/Bootstraper.cs +++ b/src/ProtonVPN.App/Core/Bootstraper.cs @@ -49,6 +49,7 @@ using ProtonVPN.Core.Network; using ProtonVPN.Core.OS.Net; using ProtonVPN.Core.Profiles; +using ProtonVPN.Core.ReportAnIssue; using ProtonVPN.Core.Servers; using ProtonVPN.Core.Service; using ProtonVPN.Core.Service.Settings; @@ -519,6 +520,7 @@ private async Task SwitchToAppWindow(bool autoLogin) Resolve().CheckForInsecureWiFi(); await Resolve().StoreLatestEvent(); Resolve().Start(); + await Resolve().FetchData(); } private void LoadViewModels() diff --git a/src/ProtonVPN.App/Core/Ioc/AppModule.cs b/src/ProtonVPN.App/Core/Ioc/AppModule.cs index c49bd33ec..a886fefd5 100644 --- a/src/ProtonVPN.App/Core/Ioc/AppModule.cs +++ b/src/ProtonVPN.App/Core/Ioc/AppModule.cs @@ -23,8 +23,6 @@ using Caliburn.Micro; using ProtonVPN.About; using ProtonVPN.Account; -using ProtonVPN.BugReporting; -using ProtonVPN.BugReporting.Diagnostic; using ProtonVPN.Common.Configuration; using ProtonVPN.Common.Logging; using ProtonVPN.Common.OS.Processes; @@ -279,7 +277,6 @@ protected override void Load(ContainerBuilder builder) builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().SingleInstance(); - builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.Register(c => new VpnInfoChecker( c.Resolve(), @@ -287,7 +284,6 @@ protected override void Load(ContainerBuilder builder) c.Resolve(), c.Resolve(), c.Resolve())).SingleInstance(); - builder.RegisterType().As().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().AsImplementedInterfaces().AsSelf().SingleInstance(); builder.RegisterType().As().SingleInstance(); diff --git a/src/ProtonVPN.App/Core/Ioc/CoreModule.cs b/src/ProtonVPN.App/Core/Ioc/CoreModule.cs index 1bd11af89..16dc31c7b 100644 --- a/src/ProtonVPN.App/Core/Ioc/CoreModule.cs +++ b/src/ProtonVPN.App/Core/Ioc/CoreModule.cs @@ -47,6 +47,7 @@ using ProtonVPN.Core.OS.Net; using ProtonVPN.Core.OS.Net.Dns; using ProtonVPN.Core.OS.Net.DoH; +using ProtonVPN.Core.ReportAnIssue; using ProtonVPN.Core.Servers; using ProtonVPN.Core.Service; using ProtonVPN.Core.Settings; @@ -57,7 +58,6 @@ using ProtonVPN.HumanVerification; using ProtonVPN.Modals.ApiActions; using ProtonVPN.Settings; -using ProtonVPN.Translations; using ProtonVPN.Vpn; using Module = Autofac.Module; @@ -283,6 +283,8 @@ protected override void Load(ContainerBuilder builder) builder.Register(c => new NtpClient(c.Resolve().NtpServerUrl, c.Resolve())) .As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); } } } \ No newline at end of file diff --git a/src/ProtonVPN.App/Properties/Settings.Designer.cs b/src/ProtonVPN.App/Properties/Settings.Designer.cs index ab14513ae..7350f4cd4 100644 --- a/src/ProtonVPN.App/Properties/Settings.Designer.cs +++ b/src/ProtonVPN.App/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace ProtonVPN.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.10.0.0")] public sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); @@ -1444,5 +1444,17 @@ public bool HardwareAccelerationEnabled { this["HardwareAccelerationEnabled"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("")] + public string UserReportAnIssueFormData { + get { + return ((string)(this["UserReportAnIssueFormData"])); + } + set { + this["UserReportAnIssueFormData"] = value; + } + } } } diff --git a/src/ProtonVPN.App/Properties/Settings.settings b/src/ProtonVPN.App/Properties/Settings.settings index 0d2d6c552..0972ae55e 100644 --- a/src/ProtonVPN.App/Properties/Settings.settings +++ b/src/ProtonVPN.App/Properties/Settings.settings @@ -359,5 +359,8 @@ True + + + \ No newline at end of file diff --git a/src/ProtonVPN.App/ProtonVPN.App.csproj b/src/ProtonVPN.App/ProtonVPN.App.csproj index 55c2d046c..1a23a24c0 100644 --- a/src/ProtonVPN.App/ProtonVPN.App.csproj +++ b/src/ProtonVPN.App/ProtonVPN.App.csproj @@ -116,18 +116,48 @@ + + + + + + + - - + + + + + + + + + + + FormView.xaml + + + SolutionsView.xaml + + + CategorySelectionView.xaml + + + + + + StepsContainerView.xaml + + @@ -332,26 +362,22 @@ - + FailureView.xaml - - - Form.xaml - - + ReportBugModalView.xaml - + SendingView.xaml - - + + SentView.xaml - + @@ -878,6 +904,22 @@ + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + MSBuild:Compile Designer @@ -1231,11 +1273,7 @@ AppWindow.xaml Code - - Designer - MSBuild:Compile - - + Designer MSBuild:Compile @@ -1243,11 +1281,11 @@ Designer MSBuild:Compile - + Designer MSBuild:Compile - + Designer MSBuild:Compile @@ -2039,6 +2077,9 @@ 4.7.0.9 + + 2.1.1 + 7.0.0 @@ -2066,6 +2107,9 @@ 1.2.0 + + 1.1.0 + diff --git a/src/ProtonVPN.App/Resources/Assets/Styles/ToggleSwitch.xaml b/src/ProtonVPN.App/Resources/Assets/Styles/ToggleSwitch.xaml index 1dcd40cf2..9ab691a1c 100644 --- a/src/ProtonVPN.App/Resources/Assets/Styles/ToggleSwitch.xaml +++ b/src/ProtonVPN.App/Resources/Assets/Styles/ToggleSwitch.xaml @@ -1,5 +1,5 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + . Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Foreground="White" Padding="8" + adorners:Watermark.TextStyle="{StaticResource AdornerTextStyle}" adorners:Watermark.Text="{Binding Placeholder}"> - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml index 5e7e44167..b68a83b0d 100644 --- a/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml +++ b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml @@ -32,10 +32,10 @@ along with ProtonVPN. If not, see . - + - +