diff --git a/COPYING.md b/COPYING.md
index 116a7b5b1..16efae2cc 100644
--- a/COPYING.md
+++ b/COPYING.md
@@ -52,6 +52,8 @@ ProtonVPN Windows app includes the following 3rd party software:
| [The MIT License](https://github.com/davideicardi/DynamicExpresso/blob/master/LICENSE).
* [FontAwesome.WPF](https://github.com/charri/Font-Awesome-WPF/) by charri
| [The MIT License](https://github.com/charri/Font-Awesome-WPF/blob/master/LICENSE).
+* [Gu.Wpf.Adorners](https://github.com/GuOrg/Gu.Wpf.Adorners) by Johan Larsson
+ | [The MIT License](https://github.com/GuOrg/Gu.Wpf.Adorners/blob/master/LICENSE).
* [MvvmLightLibsStd10](http://www.mvvmlight.net/) by Laurent Bugnion (GalaSoft)
| [The MIT License](https://github.com/lbugnion/mvvmlight/blob/master/LICENSE).
* [Newtonsoft.Json](https://www.newtonsoft.com/json) by James Newton-King
@@ -83,4 +85,6 @@ ProtonVPN Windows app includes the following 3rd party software:
* [System.Buffers](https://dot.net) by Microsoft
| [The MIT License](https://github.com/dotnet/corefx/blob/master/LICENSE.TXT).
* [System.Collections.Immutable](https://dot.net) by Microsoft
- | [The MIT License](https://github.com/dotnet/corefx/blob/master/LICENSE.TXT).
\ No newline at end of file
+ | [The MIT License](https://github.com/dotnet/corefx/blob/master/LICENSE.TXT).
+* [WpfScreenHelper](https://github.com/micdenny/WpfScreenHelper) by Michael Denny
+ | [The MIT License](https://github.com/micdenny/WpfScreenHelper/blob/master/LICENSE).
\ No newline at end of file
diff --git a/Setup/ProtonVPN.aip b/Setup/ProtonVPN.aip
index 0b34868d2..365d8b964 100644
--- a/Setup/ProtonVPN.aip
+++ b/Setup/ProtonVPN.aip
@@ -103,6 +103,7 @@
+
@@ -165,6 +166,7 @@
+
@@ -285,6 +287,8 @@
+
+
@@ -658,6 +662,8 @@
+
+
diff --git a/src/GlobalAssemblyInfo.cs b/src/GlobalAssemblyInfo.cs
index 011760650..51445b364 100644
--- a/src/GlobalAssemblyInfo.cs
+++ b/src/GlobalAssemblyInfo.cs
@@ -9,11 +9,11 @@
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ProtonVPN")]
-[assembly: AssemblyCopyright("Copyright © 2021 Proton Technologies AG")]
+[assembly: AssemblyCopyright("Copyright © 2021 Proton Technologies AG")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
-[assembly: AssemblyVersion("1.24.2.0")]
-[assembly: AssemblyFileVersion("1.24.2.0")]
+[assembly: AssemblyVersion("1.25.0.0")]
+[assembly: AssemblyFileVersion("1.25.0.0")]
[assembly: ComVisible(false)]
[assembly: AssemblyInformationalVersion("$AssemblyVersion")]
\ No newline at end of file
diff --git a/src/ProtonVPN.App/About/UpdateViewModel.cs b/src/ProtonVPN.App/About/UpdateViewModel.cs
index dbb9a7c83..52f522bde 100644
--- a/src/ProtonVPN.App/About/UpdateViewModel.cs
+++ b/src/ProtonVPN.App/About/UpdateViewModel.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -23,14 +23,13 @@
using System.Windows;
using System.Windows.Input;
using GalaSoft.MvvmLight.Command;
-using ProtonVPN.BugReporting.Diagnostic;
using ProtonVPN.Common.KillSwitch;
using ProtonVPN.Common.OS.Processes;
using ProtonVPN.Common.Vpn;
using ProtonVPN.Core.Modals;
using ProtonVPN.Core.MVVM;
+using ProtonVPN.Core.OS;
using ProtonVPN.Core.Service.Settings;
-using ProtonVPN.Core.Service.Vpn;
using ProtonVPN.Core.Settings;
using ProtonVPN.Core.Update;
using ProtonVPN.Core.Vpn;
@@ -45,7 +44,6 @@ public class UpdateViewModel : ViewModel, IUpdateStateAware, IVpnStateAware
private readonly IOsProcesses _osProcesses;
private readonly IModals _modals;
private readonly IAppSettings _appSettings;
- private readonly IVpnServiceManager _vpnServiceManager;
private readonly ISystemState _systemState;
private readonly ISettingsServiceClientManager _settingsServiceClientManager;
@@ -57,7 +55,6 @@ public UpdateViewModel(
IOsProcesses osProcesses,
IModals modals,
IAppSettings appSettings,
- IVpnServiceManager vpnServiceManager,
ISystemState systemState,
ISettingsServiceClientManager settingsServiceClientManager)
{
@@ -65,7 +62,6 @@ public UpdateViewModel(
_osProcesses = osProcesses;
_modals = modals;
_appSettings = appSettings;
- _vpnServiceManager = vpnServiceManager;
_systemState = systemState;
_settingsServiceClientManager = settingsServiceClientManager;
diff --git a/src/ProtonVPN.App/App.config b/src/ProtonVPN.App/App.config
index ec7cfd678..a18a4cc36 100644
--- a/src/ProtonVPN.App/App.config
+++ b/src/ProtonVPN.App/App.config
@@ -361,6 +361,9 @@
True
+
+
+
diff --git a/src/ProtonVPN.App/App.xaml.cs b/src/ProtonVPN.App/App.xaml.cs
index aa5c30673..05cd14e8c 100644
--- a/src/ProtonVPN.App/App.xaml.cs
+++ b/src/ProtonVPN.App/App.xaml.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -25,7 +25,6 @@
using System.Threading.Tasks;
using System.Windows;
using Microsoft.Toolkit.Uwp.Notifications;
-using ProtonVPN.Common.Cli;
using ProtonVPN.Common.Configuration;
using ProtonVPN.Common.CrashReporting;
using ProtonVPN.Common.Extensions;
@@ -83,8 +82,6 @@ private static async Task Run(string[] args)
_bootstrapper = new Bootstrapper(args);
_bootstrapper.Initialize();
- HandleIntentionalCrash(app, args);
-
app.Run();
}
}
@@ -150,15 +147,6 @@ private static Common.Configuration.Config GetConfig()
return config;
}
- private static void HandleIntentionalCrash(Application app, string[] args)
- {
- var option = new CommandLineOption("crash", args);
- if (!option.Exists())
- return;
-
- app.Deactivated += (sender, ea) => throw new StackOverflowException("Intentional crash test");
- }
-
private static void SetDllDirectories()
{
Kernel32.SetDefaultDllDirectories(Kernel32.SetDefaultDllDirectoriesFlags.LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
diff --git a/src/ProtonVPN.App/BugReporting/Actions/CategoryAction.cs b/src/ProtonVPN.App/BugReporting/Actions/CategoryAction.cs
new file mode 100644
index 000000000..a10b86b23
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/CategoryAction.cs
@@ -0,0 +1,31 @@
+/*
+ * 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.Actions
+{
+ public abstract class CategoryAction
+ {
+ public string Category { get; }
+
+ protected CategoryAction(string category)
+ {
+ Category = category;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/FillTheFormAction.cs b/src/ProtonVPN.App/BugReporting/Actions/FillTheFormAction.cs
new file mode 100644
index 000000000..0564e5241
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/FillTheFormAction.cs
@@ -0,0 +1,28 @@
+/*
+ * 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.Actions
+{
+ public class FillTheFormAction : CategoryAction
+ {
+ public FillTheFormAction(string category) : base(category)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/FinishReportAction.cs b/src/ProtonVPN.App/BugReporting/Actions/FinishReportAction.cs
new file mode 100644
index 000000000..82afe7fc7
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/FinishReportAction.cs
@@ -0,0 +1,26 @@
+/*
+ * 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.Actions
+{
+ public class FinishReportAction
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/FormStateChange.cs b/src/ProtonVPN.App/BugReporting/Actions/FormStateChange.cs
new file mode 100644
index 000000000..098cc4111
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/FormStateChange.cs
@@ -0,0 +1,31 @@
+/*
+ * 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.Actions
+{
+ public class FormStateChange
+ {
+ public FormStateChange(FormState state)
+ {
+ State = state;
+ }
+
+ public FormState State { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/GoBackAfterFailureAction.cs b/src/ProtonVPN.App/BugReporting/Actions/GoBackAfterFailureAction.cs
new file mode 100644
index 000000000..7ef2329ce
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/GoBackAfterFailureAction.cs
@@ -0,0 +1,25 @@
+/*
+ * 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.Actions
+{
+ public class GoBackAfterFailureAction
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/RetryAction.cs b/src/ProtonVPN.App/BugReporting/Actions/RetryAction.cs
new file mode 100644
index 000000000..a3b2b47e6
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/RetryAction.cs
@@ -0,0 +1,25 @@
+/*
+ * 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.Actions
+{
+ public class RetryAction
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/SelectCategoryAction.cs b/src/ProtonVPN.App/BugReporting/Actions/SelectCategoryAction.cs
new file mode 100644
index 000000000..185997586
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/SelectCategoryAction.cs
@@ -0,0 +1,28 @@
+/*
+ * 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.Actions
+{
+ public class SelectCategoryAction : CategoryAction
+ {
+ public SelectCategoryAction(string category) : base(category)
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Actions/SendReportAction.cs b/src/ProtonVPN.App/BugReporting/Actions/SendReportAction.cs
new file mode 100644
index 000000000..c7b782648
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Actions/SendReportAction.cs
@@ -0,0 +1,38 @@
+/*
+ * 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 ProtonVPN.BugReporting.FormElements;
+
+namespace ProtonVPN.BugReporting.Actions
+{
+ public class SendReportAction
+ {
+ public SendReportAction(string category, IList formElements, bool sendErrorLogs)
+ {
+ Category = category;
+ FormElements = formElements;
+ SendErrorLogs = sendErrorLogs;
+ }
+
+ public string Category;
+ public IList FormElements;
+ public bool SendErrorLogs;
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/BugReport.cs b/src/ProtonVPN.App/BugReporting/BugReport.cs
index c71c40d30..f3f489e8d 100644
--- a/src/ProtonVPN.App/BugReporting/BugReport.cs
+++ b/src/ProtonVPN.App/BugReporting/BugReport.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
diff --git a/src/ProtonVPN.App/BugReporting/BugReportingModule.cs b/src/ProtonVPN.App/BugReporting/BugReportingModule.cs
index e89c863ca..b80188963 100644
--- a/src/ProtonVPN.App/BugReporting/BugReportingModule.cs
+++ b/src/ProtonVPN.App/BugReporting/BugReportingModule.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -20,10 +20,12 @@
using Autofac;
using ProtonVPN.BugReporting.Attachments.Source;
using ProtonVPN.BugReporting.Diagnostic;
+using ProtonVPN.BugReporting.FormElements;
using ProtonVPN.Common.Helpers;
using ProtonVPN.Common.Logging;
using ProtonVPN.Common.OS.Net.NetworkInterface;
using ProtonVPN.Common.OS.Processes;
+using ProtonVPN.Core.OS;
namespace ProtonVPN.BugReporting
{
@@ -37,8 +39,8 @@ protected override void Load(ContainerBuilder builder)
builder.Register(c =>
{
- var appConfig = c.Resolve();
- var logger = c.Resolve();
+ Common.Configuration.Config appConfig = c.Resolve();
+ ILogger logger = c.Resolve();
return new Attachments.Attachments(
new FilesToAttachments(
@@ -86,6 +88,8 @@ protected override void Load(ContainerBuilder builder)
.SingleInstance();
builder.RegisterType().SingleInstance();
+ builder.RegisterType().As().SingleInstance();
+ builder.RegisterType().As().SingleInstance();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs b/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs
index 2b37330fc..06e7ff5c6 100644
--- a/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs
+++ b/src/ProtonVPN.App/BugReporting/Diagnostic/NetworkAdapterLog.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -18,6 +18,7 @@
*/
using System.IO;
+using ProtonVPN.Common.Extensions;
using ProtonVPN.Common.OS.Net.NetworkInterface;
namespace ProtonVPN.BugReporting.Diagnostic
@@ -40,9 +41,9 @@ private string Content
{
get
{
- var str = string.Empty;
- var interfaces = _networkInterfaces.GetInterfaces();
- foreach (var networkInterface in interfaces)
+ string str = string.Empty;
+ INetworkInterface[] interfaces = _networkInterfaces.GetInterfaces();
+ foreach (INetworkInterface networkInterface in interfaces)
{
str += GetInterfaceDetails(networkInterface);
}
@@ -53,11 +54,9 @@ private string Content
private string GetInterfaceDetails(INetworkInterface networkInterface)
{
- var active = networkInterface.IsActive ? "true" : "false";
-
return $"Name: {networkInterface.Name}\n" +
$"Description: {networkInterface.Description}\n" +
- $"Active: {active}\n\n";
+ $"Active: {networkInterface.IsActive.ToYesNoString()}\n\n";
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/EmailValidator.cs b/src/ProtonVPN.App/BugReporting/EmailValidator.cs
index 261bc9d47..d53439da2 100644
--- a/src/ProtonVPN.App/BugReporting/EmailValidator.cs
+++ b/src/ProtonVPN.App/BugReporting/EmailValidator.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -28,7 +28,9 @@ internal class EmailValidator
public static bool IsValid(string email)
{
if (string.IsNullOrWhiteSpace(email))
+ {
return false;
+ }
try
{
@@ -37,8 +39,8 @@ public static bool IsValid(string email)
string DomainMapper(Match match)
{
- var idn = new IdnMapping();
- var domainName = idn.GetAscii(match.Groups[2].Value);
+ IdnMapping idn = new();
+ string domainName = idn.GetAscii(match.Groups[2].Value);
return match.Groups[1].Value + domainName;
}
@@ -64,4 +66,4 @@ string DomainMapper(Match match)
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Form.xaml b/src/ProtonVPN.App/BugReporting/Form.xaml
deleted file mode 100644
index 2bd22256c..000000000
--- a/src/ProtonVPN.App/BugReporting/Form.xaml
+++ /dev/null
@@ -1,199 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/EmailInput.cs b/src/ProtonVPN.App/BugReporting/FormElements/EmailInput.cs
new file mode 100644
index 000000000..8fe6143b9
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/EmailInput.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.FormElements
+{
+ public class EmailInput : SingleLineTextInput
+ {
+ public override bool IsValid()
+ {
+ return base.IsValid() && EmailValidator.IsValid(Value);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/FormElement.cs b/src/ProtonVPN.App/BugReporting/FormElements/FormElement.cs
new file mode 100644
index 000000000..148507c9b
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/FormElement.cs
@@ -0,0 +1,44 @@
+/*
+ * 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 ProtonVPN.Common.Extensions;
+using ProtonVPN.Core.MVVM;
+
+namespace ProtonVPN.BugReporting.FormElements
+{
+ public abstract class FormElement : ViewModel
+ {
+ private string _value;
+ public string Value
+ {
+ get => _value;
+ set => Set(ref _value, value);
+ }
+
+ public string Label { get; set; }
+ public string SubmitLabel { get; set; }
+ public string Placeholder { get; set; }
+ public bool IsMandatory { get; set; }
+
+ public virtual bool IsValid()
+ {
+ return !IsMandatory || !Value.IsNullOrEmpty() && !Value.Trim().IsNullOrEmpty();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/FormElementBuilder.cs b/src/ProtonVPN.App/BugReporting/FormElements/FormElementBuilder.cs
new file mode 100644
index 000000000..1d9a39d86
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/FormElementBuilder.cs
@@ -0,0 +1,95 @@
+/*
+ * 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.Linq;
+using ProtonVPN.Common.Logging;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
+using ProtonVPN.Core.ReportAnIssue;
+using ProtonVPN.Translations;
+
+namespace ProtonVPN.BugReporting.FormElements
+{
+ public class FormElementBuilder : IFormElementBuilder
+ {
+ private readonly ILogger _logger;
+ private readonly IReportAnIssueFormDataProvider _reportAnIssueFormDataProvider;
+
+ public FormElementBuilder(ILogger logger, IReportAnIssueFormDataProvider reportAnIssueFormDataProvider)
+ {
+ _logger = logger;
+ _reportAnIssueFormDataProvider = reportAnIssueFormDataProvider;
+ }
+
+ public List GetFormElements(string categorySubmitName)
+ {
+ IList inputs = _reportAnIssueFormDataProvider.GetInputs(categorySubmitName);
+ List formElements = new() { GetEmailField() };
+ formElements.AddRange(inputs.Select(MapField).Where(element => element != null));
+ return formElements;
+ }
+
+ private FormElement GetEmailField()
+ {
+ return new EmailInput
+ {
+ SubmitLabel = "email",
+ Placeholder = Translation.Get("BugReport_lbl_EmailPlaceholder"),
+ Label = Translation.Get("BugReport_lbl_Email"),
+ IsMandatory = true
+ };
+ }
+
+ private FormElement MapField(IssueInput input)
+ {
+ switch (input.Type)
+ {
+ case InputType.SingleLineInput:
+ return CreateSingleLineTextField(input);
+ case InputType.MultiLineInput:
+ return CreateMultiLineTextField(input);
+ default:
+ _logger.Info($"[FormElementBuilder] Unknown input type {input.Type}. This field won't be added to the form.");
+ return null;
+ }
+ }
+
+ private FormElement CreateSingleLineTextField(IssueInput input)
+ {
+ return new SingleLineTextInput
+ {
+ Label = input.Label,
+ Placeholder = input.Placeholder,
+ SubmitLabel = input.SubmitLabel,
+ IsMandatory = input.IsMandatory
+ };
+ }
+
+ private FormElement CreateMultiLineTextField(IssueInput input)
+ {
+ return new MultiLineTextInput
+ {
+ Label = input.Label,
+ Placeholder = input.Placeholder,
+ SubmitLabel = input.SubmitLabel,
+ IsMandatory = input.IsMandatory
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/FormElementExtensions.cs b/src/ProtonVPN.App/BugReporting/FormElements/FormElementExtensions.cs
new file mode 100644
index 000000000..75ea61713
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/FormElementExtensions.cs
@@ -0,0 +1,37 @@
+/*
+ * 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.Linq;
+
+namespace ProtonVPN.BugReporting.FormElements
+{
+ public static class FormElementExtensions
+ {
+ public static FormElement GetEmailField(this IList elements)
+ {
+ return elements.FirstOrDefault(IsEmailField);
+ }
+
+ public static bool IsEmailField(this FormElement element)
+ {
+ return element is EmailInput;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/IFormElementBuilder.cs b/src/ProtonVPN.App/BugReporting/FormElements/IFormElementBuilder.cs
new file mode 100644
index 000000000..f0a83dab7
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/IFormElementBuilder.cs
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+namespace ProtonVPN.BugReporting.FormElements
+{
+ public interface IFormElementBuilder
+ {
+ List GetFormElements(string categorySubmitName);
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/MultiLineTextInput.cs b/src/ProtonVPN.App/BugReporting/FormElements/MultiLineTextInput.cs
new file mode 100644
index 000000000..e0f45f863
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/MultiLineTextInput.cs
@@ -0,0 +1,25 @@
+/*
+ * 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.FormElements
+{
+ public class MultiLineTextInput : FormElement
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormElements/SingleLineTextInput.cs b/src/ProtonVPN.App/BugReporting/FormElements/SingleLineTextInput.cs
new file mode 100644
index 000000000..9d964a365
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/FormElements/SingleLineTextInput.cs
@@ -0,0 +1,25 @@
+/*
+ * 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.FormElements
+{
+ public class SingleLineTextInput : FormElement
+ {
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/SentViewModel.cs b/src/ProtonVPN.App/BugReporting/FormState.cs
similarity index 83%
rename from src/ProtonVPN.App/BugReporting/SentViewModel.cs
rename to src/ProtonVPN.App/BugReporting/FormState.cs
index 9bf5bdabc..953fd51e0 100644
--- a/src/ProtonVPN.App/BugReporting/SentViewModel.cs
+++ b/src/ProtonVPN.App/BugReporting/FormState.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,13 @@
* along with ProtonVPN. If not, see .
*/
-using ProtonVPN.Core.MVVM;
-
namespace ProtonVPN.BugReporting
{
- public class SentViewModel : ViewModel
+ public enum FormState
{
+ Editing,
+ Sending,
+ FailedToSend,
+ Sent
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FormViewModel.cs b/src/ProtonVPN.App/BugReporting/FormViewModel.cs
deleted file mode 100644
index 27182f49e..000000000
--- a/src/ProtonVPN.App/BugReporting/FormViewModel.cs
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (c) 2020 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.Runtime.CompilerServices;
-using ProtonVPN.Core.Auth;
-using ProtonVPN.Core.Models;
-using ProtonVPN.Core.Settings;
-using ProtonVPN.Core.User;
-using ProtonVPN.Translations;
-using ProtonVPN.Validation;
-
-namespace ProtonVPN.BugReporting
-{
- public class FormViewModel : ValidationViewModel, IUserDataAware, ILogoutAware
- {
- private string _whatWentWrong;
- private string _stepsToReproduce;
- private string _email;
- private bool _includeLogs;
-
- private readonly IUserStorage _userStorage;
- private readonly IReportFieldProvider _reportFieldProvider;
-
- public FormViewModel(IUserStorage userStorage, IReportFieldProvider reportFieldProvider)
- {
- _userStorage = userStorage;
- _reportFieldProvider = reportFieldProvider;
- }
-
- public string Email
- {
- get => _email;
- set
- {
- Set(ref _email, value);
- Validate();
- }
- }
-
- public string WhatWentWrong
- {
- get => _whatWentWrong;
- set
- {
- Set(ref _whatWentWrong, value);
- Validate();
- }
- }
-
- public string StepsToReproduce
- {
- get => _stepsToReproduce;
- set
- {
- Set(ref _stepsToReproduce, value);
- Validate();
- }
- }
-
- public bool IncludeLogs
- {
- get => _includeLogs;
- set => Set(ref _includeLogs, value);
- }
-
- public void Load()
- {
- ClearForm();
- LoadEmail();
- }
-
- public KeyValuePair[] GetFields()
- {
- return _reportFieldProvider.GetFields(Description, Email);
- }
-
- public void OnUserDataChanged()
- {
- LoadEmail();
- }
-
- public void OnUserLoggedOut()
- {
- Email = string.Empty;
- }
-
- private string Description => $"What went wrong?\n\n{WhatWentWrong}\n\n" +
- $"What are the exact steps you performed?\n\n{StepsToReproduce}";
-
- private void ClearForm()
- {
- WhatWentWrong = string.Empty;
- StepsToReproduce = string.Empty;
- IncludeLogs = true;
- }
-
- private void LoadEmail()
- {
- User user = _userStorage.User();
-
- if (EmailValidator.IsValid(user.Username))
- {
- Email = user.Username;
- }
- }
-
- public new bool HasErrors => base.HasErrors || string.IsNullOrEmpty(Email);
-
- private void Validate([CallerMemberName] string field = null)
- {
- switch (field)
- {
- case nameof(Email):
- if (!EmailValidator.IsValid(Email))
- {
- SetError(nameof(Email), Translation.Get("BugReport_msg_EmailNotValid"));
- }
- else
- {
- ClearError(field);
- }
-
- break;
- case nameof(WhatWentWrong):
- if (string.IsNullOrEmpty(WhatWentWrong))
- {
- SetError(nameof(WhatWentWrong), "empty");
- }
- else
- {
- ClearError(field);
- }
-
- break;
- case nameof(StepsToReproduce):
- if (string.IsNullOrEmpty(StepsToReproduce))
- {
- SetError(nameof(StepsToReproduce), "empty");
- }
- else
- {
- ClearError(field);
- }
-
- break;
- }
- }
- }
-}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/IBugReport.cs b/src/ProtonVPN.App/BugReporting/IBugReport.cs
index f2d1c457c..43f0cf07d 100644
--- a/src/ProtonVPN.App/BugReporting/IBugReport.cs
+++ b/src/ProtonVPN.App/BugReporting/IBugReport.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
diff --git a/src/ProtonVPN.App/BugReporting/IReportFieldProvider.cs b/src/ProtonVPN.App/BugReporting/IReportFieldProvider.cs
index b521657f7..df824d7e1 100644
--- a/src/ProtonVPN.App/BugReporting/IReportFieldProvider.cs
+++ b/src/ProtonVPN.App/BugReporting/IReportFieldProvider.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -18,11 +18,12 @@
*/
using System.Collections.Generic;
+using ProtonVPN.BugReporting.FormElements;
namespace ProtonVPN.BugReporting
{
public interface IReportFieldProvider
{
- KeyValuePair[] GetFields(string description, string email);
+ KeyValuePair[] GetFields(string category, IList formElements);
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml b/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml
index 78c187ec0..cd43099e9 100644
--- a/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml
+++ b/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml
@@ -1,5 +1,5 @@
-
-
-
-
-
-
-
-
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:bugReporting="clr-namespace:ProtonVPN.BugReporting"
+ xmlns:wpf="clr-namespace:ProtonVPN.Core.Wpf"
+ xmlns:translations="clr-namespace:ProtonVPN.Translations;assembly=ProtonVPN.Translations"
+ xmlns:resource="clr-namespace:ProtonVPN.Resource;assembly=ProtonVPN.Resource"
+ xmlns:steps="clr-namespace:ProtonVPN.BugReporting.Steps"
+ xmlns:screens="clr-namespace:ProtonVPN.BugReporting.Screens"
+ Style="{StaticResource BaseWindowStyle}"
+ Title="{translations:Loc BugReport_ttl}"
+ mc:Ignorable="d"
+ Width="653"
+ Height="Auto"
+ MaxHeight="700"
+ SizeToContent="Height"
+ d:DataContext="{d:DesignInstance bugReporting:ReportBugModalViewModel}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml.cs b/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml.cs
index 2d0ddc873..dce290734 100644
--- a/src/ProtonVPN.App/BugReporting/ReportBugModalView.xaml.cs
+++ b/src/ProtonVPN.App/BugReporting/ReportBugModalView.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,6 +17,8 @@
* along with ProtonVPN. If not, see .
*/
+using WpfScreenHelper;
+
namespace ProtonVPN.BugReporting
{
public partial class ReportBugModalView
@@ -24,6 +26,21 @@ public partial class ReportBugModalView
public ReportBugModalView()
{
InitializeComponent();
+ SizeChanged += ReportBugModalView_SizeChanged;
+ }
+
+ private void ReportBugModalView_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
+ {
+ if (e.PreviousSize == e.NewSize)
+ {
+ return;
+ }
+
+ Screen screen = Screen.FromWindow(this);
+ if (screen != null)
+ {
+ Top = screen.WorkingArea.Top + (screen.WorkingArea.Height - ActualHeight) / 2;
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/ReportBugModalViewModel.cs b/src/ProtonVPN.App/BugReporting/ReportBugModalViewModel.cs
index 59c2ff2e4..3109def10 100644
--- a/src/ProtonVPN.App/BugReporting/ReportBugModalViewModel.cs
+++ b/src/ProtonVPN.App/BugReporting/ReportBugModalViewModel.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -17,145 +17,132 @@
* along with ProtonVPN. If not, see .
*/
-using System.Windows.Input;
-using GalaSoft.MvvmLight.CommandWpf;
+using System.Collections.Generic;
+using Caliburn.Micro;
+using ProtonVPN.BugReporting.Actions;
+using ProtonVPN.BugReporting.FormElements;
+using ProtonVPN.BugReporting.Screens;
+using ProtonVPN.BugReporting.Steps;
using ProtonVPN.Common.Abstract;
-using ProtonVPN.Core.MVVM;
using ProtonVPN.Modals;
namespace ProtonVPN.BugReporting
{
- public class ReportBugModalViewModel : BaseModalViewModel
+ public class ReportBugModalViewModel : BaseModalViewModel,
+ IHandle,
+ IHandle,
+ IHandle,
+ IHandle
{
private readonly IBugReport _bugReport;
- private readonly SentViewModel _sentViewModel;
+ private readonly StepsContainerViewModel _stepsContainerViewModel;
+ private readonly IReportFieldProvider _reportFieldProvider;
+ private readonly IEventAggregator _eventAggregator;
private readonly SendingViewModel _sendingViewModel;
private readonly FailureViewModel _failureViewModel;
+ private readonly SentViewModel _sentViewModel;
+ private FormState _formState = FormState.Editing;
- private ViewModel _currentViewModel;
- private bool _sending;
- private bool _sent;
+ private Screen _screenViewModel;
public ReportBugModalViewModel(
IBugReport bugReport,
+ StepsContainerViewModel stepsContainerViewModel,
+ IReportFieldProvider reportFieldProvider,
+ IEventAggregator eventAggregator,
SendingViewModel sendingViewModel,
- SentViewModel sentViewModel,
- FormViewModel formViewModel,
- FailureViewModel failureViewModel)
+ FailureViewModel failureViewModel,
+ SentViewModel sentViewModel)
{
+ eventAggregator.Subscribe(this);
+
_bugReport = bugReport;
+ _stepsContainerViewModel = stepsContainerViewModel;
+ _reportFieldProvider = reportFieldProvider;
+ _eventAggregator = eventAggregator;
_sendingViewModel = sendingViewModel;
- _sentViewModel = sentViewModel;
- FormViewModel = formViewModel;
_failureViewModel = failureViewModel;
+ _sentViewModel = sentViewModel;
- SendReportCommand = new RelayCommand(SendReport, CanSend);
- BackCommand = new RelayCommand(Back);
- RetryCommand = new RelayCommand(Retry, CanSend);
- }
-
- public ICommand SendReportCommand { get; set; }
- public ICommand BackCommand { get; set; }
- public RelayCommand RetryCommand { get; set; }
- public FormViewModel FormViewModel { get; set; }
-
- public ViewModel OverlayViewModel
- {
- get => _currentViewModel;
- set => Set(ref _currentViewModel, value);
- }
-
- public bool Sending
- {
- get => _sending;
- set => Set(ref _sending, value);
+ ScreenViewModel = stepsContainerViewModel;
}
- public bool Sent
+ public Screen ScreenViewModel
{
- get => _sent;
- set => Set(ref _sent, value);
+ get => _screenViewModel;
+ set => Set(ref _screenViewModel, value);
}
- public void ShowSendingWindow()
+ public async void Handle(SendReportAction message)
{
- OverlayViewModel = _sendingViewModel;
- }
+ await _eventAggregator.PublishOnUIThreadAsync(new FormStateChange(FormState.Sending));
- public void ShowReportSentWindow()
- {
- OverlayViewModel = _sentViewModel;
- }
+ ShowSendingView();
- protected override void OnActivate()
- {
- base.OnActivate();
+ KeyValuePair[] fields = _reportFieldProvider.GetFields(message.Category, message.FormElements);
+ Result result = message.SendErrorLogs
+ ? await _bugReport.SendWithLogsAsync(fields)
+ : await _bugReport.SendAsync(fields);
- if (OverlayViewModel is SentViewModel)
+ FormState formState;
+ if (result.Success)
{
- ClearOverlay();
+ formState = FormState.Sent;
+ ShowSuccessView(message.FormElements.GetEmailField().Value);
+ }
+ else
+ {
+ formState = FormState.FailedToSend;
+ ShowFailureView(result.Error);
}
- FormViewModel.Load();
+ await _eventAggregator.PublishOnUIThreadAsync(new FormStateChange(formState));
}
- public override void CloseAction()
+ public void Handle(FinishReportAction message)
{
- base.CloseAction();
-
- Sent = false;
- if (OverlayViewModel is FailureViewModel)
- {
- ClearOverlay();
- }
+ TryClose();
}
- private void ShowFailureView(string error)
+ public void Handle(FormStateChange message)
{
- _failureViewModel.Error = error;
- OverlayViewModel = _failureViewModel;
- RetryCommand.RaiseCanExecuteChanged();
+ _formState = message.State;
}
- private async void SendReport()
+ public void Handle(GoBackAfterFailureAction message)
{
- Sending = true;
- ShowSendingWindow();
-
- Result result = FormViewModel.IncludeLogs
- ? await _bugReport.SendWithLogsAsync(FormViewModel.GetFields())
- : await _bugReport.SendAsync(FormViewModel.GetFields());
-
- Sending = false;
+ ShowStepsView();
+ }
- if (result.Success)
- {
- ShowReportSentWindow();
- }
- else
+ protected override void OnDeactivate(bool close)
+ {
+ base.OnDeactivate(close);
+ if (_formState == FormState.Sent)
{
- ShowFailureView(result.Error);
+ ShowStepsView();
}
}
- private bool CanSend()
+ private void ShowStepsView()
{
- return !_sending && !FormViewModel.HasErrors;
+ ScreenViewModel = _stepsContainerViewModel;
}
- private void Retry()
+ private void ShowSendingView()
{
- SendReport();
+ ScreenViewModel = _sendingViewModel;
}
- private void ClearOverlay()
+ private void ShowSuccessView(string email)
{
- OverlayViewModel = null;
+ _sentViewModel.Email = email;
+ ScreenViewModel = _sentViewModel;
}
- private void Back()
+ private void ShowFailureView(string error)
{
- ClearOverlay();
+ _failureViewModel.Error = error;
+ ScreenViewModel = _failureViewModel;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs b/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs
index f221ea331..41d644452 100644
--- a/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs
+++ b/src/ProtonVPN.App/BugReporting/ReportFieldProvider.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -19,9 +19,12 @@
using System;
using System.Collections.Generic;
+using System.Text;
using ProtonVPN.Account;
-using ProtonVPN.BugReporting.Diagnostic;
+using ProtonVPN.BugReporting.FormElements;
+using ProtonVPN.Common.Extensions;
using ProtonVPN.Core.Models;
+using ProtonVPN.Core.OS;
using ProtonVPN.Core.Settings;
using ProtonVPN.Core.User;
using ProtonVPN.Servers;
@@ -34,14 +37,15 @@ public class ReportFieldProvider : IReportFieldProvider
private readonly Common.Configuration.Config _config;
private readonly ISystemState _systemState;
- public ReportFieldProvider(IUserStorage userStorage, Common.Configuration.Config config, ISystemState systemState)
+ public ReportFieldProvider(IUserStorage userStorage, Common.Configuration.Config config,
+ ISystemState systemState)
{
_config = config;
_userStorage = userStorage;
_systemState = systemState;
}
- public KeyValuePair[] GetFields(string description, string email)
+ public KeyValuePair[] GetFields(string category, IList formElements)
{
User user = _userStorage.User();
UserLocation location = _userStorage.Location();
@@ -55,20 +59,43 @@ public KeyValuePair[] GetFields(string description, string email
new KeyValuePair("Client", "Windows app"),
new KeyValuePair("ClientVersion", _config.AppVersion),
new KeyValuePair("Title", "Windows app form"),
- new KeyValuePair("Description", GetDescription(description)),
+ new KeyValuePair("Description", GetDescription(category, formElements)),
new KeyValuePair("Username", user.Username),
new KeyValuePair("Plan", VpnPlanHelper.GetPlanName(user.VpnPlan)),
- new KeyValuePair("Email", email),
+ new KeyValuePair("Email", GetEmail(formElements)),
new KeyValuePair("Country", string.IsNullOrEmpty(country) ? "" : country),
new KeyValuePair("ISP", string.IsNullOrEmpty(isp) ? "" : isp),
new KeyValuePair("ClientType", "2")
};
}
- private string GetDescription(string description)
+ private string GetDescription(string category, IList formElements)
{
- return description + "\n\nAdditional info:\n" +
- "Pending reboot: " + (_systemState.PendingReboot() ? "true" : "false");
+ StringBuilder stringBuilder = new();
+ stringBuilder.AppendLine($"Category: {category}");
+ stringBuilder.AppendLine();
+
+ foreach (FormElement element in formElements)
+ {
+ if (!element.Value.IsNullOrEmpty() && !element.IsEmailField())
+ {
+ stringBuilder.AppendLine(element.SubmitLabel);
+ stringBuilder.AppendLine(element.Value);
+ stringBuilder.AppendLine();
+ }
+ }
+
+ stringBuilder.AppendLine("Additional info");
+ stringBuilder.AppendLine($"Pending reboot: {_systemState.PendingReboot().ToYesNoString()}");
+ stringBuilder.AppendLine($"DeviceID: {_config.DeviceId}");
+
+ return stringBuilder.ToString();
+ }
+
+ private string GetEmail(IList formElements)
+ {
+ FormElement emailField = formElements.GetEmailField();
+ return emailField != null ? emailField.Value : string.Empty;
}
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/FailureView.xaml b/src/ProtonVPN.App/BugReporting/Screens/FailureView.xaml
similarity index 62%
rename from src/ProtonVPN.App/BugReporting/FailureView.xaml
rename to src/ProtonVPN.App/BugReporting/Screens/FailureView.xaml
index 75402fe01..92a49095c 100644
--- a/src/ProtonVPN.App/BugReporting/FailureView.xaml
+++ b/src/ProtonVPN.App/BugReporting/Screens/FailureView.xaml
@@ -1,5 +1,5 @@
-
-
-
+
+
+ Foreground="White" />
-
-
+
\ 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/SentView.xaml.cs b/src/ProtonVPN.App/BugReporting/Screens/SentView.xaml.cs
similarity index 90%
rename from src/ProtonVPN.App/BugReporting/SentView.xaml.cs
rename to src/ProtonVPN.App/BugReporting/Screens/SentView.xaml.cs
index db5f5a35e..901b0c883 100644
--- a/src/ProtonVPN.App/BugReporting/SentView.xaml.cs
+++ b/src/ProtonVPN.App/BugReporting/Screens/SentView.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 SentView
{
@@ -26,4 +26,4 @@ public SentView()
InitializeComponent();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Screens/SentViewModel.cs b/src/ProtonVPN.App/BugReporting/Screens/SentViewModel.cs
new file mode 100644
index 000000000..8cb7b724f
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Screens/SentViewModel.cs
@@ -0,0 +1,58 @@
+/*
+ * 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.CommandWpf;
+using ProtonVPN.BugReporting.Actions;
+using ProtonVPN.Translations;
+
+namespace ProtonVPN.BugReporting.Screens
+{
+ public class SentViewModel : Screen
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private string _email;
+
+ public SentViewModel(IEventAggregator eventAggregator)
+ {
+ _eventAggregator = eventAggregator;
+ FinishReportCommand = new RelayCommand(FinishReportAction);
+ }
+
+ public ICommand FinishReportCommand { get; }
+
+ public string Email
+ {
+ get => _email;
+ set
+ {
+ _email = value;
+ NotifyOfPropertyChange(SuccessMessage);
+ }
+ }
+
+ public string SuccessMessage => Translation.Format("BugReport_lbl_WillGetBack", Email);
+
+ private void FinishReportAction()
+ {
+ _eventAggregator.PublishOnUIThread(new FinishReportAction());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionView.xaml b/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionView.xaml
new file mode 100644
index 000000000..79b46d15c
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/CategorySelectionView.xaml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..aca1cc842
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml.cs b/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml.cs
new file mode 100644
index 000000000..2586e39ae
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/FormView.xaml.cs
@@ -0,0 +1,41 @@
+/*
+ * 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 FormView
+ {
+ public FormView()
+ {
+ InitializeComponent();
+ Loaded += OnFormViewLoaded;
+ }
+
+ private void OnFormViewLoaded(object sender, System.Windows.RoutedEventArgs e)
+ {
+ ErrorLogsCheckBox.Checked += OnErrorLogsCheckBoxToggle;
+ ErrorLogsCheckBox.Unchecked += OnErrorLogsCheckBoxToggle;
+ }
+
+ private void OnErrorLogsCheckBoxToggle(object sender, System.Windows.RoutedEventArgs e)
+ {
+ ScrollViewer.ScrollToEnd();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs b/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs
new file mode 100644
index 000000000..23c02ad8c
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/FormViewModel.cs
@@ -0,0 +1,186 @@
+/*
+ * 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.ComponentModel;
+using System.Linq;
+using System.Windows.Input;
+using Caliburn.Micro;
+using GalaSoft.MvvmLight.CommandWpf;
+using ProtonVPN.BugReporting.Actions;
+using ProtonVPN.BugReporting.FormElements;
+using ProtonVPN.Common.Extensions;
+using ProtonVPN.Core.Auth;
+using ProtonVPN.Core.Models;
+using ProtonVPN.Core.Settings;
+using ProtonVPN.Core.User;
+
+namespace ProtonVPN.BugReporting.Steps
+{
+ public class FormViewModel : Screen,
+ IUserDataAware,
+ ILogoutAware,
+ IHandle,
+ IHandle,
+ IHandle
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private readonly IUserStorage _userStorage;
+ private readonly IFormElementBuilder _formElementBuilder;
+ private bool _isToIncludeErrorLogs = true;
+ private List _formElements = new();
+ private bool _isFormBeingSent;
+ private bool _hasErrors = true;
+ private bool _isEmailValid = true;
+ private string _category;
+
+ public FormViewModel(IEventAggregator eventAggregator, IUserStorage userStorage,
+ IFormElementBuilder formElementBuilder)
+ {
+ _eventAggregator = eventAggregator;
+ _userStorage = userStorage;
+ _formElementBuilder = formElementBuilder;
+ eventAggregator.Subscribe(this);
+ SendReportCommand = new RelayCommand(SendReportAction, () => !_hasErrors && !_isFormBeingSent);
+ }
+
+ public ICommand SendReportCommand { get; }
+
+ public bool IsToIncludeErrorLogs
+ {
+ get => _isToIncludeErrorLogs;
+ set
+ {
+ Set(ref _isToIncludeErrorLogs, value);
+ NotifyOfPropertyChange(nameof(IsToShowLogsWarning));
+ }
+ }
+
+ public bool IsEmailValid
+ {
+ get => _isEmailValid;
+ set => Set(ref _isEmailValid, value);
+ }
+
+ public bool IsToShowLogsWarning => !IsToIncludeErrorLogs;
+
+ public List FormElements
+ {
+ get => _formElements;
+ set => Set(ref _formElements, value);
+ }
+
+ public void OnUserLoggedOut()
+ {
+ FormElements.Clear();
+ }
+
+ public void Handle(FillTheFormAction message)
+ {
+ if (!_category.IsNullOrEmpty() && _category != message.Category)
+ {
+ RemoveFormElements();
+ }
+
+ if (FormElements.Count == 0)
+ {
+ _category = message.Category;
+ AddFormElements();
+ }
+ }
+
+ private void AddFormElements()
+ {
+ FormElements = _formElementBuilder.GetFormElements(_category);
+ foreach (FormElement element in FormElements)
+ {
+ element.PropertyChanged += OnFormElementChanged;
+ }
+
+ UpdateEmailInput();
+ }
+
+ public void Handle(FormStateChange message)
+ {
+ _isFormBeingSent = message.State == FormState.Sending;
+ if (message.State == FormState.Sent)
+ {
+ RemoveFormElements();
+ }
+ }
+
+ private void RemoveFormElements()
+ {
+ foreach (FormElement element in FormElements)
+ {
+ element.PropertyChanged -= OnFormElementChanged;
+ }
+
+ FormElements.Clear();
+ }
+
+ private void OnFormElementChanged(object sender, PropertyChangedEventArgs e)
+ {
+ ValidateForm();
+ }
+
+ private void ValidateForm()
+ {
+ ValidateEmailField();
+ _hasErrors = FormElements.Any(element => !element.IsValid());
+ }
+
+ private void ValidateEmailField()
+ {
+ FormElement emailField = FormElements.GetEmailField();
+ if (emailField != null)
+ {
+ IsEmailValid = emailField.IsValid();
+ }
+ }
+
+ public void Handle(RetryAction message)
+ {
+ SendReportAction();
+ }
+
+ private void SendReportAction()
+ {
+ _eventAggregator.PublishOnUIThread(new SendReportAction(_category, FormElements, IsToIncludeErrorLogs));
+ }
+
+ public void OnUserDataChanged()
+ {
+ UpdateEmailInput();
+ }
+
+ private void UpdateEmailInput()
+ {
+ User user = _userStorage.User();
+ if (EmailValidator.IsValid(user.Username))
+ {
+ FormElement emailField = FormElements.GetEmailField();
+ if (emailField != null)
+ {
+ emailField.Value = user.Username;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/SolutionsView.xaml b/src/ProtonVPN.App/BugReporting/Steps/SolutionsView.xaml
new file mode 100644
index 000000000..44455843a
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/SolutionsView.xaml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/SolutionsView.xaml.cs b/src/ProtonVPN.App/BugReporting/Steps/SolutionsView.xaml.cs
new file mode 100644
index 000000000..229d536d8
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/SolutionsView.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 SolutionsView
+ {
+ public SolutionsView()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/SolutionsViewModel.cs b/src/ProtonVPN.App/BugReporting/Steps/SolutionsViewModel.cs
new file mode 100644
index 000000000..e4d469326
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/SolutionsViewModel.cs
@@ -0,0 +1,77 @@
+/*
+ * 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.Common.OS.Processes;
+using ProtonVPN.Config.Url;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
+using ProtonVPN.Core.ReportAnIssue;
+
+namespace ProtonVPN.BugReporting.Steps
+{
+ public class SolutionsViewModel : Screen, IHandle
+ {
+ private readonly IEventAggregator _eventAggregator;
+ private readonly IReportAnIssueFormDataProvider _reportAnIssueFormDataProvider;
+ private readonly IOsProcesses _osProcesses;
+ private string _categorySubmitName;
+
+ public SolutionsViewModel(IEventAggregator eventAggregator, IReportAnIssueFormDataProvider reportAnIssueFormDataProvider, IOsProcesses osProcesses)
+ {
+ eventAggregator.Subscribe(this);
+ _eventAggregator = eventAggregator;
+ _reportAnIssueFormDataProvider = reportAnIssueFormDataProvider;
+ _osProcesses = osProcesses;
+ FillTheFormCommand = new RelayCommand(FillTheFormAction);
+ LearnMoreCommand = new RelayCommand(LearnMoreAction);
+ }
+
+ public ICommand FillTheFormCommand { get; }
+ public ICommand LearnMoreCommand { get; }
+
+ private List _suggestions = new();
+
+ public List Suggestions
+ {
+ get => _suggestions;
+ set => Set(ref _suggestions, value);
+ }
+
+ public void Handle(SelectCategoryAction message)
+ {
+ _categorySubmitName = message.Category;
+ Suggestions = _reportAnIssueFormDataProvider.GetSuggestions(message.Category);
+ }
+
+ private void FillTheFormAction()
+ {
+ _eventAggregator.PublishOnUIThread(new FillTheFormAction(_categorySubmitName));
+ }
+
+ private void LearnMoreAction(string url)
+ {
+ ActiveUrl activeUrl = new(_osProcesses, url);
+ activeUrl.Open();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml
new file mode 100644
index 000000000..b68a83b0d
--- /dev/null
+++ b/src/ProtonVPN.App/BugReporting/Steps/StepsContainerView.xaml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/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/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/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/Bootstraper.cs b/src/ProtonVPN.App/Core/Bootstraper.cs
index 5113567a4..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;
@@ -200,19 +201,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;
}
@@ -518,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/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/INetworkAdapterValidator.cs b/src/ProtonVPN.App/Core/Service/Vpn/INetworkAdapterValidator.cs
index 509a4a729..9992fe53b 100644
--- a/src/ProtonVPN.App/Core/Service/Vpn/INetworkAdapterValidator.cs
+++ b/src/ProtonVPN.App/Core/Service/Vpn/INetworkAdapterValidator.cs
@@ -21,6 +21,6 @@ namespace ProtonVPN.Core.Service.Vpn
{
public interface INetworkAdapterValidator
{
- bool IsAdapterAvailable();
+ bool IsOpenVpnAdapterAvailable();
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/Core/Service/Vpn/NetworkAdapterValidator.cs b/src/ProtonVPN.App/Core/Service/Vpn/NetworkAdapterValidator.cs
index 50dff6d2b..a46d29cdc 100644
--- a/src/ProtonVPN.App/Core/Service/Vpn/NetworkAdapterValidator.cs
+++ b/src/ProtonVPN.App/Core/Service/Vpn/NetworkAdapterValidator.cs
@@ -17,51 +17,59 @@
* along with ProtonVPN. If not, see .
*/
-using ProtonVPN.Common.Networking;
+using ProtonVPN.Common.Logging;
using ProtonVPN.Common.OS.Net;
using ProtonVPN.Common.OS.Net.NetworkInterface;
-using ProtonVPN.Core.Settings;
-using Sentry;
-using Sentry.Protocol;
namespace ProtonVPN.Core.Service.Vpn
{
public class NetworkAdapterValidator : INetworkAdapterValidator
{
private readonly INetworkInterfaceLoader _networkInterfaceLoader;
- private readonly IAppSettings _appSettings;
+ private readonly ILogger _logger;
- public NetworkAdapterValidator(INetworkInterfaceLoader networkInterfaceLoader, IAppSettings appSettings)
+ public NetworkAdapterValidator(INetworkInterfaceLoader networkInterfaceLoader, ILogger logger)
{
_networkInterfaceLoader = networkInterfaceLoader;
- _appSettings = appSettings;
+ _logger = logger;
}
- public bool IsAdapterAvailable()
+ public bool IsOpenVpnAdapterAvailable()
{
- INetworkInterface openVpnTunInterface = _networkInterfaceLoader.GetOpenVpnTunInterface();
INetworkInterface openVpnTapInterface = _networkInterfaceLoader.GetOpenVpnTapInterface();
- if (openVpnTunInterface == null && openVpnTapInterface == null)
- {
- return false;
- }
+ INetworkInterface openVpnTunInterface = _networkInterfaceLoader.GetOpenVpnTunInterface();
+ bool isOpenVpnAdapterAvailable = openVpnTapInterface != null || openVpnTunInterface != null;
+
+ LogIsOpenVpnAdapterAvailable(isOpenVpnAdapterAvailable,
+ CreateInterfaceLogMessage("TAP", openVpnTapInterface),
+ CreateInterfaceLogMessage("TUN", openVpnTunInterface));
- if (openVpnTunInterface == null && _appSettings.NetworkAdapterType == OpenVpnAdapter.Tun)
+ return isOpenVpnAdapterAvailable;
+ }
+
+ private string CreateInterfaceLogMessage(string interfaceType, INetworkInterface networkInterface)
+ {
+ if (networkInterface == null)
{
- _appSettings.NetworkAdapterType = OpenVpnAdapter.Tap;
- SendTunFallbackEvent();
+ return $"The {interfaceType} adapter is unavailable.";
}
- return true;
+ return $"The {interfaceType} adapter is available (Index: {networkInterface.Index}, " +
+ $"Name: '{networkInterface.Name}', Description: '{networkInterface.Description}').";
}
- private void SendTunFallbackEvent()
+ private void LogIsOpenVpnAdapterAvailable(bool isOpenVpnAdapterAvailable, params string[] openVpnInterfaces)
{
- SentrySdk.CaptureEvent(new SentryEvent
+ string logMessage = $"[NetworkAdapterValidator] Checking which OpenVPN adapters are available. " +
+ string.Join(" ", openVpnInterfaces);
+ if (isOpenVpnAdapterAvailable)
{
- Message = "TUN adapter not found. Adapter changed to TAP.",
- Level = SentryLevel.Info,
- });
+ _logger.Info(logMessage);
+ }
+ else
+ {
+ _logger.Warn(logMessage);
+ }
}
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs b/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs
index bb10481c1..8f7d01dc6 100644
--- a/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs
+++ b/src/ProtonVPN.App/Core/Service/Vpn/VpnConnector.cs
@@ -42,7 +42,6 @@ public class VpnConnector : IVpnConnector, ILogoutAware
private readonly IAppSettings _appSettings;
private readonly GuestHoleState _guestHoleState;
private readonly IUserStorage _userStorage;
- private readonly INetworkAdapterValidator _networkAdapterValidator;
private readonly ServerManager _serverManager;
private readonly ILogger _logger;
@@ -59,7 +58,6 @@ public VpnConnector(
IAppSettings appSettings,
GuestHoleState guestHoleState,
IUserStorage userStorage,
- INetworkAdapterValidator networkAdapterValidator,
ServerManager serverManager,
ILogger logger)
{
@@ -68,7 +66,6 @@ public VpnConnector(
_appSettings = appSettings;
_guestHoleState = guestHoleState;
_userStorage = userStorage;
- _networkAdapterValidator = networkAdapterValidator;
_serverManager = serverManager;
_logger = logger;
}
@@ -103,24 +100,6 @@ private async Task GetQuickConnectProfileAsync()
}
public async Task ConnectToBestProfileAsync(Profile profile, Profile fallbackProfile = null, int? maxServers = null)
- {
- await ValidateConnectionAsync(() => ExecuteConnectToBestProfileAsync(profile, fallbackProfile, maxServers));
- }
-
- private async Task ValidateConnectionAsync(Func connectionFunction)
- {
- if (_networkAdapterValidator.IsAdapterAvailable())
- {
- await connectionFunction();
- }
- else
- {
- RaiseVpnStateChanged(new VpnStateChangedEventArgs(new VpnState(VpnStatus.Disconnected),
- VpnError.NoTapAdaptersError, false));
- }
- }
-
- private async Task ExecuteConnectToBestProfileAsync(Profile profile, Profile fallbackProfile = null, int? maxServers = null)
{
IList profiles = CreateProfilePreferenceList(profile, fallbackProfile);
VpnManagerProfileCandidates profileCandidates = GetBestProfileCandidates(profiles);
@@ -201,21 +180,11 @@ private async Task ConnectAsync(VpnManagerProfileCandidates profileCandidates, V
}
public async Task ConnectToPreSortedCandidatesAsync(IReadOnlyCollection sortedCandidates, VpnProtocol vpnProtocol)
- {
- await ValidateConnectionAsync(() => ExecuteConnectToPreSortedCandidatesAsync(sortedCandidates, vpnProtocol));
- }
-
- private async Task ExecuteConnectToPreSortedCandidatesAsync(IReadOnlyCollection sortedCandidates, VpnProtocol vpnProtocol)
{
await _profileConnector.ConnectWithPreSortedCandidates(sortedCandidates, vpnProtocol);
}
public async Task ConnectToProfileAsync(Profile profile, VpnProtocol? vpnProtocol = null)
- {
- await ValidateConnectionAsync(() => ExecuteConnectToProfileAsync(profile, vpnProtocol));
- }
-
- private async Task ExecuteConnectToProfileAsync(Profile profile, VpnProtocol? vpnProtocol = null)
{
VpnManagerProfileCandidates profileCandidates = GetProfileCandidates(profile);
await ConnectToProfileCandidatesAsync(profileCandidates, vpnProtocol);
@@ -238,7 +207,7 @@ private void SetPropertiesOnVpnStateChanged(VpnStateChangedEventArgs e)
LastServer = _serverManager.GetServerByEntryIpAndLabel(e.State.EntryIp, e.State.Label);
}
- State = new VpnState(e.State.Status, LastServer, e.State.VpnProtocol);
+ State = new VpnState(e.State.Status, LastServer, e.State.VpnProtocol, e.State.NetworkAdapterType);
NetworkBlocked = e.NetworkBlocked;
RaiseVpnStateChanged(new VpnStateChangedEventArgs(State, e.Error, e.NetworkBlocked));
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/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/Modals/AssignVpnConnectionsModalView.xaml.cs b/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.xaml.cs
new file mode 100644
index 000000000..f3c534ebd
--- /dev/null
+++ b/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalView.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.Modals
+{
+ public partial class AssignVpnConnectionsModalView
+ {
+ public AssignVpnConnectionsModalView()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalViewModel.cs b/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalViewModel.cs
new file mode 100644
index 000000000..7098f8a5a
--- /dev/null
+++ b/src/ProtonVPN.App/Modals/AssignVpnConnectionsModalViewModel.cs
@@ -0,0 +1,43 @@
+/*
+ * 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 GalaSoft.MvvmLight.CommandWpf;
+using ProtonVPN.Config.Url;
+
+namespace ProtonVPN.Modals
+{
+ public class AssignVpnConnectionsModalViewModel : BaseModalViewModel
+ {
+ private readonly IActiveUrls _urls;
+
+ public AssignVpnConnectionsModalViewModel(IActiveUrls urls)
+ {
+ _urls = urls;
+ AssignVpnConnectionsCommand = new RelayCommand(OpenAssignVpnConnectionsPage);
+ }
+
+ public ICommand AssignVpnConnectionsCommand { get; }
+
+ public void OpenAssignVpnConnectionsPage()
+ {
+ _urls.AssignVpnConnectionsUrl.Open();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs b/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs
index 46bc9467d..5c400107d 100644
--- a/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs
+++ b/src/ProtonVPN.App/Modals/DisconnectErrorModalViewModel.cs
@@ -126,7 +126,8 @@ protected override async void OnViewReady(object view)
switch (Error)
{
case VpnError.TlsError:
- case VpnError.TimeoutError:
+ case VpnError.PingTimeoutError:
+ case VpnError.AdapterTimeoutError:
case VpnError.UserTierTooLowError:
case VpnError.Unpaid:
case VpnError.SessionLimitReached:
@@ -153,7 +154,8 @@ await _vpnManager.ReconnectAsync(new VpnReconnectionSettings
private async Task CloseModalAsync()
{
- // If TryClose() is called before any await (that actually awaits), Caliburn will throw a NullReferenceException after OnViewReady() ends
+ // If TryClose() is called before any await (that actually awaits), Caliburn will throw a
+ // NullReferenceException after OnViewReady() ends.
await Task.Delay(TimeSpan.FromMilliseconds(1));
TryClose(true);
}
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 acac72fe5..1a23a24c0 100644
--- a/src/ProtonVPN.App/ProtonVPN.App.csproj
+++ b/src/ProtonVPN.App/ProtonVPN.App.csproj
@@ -76,7 +76,6 @@
-
@@ -117,18 +116,48 @@
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+ FormView.xaml
+
+
+ SolutionsView.xaml
+
+
+ CategorySelectionView.xaml
+
+
+
+
+
+ StepsContainerView.xaml
+
+
@@ -168,6 +197,7 @@
+
ExitModalView.xaml
@@ -179,6 +209,9 @@
OfferModalView.xaml
+
+ AssignVpnConnectionsModalView.xaml
+
@@ -329,26 +362,22 @@
-
+
FailureView.xaml
-
-
- Form.xaml
-
-
+
ReportBugModalView.xaml
-
+
SendingView.xaml
-
-
+
+
SentView.xaml
-
+
@@ -875,6 +904,22 @@
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -883,6 +928,10 @@
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
Designer
MSBuild:Compile
@@ -1224,11 +1273,7 @@
AppWindow.xaml
Code
-
- Designer
- MSBuild:Compile
-
-
+
Designer
MSBuild:Compile
@@ -1236,11 +1281,11 @@
Designer
MSBuild:Compile
-
+
Designer
MSBuild:Compile
-
+
Designer
MSBuild:Compile
@@ -2032,6 +2077,9 @@
4.7.0.9
+
+ 2.1.1
+
7.0.0
@@ -2059,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">
+
+
+
+
+
+
+
+
+
+
+
@@ -103,14 +132,15 @@ along with ProtonVPN. If not, see .
-
+
-
-
+
+
-
+
diff --git a/src/ProtonVPN.App/Windows/Popups/DeveloperTools/DeveloperToolsPopupViewModel.cs b/src/ProtonVPN.App/Windows/Popups/DeveloperTools/DeveloperToolsPopupViewModel.cs
index 047403a12..61a96cef0 100644
--- a/src/ProtonVPN.App/Windows/Popups/DeveloperTools/DeveloperToolsPopupViewModel.cs
+++ b/src/ProtonVPN.App/Windows/Popups/DeveloperTools/DeveloperToolsPopupViewModel.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -23,16 +23,17 @@
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows.Input;
-using System.Windows.Interop;
-using System.Windows.Media;
using GalaSoft.MvvmLight.CommandWpf;
using Microsoft.Toolkit.Uwp.Notifications;
using ProtonVPN.Common.Extensions;
+using ProtonVPN.Common.Networking;
+using ProtonVPN.Common.Vpn;
using ProtonVPN.Core.Auth;
using ProtonVPN.Core.Modals;
using ProtonVPN.Core.Servers.Models;
using ProtonVPN.Core.Service.Vpn;
using ProtonVPN.Core.Settings;
+using ProtonVPN.Core.Vpn;
using ProtonVPN.Core.Window.Popups;
using ProtonVPN.Modals.SessionLimits;
using ProtonVPN.Notifications;
@@ -42,7 +43,7 @@
namespace ProtonVPN.Windows.Popups.DeveloperTools
{
- public class DeveloperToolsPopupViewModel : BasePopupViewModel
+ public class DeveloperToolsPopupViewModel : BasePopupViewModel, IVpnStateAware
{
private readonly UserAuth _userAuth;
private readonly IPopupWindows _popups;
@@ -85,6 +86,7 @@ private void InitializeCommands()
FullToastCommand = new RelayCommand(FullToastAction);
BasicToastCommand = new RelayCommand(BasicToastAction);
ClearToastNotificationLogsCommand = new RelayCommand(ClearToastNotificationLogsAction);
+ TriggerIntentionalCrashCommand = new RelayCommand(TriggerIntentionalCrashAction);
}
public ICommand ShowModalCommand { get; set; }
@@ -96,6 +98,30 @@ private void InitializeCommands()
public ICommand FullToastCommand { get; set; }
public ICommand BasicToastCommand { get; set; }
public ICommand ClearToastNotificationLogsCommand { get; set; }
+ public ICommand TriggerIntentionalCrashCommand { get; set; }
+
+
+ private bool _isConnected;
+ public bool IsConnected
+ {
+ get => _isConnected;
+ set
+ {
+ _isConnected = value;
+ NotifyOfPropertyChange();
+ }
+ }
+
+ private string _networkInformation;
+ public string NetworkInformation
+ {
+ get => _networkInformation;
+ set
+ {
+ _networkInformation = value;
+ NotifyOfPropertyChange();
+ }
+ }
private string _toastNotificationLog;
public string ToastNotificationLog
@@ -112,17 +138,6 @@ private set
}
}
- public bool IsHardwareAccelerationEnabled
- {
- get => RenderOptions.ProcessRenderMode == RenderMode.Default;
- set => SetHardwareAcceleration(value);
- }
-
- private void SetHardwareAcceleration(bool isToEnableHardwareAcceleration)
- {
- RenderOptions.ProcessRenderMode = isToEnableHardwareAcceleration ? RenderMode.Default : RenderMode.SoftwareOnly;
- }
-
private async void ShowModalAction()
{
await Task.Delay(TimeSpan.FromSeconds(5));
@@ -221,5 +236,45 @@ private void ClearToastNotificationLogsAction()
{
ToastNotificationLog = string.Empty;
}
+
+ private void TriggerIntentionalCrashAction()
+ {
+ throw new StackOverflowException("Intentional crash test");
+ }
+
+ public async Task OnVpnStateChanged(VpnStateChangedEventArgs e)
+ {
+ if (e.State.Status == VpnStatus.Connected)
+ {
+ IsConnected = true;
+ NetworkInformation = GenerateNetworkAdapterString(e.State);
+ }
+ else
+ {
+ IsConnected = false;
+ NetworkInformation = "Disconnected";
+ }
+ }
+
+ private string GenerateNetworkAdapterString(VpnState vpnState)
+ {
+ string result = "Connected" + Environment.NewLine + GenerateVpnProtocolString(vpnState.VpnProtocol);
+ if (vpnState.VpnProtocol == VpnProtocol.WireGuard)
+ {
+ return result;
+ }
+
+ return result + Environment.NewLine + GenerateOpenVpnAdapterString(vpnState.NetworkAdapterType);
+ }
+
+ private string GenerateVpnProtocolString(VpnProtocol vpnProtocol)
+ {
+ return $"Network protocol: {vpnProtocol}";
+ }
+
+ private string GenerateOpenVpnAdapterString(OpenVpnAdapter? networkAdapterType)
+ {
+ return $"Network adapter: {networkAdapterType}";
+ }
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/Configuration/Config.cs b/src/ProtonVPN.Common/Configuration/Config.cs
index a41dcec6b..f65099699 100644
--- a/src/ProtonVPN.Common/Configuration/Config.cs
+++ b/src/ProtonVPN.Common/Configuration/Config.cs
@@ -243,5 +243,7 @@ public class Config
public int AuthCertificateMaxNumOfRetries { get; internal set; }
public string NtpServerUrl { get; internal set; }
+
+ public string DeviceId { get; internal set; }
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/Configuration/Source/DefaultConfig.cs b/src/ProtonVPN.Common/Configuration/Source/DefaultConfig.cs
index bf2948560..6be2f6871 100644
--- a/src/ProtonVPN.Common/Configuration/Source/DefaultConfig.cs
+++ b/src/ProtonVPN.Common/Configuration/Source/DefaultConfig.cs
@@ -22,6 +22,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using DeviceId;
using ProtonVPN.Common.Configuration.Api.Handlers.TlsPinning;
using ProtonVPN.Common.Extensions;
@@ -223,6 +224,7 @@ public Config Value()
InvoicesUrl = "https://account.protonvpn.com/payments#invoices",
AboutSmartProtocolUrl = "https://protonvpn.com/support/how-to-change-vpn-protocols",
IncorrectSystemTimeArticleUrl = "https://protonvpn.com/support/update-windows-clock",
+ AssignVpnConnectionsUrl = "https://protonvpn.com/support/assign-vpn-connection"
},
OpenVpn =
@@ -342,7 +344,17 @@ public Config Value()
},
NtpServerUrl = "time.windows.com",
+
+ DeviceId = GetDeviceId()
};
}
+
+ private string GetDeviceId()
+ {
+ return new DeviceIdBuilder()
+ .AddProcessorId()
+ .AddMotherboardSerialNumber()
+ .ToString();
+ }
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/Configuration/UrlConfig.cs b/src/ProtonVPN.Common/Configuration/UrlConfig.cs
index 54032de87..fea620199 100644
--- a/src/ProtonVPN.Common/Configuration/UrlConfig.cs
+++ b/src/ProtonVPN.Common/Configuration/UrlConfig.cs
@@ -121,5 +121,8 @@ public class UrlConfig
[Required]
public string IncorrectSystemTimeArticleUrl { get; internal set; }
+
+ [Required]
+ public string AssignVpnConnectionsUrl { get; internal set; }
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/CrashReporting/CrashReports.cs b/src/ProtonVPN.Common/CrashReporting/CrashReports.cs
index a2022c902..df4244337 100644
--- a/src/ProtonVPN.Common/CrashReporting/CrashReports.cs
+++ b/src/ProtonVPN.Common/CrashReporting/CrashReports.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 .
*/
-using DeviceId;
+using System.Diagnostics;
using ProtonVPN.Common.Configuration;
using ProtonVPN.Common.Logging;
using ProtonVPN.Common.Service;
@@ -29,11 +29,12 @@ public static class CrashReports
{
public static void Init(Config config, ILogger logger = null)
{
- var options = new SentryOptions
+ SentryOptions options = new()
{
Release = $"vpn.windows-{config.AppVersion}",
AttachStacktrace = true,
Dsn = !string.IsNullOrEmpty(GlobalConfig.SentryDsn) ? new Dsn(GlobalConfig.SentryDsn) : null,
+ ReportAssemblies = false,
};
if (logger != null)
@@ -44,10 +45,8 @@ public static void Init(Config config, ILogger logger = null)
options.BeforeSend = e =>
{
- e.User.Id = new DeviceIdBuilder()
- .AddProcessorId()
- .AddMotherboardSerialNumber()
- .ToString();
+ e.SetTag("ProcessName", Process.GetCurrentProcess().ProcessName);
+ e.User.Id = config.DeviceId;
return e;
};
@@ -55,4 +54,4 @@ public static void Init(Config config, ILogger logger = null)
SentrySdk.Init(options);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/Extensions/BoolExtensions.cs b/src/ProtonVPN.Common/Extensions/BoolExtensions.cs
new file mode 100644
index 000000000..906f973e0
--- /dev/null
+++ b/src/ProtonVPN.Common/Extensions/BoolExtensions.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.Common.Extensions
+{
+ public static class BoolExtensions
+ {
+ public static string ToYesNoString(this bool value)
+ {
+ return value ? "yes" : "no";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/Extensions/StringExtensions.cs b/src/ProtonVPN.Common/Extensions/StringExtensions.cs
index 82731b062..940b0cd1b 100644
--- a/src/ProtonVPN.Common/Extensions/StringExtensions.cs
+++ b/src/ProtonVPN.Common/Extensions/StringExtensions.cs
@@ -59,6 +59,11 @@ public static bool EndsWithIgnoringCase(this string value, string other)
return value != null && value.EndsWith(other, StringComparison.OrdinalIgnoreCase);
}
+ public static bool IsNotNullAndContains(this string value, string other)
+ {
+ return value != null && value.Contains(other);
+ }
+
public static string FirstCharToUpper(this string value)
{
if (string.IsNullOrEmpty(value))
diff --git a/src/ProtonVPN.Common/OS/Net/INetworkInterfaceLoader.cs b/src/ProtonVPN.Common/OS/Net/INetworkInterfaceLoader.cs
index 7474f0363..6b7c890b5 100644
--- a/src/ProtonVPN.Common/OS/Net/INetworkInterfaceLoader.cs
+++ b/src/ProtonVPN.Common/OS/Net/INetworkInterfaceLoader.cs
@@ -29,5 +29,6 @@ public interface INetworkInterfaceLoader
INetworkInterface GetWireGuardTunInterface();
INetworkInterface GetByVpnProtocol(VpnProtocol vpnProtocol, OpenVpnAdapter? openVpnAdapter);
+ INetworkInterface GetByOpenVpnAdapter(OpenVpnAdapter? openVpnAdapter);
}
}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/OS/Net/NetworkInterface/SystemNetworkInterfaces.cs b/src/ProtonVPN.Common/OS/Net/NetworkInterface/SystemNetworkInterfaces.cs
index 6537e61e9..2435ab5c0 100644
--- a/src/ProtonVPN.Common/OS/Net/NetworkInterface/SystemNetworkInterfaces.cs
+++ b/src/ProtonVPN.Common/OS/Net/NetworkInterface/SystemNetworkInterfaces.cs
@@ -60,8 +60,7 @@ public INetworkInterface GetByLocalAddress(IPAddress localAddress)
foreach (System.Net.NetworkInformation.NetworkInterface i in interfaces)
{
- if (i.GetIPProperties().UnicastAddresses.FirstOrDefault(a => a.Address.Equals(localAddress)) !=
- null)
+ if (i.GetIPProperties().UnicastAddresses.FirstOrDefault(a => a.Address.Equals(localAddress)) != null)
{
return new SystemNetworkInterface(i);
}
diff --git a/src/ProtonVPN.Common/OS/Net/NetworkInterfaceLoader.cs b/src/ProtonVPN.Common/OS/Net/NetworkInterfaceLoader.cs
index 55cba001e..228cc2eca 100644
--- a/src/ProtonVPN.Common/OS/Net/NetworkInterfaceLoader.cs
+++ b/src/ProtonVPN.Common/OS/Net/NetworkInterfaceLoader.cs
@@ -53,11 +53,13 @@ public INetworkInterface GetWireGuardTunInterface()
public INetworkInterface GetByVpnProtocol(VpnProtocol vpnProtocol, OpenVpnAdapter? openVpnAdapter)
{
- if (vpnProtocol == VpnProtocol.WireGuard)
- {
- return GetWireGuardTunInterface();
- }
+ return vpnProtocol == VpnProtocol.WireGuard
+ ? GetWireGuardTunInterface()
+ : GetByOpenVpnAdapter(openVpnAdapter);
+ }
+ public INetworkInterface GetByOpenVpnAdapter(OpenVpnAdapter? openVpnAdapter)
+ {
return openVpnAdapter switch
{
OpenVpnAdapter.Tap => GetOpenVpnTapInterface(),
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/ProtonVPN.Common.csproj b/src/ProtonVPN.Common/ProtonVPN.Common.csproj
index 67ded7fa9..14424b583 100644
--- a/src/ProtonVPN.Common/ProtonVPN.Common.csproj
+++ b/src/ProtonVPN.Common/ProtonVPN.Common.csproj
@@ -105,6 +105,7 @@
+
diff --git a/src/ProtonVPN.Common/Service/ErrorHandler.cs b/src/ProtonVPN.Common/Service/ErrorHandler.cs
index 4b398cff7..6181dd994 100644
--- a/src/ProtonVPN.Common/Service/ErrorHandler.cs
+++ b/src/ProtonVPN.Common/Service/ErrorHandler.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -39,12 +39,11 @@ public ErrorHandler(ILogger logger)
public bool HandleError(Exception e)
{
- if (e.GetBaseException() is PipeException pipeException &&
- pipeException.Message.Contains("There was an error reading from the pipe: The pipe has been ended. (109, 0x6d)."))
+ if (e.GetBaseException() is PipeException pipeException && pipeException.Message.Contains("0x6d)"))
{
- _logger.Info("The service communication pipe has been ended, most likely because the user is exiting the app. " +
- "If that is the case, the following pipe exception message can be ignored: " + e.CombinedMessage());
-
+ _logger.Info(
+ "The service communication pipe has been ended, most likely because the service is shutting down.");
+
return false;
}
@@ -64,4 +63,4 @@ public void ProvideFault(Exception error, MessageVersion version, ref Message fa
{
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Common/Vpn/VpnConfig.cs b/src/ProtonVPN.Common/Vpn/VpnConfig.cs
index bd5974084..c9a1e184e 100644
--- a/src/ProtonVPN.Common/Vpn/VpnConfig.cs
+++ b/src/ProtonVPN.Common/Vpn/VpnConfig.cs
@@ -34,7 +34,7 @@ public class VpnConfig
public IReadOnlyCollection SplitTunnelIPs { get; }
- public OpenVpnAdapter OpenVpnAdapter { get; }
+ public OpenVpnAdapter OpenVpnAdapter { get; set; }
public VpnProtocol VpnProtocol { get; }
diff --git a/src/ProtonVPN.Common/Vpn/VpnError.cs b/src/ProtonVPN.Common/Vpn/VpnError.cs
index 5da7fdee8..9878d17eb 100644
--- a/src/ProtonVPN.Common/Vpn/VpnError.cs
+++ b/src/ProtonVPN.Common/Vpn/VpnError.cs
@@ -30,7 +30,7 @@ public enum VpnError
TapRequiresUpdateError,
TlsError,
TlsCertificateError,
- TimeoutError,
+ PingTimeoutError,
UserTierTooLowError,
Unpaid,
SessionLimitReached,
@@ -41,6 +41,8 @@ public enum VpnError
Unknown,
MissingServerPublicKey,
IncorrectVpnConfig,
+ ServerUnreachable,
+ AdapterTimeoutError,
GuestSession = 86100,
CertificateExpired = 86101,
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/Api/ApiClient.cs b/src/ProtonVPN.Core/Api/ApiClient.cs
index 747ee0c6f..bb51ffd0d 100644
--- a/src/ProtonVPN.Core/Api/ApiClient.cs
+++ b/src/ProtonVPN.Core/Api/ApiClient.cs
@@ -25,6 +25,7 @@
using ProtonVPN.Core.Abstract;
using ProtonVPN.Core.Api.Certificates;
using ProtonVPN.Core.Api.Contracts;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
using ProtonVPN.Core.Api.Data;
using ProtonVPN.Core.Settings;
using UserLocation = ProtonVPN.Core.Api.Contracts.UserLocation;
@@ -95,7 +96,7 @@ public async Task> GetAuthInfoResponse(AuthInfoReque
public async Task> GetVpnInfoResponse()
{
- HttpRequestMessage request = GetAuthorizedRequest(HttpMethod.Get, "vpn");
+ HttpRequestMessage request = GetAuthorizedRequest(HttpMethod.Get, "vpn/v2");
try
{
using (HttpResponseMessage response = await _client.SendAsync(request).ConfigureAwait(false))
@@ -177,6 +178,21 @@ public async Task> GetServerLoadsAsync(string ip)
}
}
+ public async Task> GetReportAnIssueFormData()
+ {
+ try
+ {
+ HttpRequestMessage request = GetAuthorizedRequest(HttpMethod.Get, "vpn/v1/featureconfig/dynamic-bug-reports");
+ using HttpResponseMessage response = await _client.SendAsync(request).ConfigureAwait(false);
+ System.IO.Stream stream = await response.Content.ReadAsStreamAsync();
+ return Logged(GetResponseStreamResult(stream, response.StatusCode), "Get report an issue form data");
+ }
+ catch (Exception e) when (e.IsApiCommunicationException())
+ {
+ throw new HttpRequestException(e.Message, e);
+ }
+ }
+
public async Task> GetLocationDataAsync()
{
try
diff --git a/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueCategory.cs b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueCategory.cs
new file mode 100644
index 000000000..c5af0f830
--- /dev/null
+++ b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueCategory.cs
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+namespace ProtonVPN.Core.Api.Contracts.ReportAnIssue
+{
+ public class IssueCategory
+ {
+ public string Label { get; set; }
+ public string SubmitLabel { get; set; }
+ public List Suggestions { get; set; }
+ public List InputFields { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueInput.cs b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueInput.cs
new file mode 100644
index 000000000..094e6ffb5
--- /dev/null
+++ b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueInput.cs
@@ -0,0 +1,36 @@
+/*
+ * 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.ComponentModel;
+using Newtonsoft.Json;
+
+namespace ProtonVPN.Core.Api.Contracts.ReportAnIssue
+{
+ public class IssueInput
+ {
+ public string Label { get; set; }
+ public string SubmitLabel { get; set; }
+ public string Type { get; set; }
+ public string Placeholder { get; set; }
+
+ [DefaultValue(true)]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ public bool IsMandatory { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueSuggestion.cs b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueSuggestion.cs
new file mode 100644
index 000000000..af3f92352
--- /dev/null
+++ b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/IssueSuggestion.cs
@@ -0,0 +1,33 @@
+/*
+ * 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.ComponentModel;
+using Newtonsoft.Json;
+
+namespace ProtonVPN.Core.Api.Contracts.ReportAnIssue
+{
+ public class IssueSuggestion
+ {
+ public string Text { get; set; }
+
+ [DefaultValue("")]
+ [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
+ public string Link { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/ReportAnIssueFormData.cs b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/ReportAnIssueFormData.cs
new file mode 100644
index 000000000..b7017eb06
--- /dev/null
+++ b/src/ProtonVPN.Core/Api/Contracts/ReportAnIssue/ReportAnIssueFormData.cs
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+namespace ProtonVPN.Core.Api.Contracts.ReportAnIssue
+{
+ public class ReportAnIssueFormData : BaseResponse
+ {
+ public List Categories { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Api/IApiClient.cs b/src/ProtonVPN.Core/Api/IApiClient.cs
index 58f19e83e..8e3829927 100644
--- a/src/ProtonVPN.Core/Api/IApiClient.cs
+++ b/src/ProtonVPN.Core/Api/IApiClient.cs
@@ -21,6 +21,7 @@
using System.Threading.Tasks;
using ProtonVPN.Core.Api.Certificates;
using ProtonVPN.Core.Api.Contracts;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
using ProtonVPN.Core.Api.Data;
using UserLocation = ProtonVPN.Core.Api.Contracts.UserLocation;
@@ -35,6 +36,7 @@ public interface IApiClient : IClientBase
Task> GetLogoutResponse();
Task> GetEventResponse(string lastId = default);
Task> GetServersAsync(string ip);
+ Task> GetReportAnIssueFormData();
Task> GetServerLoadsAsync(string ip);
Task> GetLocationDataAsync();
Task> ReportBugAsync(IEnumerable> fields, IEnumerable files);
diff --git a/src/ProtonVPN.Core/Api/ResponseCodes.cs b/src/ProtonVPN.Core/Api/ResponseCodes.cs
index e11f2d165..cb3b0241a 100644
--- a/src/ProtonVPN.Core/Api/ResponseCodes.cs
+++ b/src/ProtonVPN.Core/Api/ResponseCodes.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -30,5 +30,6 @@ public class ResponseCodes
public const int InvalidProfileIdOnDelete = 86063;
public const int ProfileNameConflict = 86065;
public const int HumanVerificationRequired = 9001;
+ public const int NoVpnConnectionsAssigned = 86300;
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Auth/AuthError.cs b/src/ProtonVPN.Core/Auth/AuthError.cs
new file mode 100644
index 000000000..559268a45
--- /dev/null
+++ b/src/ProtonVPN.Core/Auth/AuthError.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.Core.Auth
+{
+ public enum AuthError
+ {
+ None,
+ NoVpnAccess,
+ InvalidServerProof,
+ Unknown,
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Auth/AuthResult.cs b/src/ProtonVPN.Core/Auth/AuthResult.cs
new file mode 100644
index 000000000..323bf95b1
--- /dev/null
+++ b/src/ProtonVPN.Core/Auth/AuthResult.cs
@@ -0,0 +1,65 @@
+/*
+ * 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 ProtonVPN.Common.Abstract;
+using ProtonVPN.Common.Extensions;
+using ProtonVPN.Core.Api;
+using ProtonVPN.Core.Api.Contracts;
+
+namespace ProtonVPN.Core.Auth
+{
+ public class AuthResult : Result
+ {
+ protected internal AuthResult(AuthError value, bool success, string error) : base(value, success, error)
+ {
+ }
+
+ public static AuthResult Fail(AuthError authError)
+ {
+ return new(authError, false, string.Empty);
+ }
+
+ public new static AuthResult Fail(string error)
+ {
+ return new(AuthError.Unknown, false, error);
+ }
+
+ public static AuthResult Fail(ApiResponseResult apiResponseResult) where T : BaseResponse
+ {
+ if (apiResponseResult.Actions.IsNullOrEmpty())
+ {
+ return apiResponseResult.Value?.Code == ResponseCodes.NoVpnConnectionsAssigned
+ ? Fail(AuthError.NoVpnAccess)
+ : Fail(apiResponseResult.Error);
+ }
+
+ return Fail();
+ }
+
+ public static AuthResult Fail()
+ {
+ return new(AuthError.None, false, string.Empty);
+ }
+
+ public new static AuthResult Ok()
+ {
+ return new(AuthError.None, true, string.Empty);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Auth/UserAuth.cs b/src/ProtonVPN.Core/Auth/UserAuth.cs
index c6c5d9029..a16d4cfc9 100644
--- a/src/ProtonVPN.Core/Auth/UserAuth.cs
+++ b/src/ProtonVPN.Core/Auth/UserAuth.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -42,7 +42,7 @@ public class UserAuth
public UserAuth(IApiClient apiClient,
ILogger logger,
IUserStorage userStorage,
- ITokenStorage tokenStorage,
+ ITokenStorage tokenStorage,
IAuthCertificateManager authCertificateManager)
{
_tokenStorage = tokenStorage;
@@ -52,104 +52,99 @@ public UserAuth(IApiClient apiClient,
_userStorage = userStorage;
}
- public const int UserStatusVpnAccess = 1;
public bool LoggedIn { get; private set; }
public event EventHandler UserLoggedOut;
public event EventHandler UserLoggedIn;
public event EventHandler UserLoggingIn;
- public async Task> RefreshVpnInfo()
+ public async Task LoginUserAsync(string username, SecureString password)
{
- ApiResponseResult vpnInfo = await _apiClient.GetVpnInfoResponse();
- if (vpnInfo.Success)
+ _logger?.Info("Trying to login user");
+ UserLoggingIn?.Invoke(this, EventArgs.Empty);
+
+ AuthResult authResult = await AuthAsync(username, password);
+ if (authResult.Failure)
{
- if (!vpnInfo.Value.Vpn.Status.Equals(UserStatusVpnAccess))
- {
- return ApiResponseResult.Fail(vpnInfo.StatusCode, "User has no vpn access.");
- }
+ return authResult;
+ }
- _userStorage.StoreVpnInfo(vpnInfo.Value);
+ ApiResponseResult vpnInfoResult = await RefreshVpnInfo();
+ if (vpnInfoResult.Failure)
+ {
+ return AuthResult.Fail(vpnInfoResult);
}
- return vpnInfo;
+ _userStorage.StoreVpnInfo(vpnInfoResult.Value);
+ await InvokeUserLoggedInAsync(false);
+ return authResult;
}
- public async Task RefreshVpnInfo(Action onSuccess)
+ public async Task AuthAsync(string username, SecureString password)
{
- try
- {
- ApiResponseResult infoResult = await RefreshVpnInfo();
- if (infoResult.Success)
- {
- onSuccess(infoResult.Value);
- }
- }
- catch (HttpRequestException ex)
+ ApiResponseResult authInfoResponse =
+ await _apiClient.GetAuthInfoResponse(new AuthInfoRequestData { Username = username });
+ if (!authInfoResponse.Success)
{
- _logger.Error(ex.Message);
+ return AuthResult.Fail(authInfoResponse);
}
- }
- public async Task> LoginUserAsync(string username, SecureString password)
- {
- _logger?.Info("Trying to login user");
- UserLoggingIn?.Invoke(this, EventArgs.Empty);
+ SrpPInvoke.GoProofs proofs = SrpPInvoke.GenerateProofs(4, username, password, authInfoResponse.Value.Salt,
+ authInfoResponse.Value.Modulus, authInfoResponse.Value.ServerEphemeral);
- ApiResponseResult authResult = await AuthAsync(username, password);
- if (authResult.Failure)
+ AuthRequestData authRequestData = GetAuthRequestData(proofs, authInfoResponse.Value.SrpSession, username);
+ ApiResponseResult response = await _apiClient.GetAuthResponse(authRequestData);
+ if (response.Failure)
{
- return authResult;
+ return AuthResult.Fail(response);
}
- ApiResponseResult vpnInfo = await RefreshVpnInfo();
- if (vpnInfo.Success)
+ if (!Convert.ToBase64String(proofs.ExpectedServerProof).Equals(response.Value.ServerProof))
{
- _userStorage.StoreVpnInfo(vpnInfo.Value);
- await InvokeUserLoggedInAsync(false);
- return authResult;
+ return AuthResult.Fail(AuthError.InvalidServerProof);
}
- return ApiResponseResult.Fail(vpnInfo);
+ _userStorage.SaveUsername(username);
+ _tokenStorage.Uid = response.Value.Uid;
+ _tokenStorage.AccessToken = response.Value.AccessToken;
+ _tokenStorage.RefreshToken = response.Value.RefreshToken;
+
+ return AuthResult.Ok();
}
- public async Task> AuthAsync(string username, SecureString password)
+ private AuthRequestData GetAuthRequestData(SrpPInvoke.GoProofs proofs, string srpSession, string username)
{
- ApiResponseResult authInfo = await _apiClient.GetAuthInfoResponse(new AuthInfoRequestData
- {
- Username = username
- });
-
- if (!authInfo.Success)
- {
- return ApiResponseResult.Fail(authInfo.StatusCode, authInfo.Error);
- }
-
- SrpPInvoke.GoProofs proofs = SrpPInvoke.GenerateProofs(4, username, password, authInfo.Value.Salt, authInfo.Value.Modulus, authInfo.Value.ServerEphemeral);
- AuthRequestData authData = new AuthRequestData
+ return new AuthRequestData
{
ClientEphemeral = Convert.ToBase64String(proofs.ClientEphemeral),
ClientProof = Convert.ToBase64String(proofs.ClientProof),
- SrpSession = authInfo.Value.SrpSession,
+ SrpSession = srpSession,
TwoFactorCode = "",
Username = username
};
+ }
- ApiResponseResult response = await _apiClient.GetAuthResponse(authData);
- if (response.Success)
+ public async Task> RefreshVpnInfo()
+ {
+ ApiResponseResult vpnInfo = await _apiClient.GetVpnInfoResponse();
+ if (vpnInfo.Success)
+ {
+ _userStorage.StoreVpnInfo(vpnInfo.Value);
+ }
+ else if (vpnInfo.Value.Code == ResponseCodes.NoVpnConnectionsAssigned)
{
- if (!Convert.ToBase64String(proofs.ExpectedServerProof).Equals(response.Value.ServerProof))
- {
- return ApiResponseResult.Fail(0, "Invalid server proof.");
- }
-
- _userStorage.SaveUsername(username);
- _tokenStorage.Uid = response.Value.Uid;
- _tokenStorage.AccessToken = response.Value.AccessToken;
- _tokenStorage.RefreshToken = response.Value.RefreshToken;
+ ClearAuthData();
}
-
- return response;
+
+ return vpnInfo;
+ }
+
+ private void ClearAuthData()
+ {
+ _tokenStorage.Uid = string.Empty;
+ _tokenStorage.AccessToken = string.Empty;
+ _tokenStorage.RefreshToken = string.Empty;
+ _userStorage.SaveUsername(string.Empty);
}
public async Task InvokeAutoLoginEventAsync()
@@ -202,4 +197,4 @@ private async Task SendLogoutRequestAsync()
}
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Auth/UserValidator.cs b/src/ProtonVPN.Core/Auth/UserValidator.cs
index a4b7b3f39..8236f616b 100644
--- a/src/ProtonVPN.Core/Auth/UserValidator.cs
+++ b/src/ProtonVPN.Core/Auth/UserValidator.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -18,6 +18,7 @@
*/
using System.Threading.Tasks;
+using ProtonVPN.Common.Extensions;
using ProtonVPN.Core.Api;
using ProtonVPN.Core.Api.Contracts;
using ProtonVPN.Core.Settings;
@@ -35,25 +36,21 @@ public UserValidator(IUserStorage userStorage, UserAuth userAuth)
_userAuth = userAuth;
}
- public async Task> GetValidateResult()
+ public async Task GetValidateResult()
{
- if (PlanInfoExists())
- return ApiResponseResult.Ok(new BaseResponse());
-
- var vpnInfoResult = await _userAuth.RefreshVpnInfo();
- if (vpnInfoResult.Success)
+ if (!_userStorage.User().VpnPlan.IsNullOrEmpty())
{
- _userStorage.StoreVpnInfo(vpnInfoResult.Value);
- return ApiResponseResult.Ok(vpnInfoResult.Value);
+ return AuthResult.Ok();
}
- return ApiResponseResult.Fail(vpnInfoResult.StatusCode, vpnInfoResult.Error);
- }
+ ApiResponseResult vpnInfoResult = await _userAuth.RefreshVpnInfo();
+ if (vpnInfoResult.Failure)
+ {
+ return AuthResult.Fail(vpnInfoResult);
+ }
- private bool PlanInfoExists()
- {
- var user = _userStorage.User();
- return !string.IsNullOrEmpty(user.VpnPlan);
+ _userStorage.StoreVpnInfo(vpnInfoResult.Value);
+ return AuthResult.Ok();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/ISystemState.cs b/src/ProtonVPN.Core/OS/ISystemState.cs
similarity index 89%
rename from src/ProtonVPN.App/BugReporting/Diagnostic/ISystemState.cs
rename to src/ProtonVPN.Core/OS/ISystemState.cs
index d3daac977..5ca950061 100644
--- a/src/ProtonVPN.App/BugReporting/Diagnostic/ISystemState.cs
+++ b/src/ProtonVPN.Core/OS/ISystemState.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.Diagnostic
+namespace ProtonVPN.Core.OS
{
public interface ISystemState
{
diff --git a/src/ProtonVPN.App/BugReporting/Diagnostic/SystemState.cs b/src/ProtonVPN.Core/OS/SystemState.cs
similarity index 96%
rename from src/ProtonVPN.App/BugReporting/Diagnostic/SystemState.cs
rename to src/ProtonVPN.Core/OS/SystemState.cs
index 89bd05229..1ceead106 100644
--- a/src/ProtonVPN.App/BugReporting/Diagnostic/SystemState.cs
+++ b/src/ProtonVPN.Core/OS/SystemState.cs
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2020 Proton Technologies AG
+ * Copyright (c) 2021 Proton Technologies AG
*
* This file is part of ProtonVPN.
*
@@ -21,7 +21,7 @@
using System.Linq;
using Microsoft.Win32;
-namespace ProtonVPN.BugReporting.Diagnostic
+namespace ProtonVPN.Core.OS
{
public class SystemState : ISystemState
{
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/ProtonVPN.Core.csproj b/src/ProtonVPN.Core/ProtonVPN.Core.csproj
index 74c6975c9..a2606fa32 100644
--- a/src/ProtonVPN.Core/ProtonVPN.Core.csproj
+++ b/src/ProtonVPN.Core/ProtonVPN.Core.csproj
@@ -103,6 +103,10 @@
+
+
+
+
@@ -173,9 +177,11 @@
+
+
@@ -200,7 +206,13 @@
+
+
+
+
+
+
diff --git a/src/ProtonVPN.Core/ReportAnIssue/DefaultDataProvider.cs b/src/ProtonVPN.Core/ReportAnIssue/DefaultDataProvider.cs
new file mode 100644
index 000000000..a93c25717
--- /dev/null
+++ b/src/ProtonVPN.Core/ReportAnIssue/DefaultDataProvider.cs
@@ -0,0 +1,283 @@
+/*
+ * 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 ProtonVPN.Core.Api.Contracts.ReportAnIssue;
+
+namespace ProtonVPN.Core.ReportAnIssue
+{
+ internal class DefaultCategoryProvider
+ {
+ public static List GetCategories()
+ {
+ return new List
+ {
+ new()
+ {
+ Label = "Browsing speed",
+ SubmitLabel = "Browsing speed",
+ Suggestions =
+ new List
+ {
+ new()
+ {
+ Text = "Turn off Secure Core. It can sometimes slow down your connection.",
+ Link = "https://protonvpn.com/support/secure-core-vpn"
+ },
+ new()
+ {
+ Text = "Use another VPN protocol.",
+ Link = "https://protonvpn.com/support/how-to-change-vpn-protocols"
+ },
+ new()
+ {
+ Text = "Try a different server. Servers in nearby countries often have faster connection speeds."
+ }
+ },
+ InputFields = new List
+ {
+ new()
+ {
+ Label = "Network type",
+ SubmitLabel = "Network type",
+ Type = InputType.SingleLineInput,
+ Placeholder = "Home WiFi, School Wi-Fi, Work, Public Wi-Fi",
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What are you trying to do?",
+ SubmitLabel = "What are you trying to do?",
+ Type = InputType.MultiLineInput,
+ Placeholder = "Example: browse a website, upload pictures, download a PDF",
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What is the speed you are getting?",
+ SubmitLabel = "What is the speed you are getting?",
+ Type = InputType.SingleLineInput,
+ Placeholder = "Example: 20 KB in download",
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What is your connection speed without VPN?",
+ SubmitLabel = "What is your connection speed without VPN?",
+ Type = InputType.SingleLineInput,
+ Placeholder = "Example: 10 MB in download",
+ IsMandatory = true
+ }
+ }
+ },
+ new()
+ {
+ Label = "Connecting to VPN",
+ SubmitLabel = "Connecting to VPN",
+ Suggestions =
+ new List
+ {
+ new() { Text = "Try a different server. Servers in nearby countries often have faster connection speeds." },
+ new()
+ {
+ Text = "Switch to another protocol from the settings.",
+ Link = "https://protonvpn.com/support/how-to-change-vpn-protocols"
+ },
+ new()
+ {
+ Text = "Turn off any antivirus or firewall software. They could be interfering with your VPN connection."
+ }
+ },
+ InputFields = new List
+ {
+ new()
+ {
+ Label = "Network type",
+ SubmitLabel = "Network type",
+ Type = InputType.SingleLineInput,
+ Placeholder = "Home WiFi, School Wi-Fi, Work, Public Wi-Fi",
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What steps have you taken to try to connect to VPN?",
+ SubmitLabel = "What steps have you taken to try to connect to VPN?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "Have you tried any quick fixes? If so, which ones?",
+ SubmitLabel = "Have you tried any quick fixes? If so, which ones?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ }
+ }
+ },
+ new()
+ {
+ Label = "Weak or unstable connection",
+ SubmitLabel = "Weak or unstable connection",
+ Suggestions =
+ new List
+ {
+ new() { Text = "Try a different server. Servers in nearby countries often have faster connection speeds." },
+ new()
+ {
+ Text = "Use another VPN protocol.",
+ Link = "https://protonvpn.com/support/how-to-change-vpn-protocols"
+ },
+ new()
+ {
+ Text = "Turn off any antivirus or firewall software. They could be interfering with your VPN connection."
+ }
+ },
+ InputFields = new List
+ {
+ new()
+ {
+ Label = "Network type",
+ SubmitLabel = "Network type",
+ Type = InputType.SingleLineInput,
+ Placeholder = "Home WiFi, School Wi-Fi, Work, Public Wi-Fi",
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "Did you get an error message? If so, what did it say?",
+ SubmitLabel = "Did you get an error message? If so, what did it say?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What have you tried already from the suggestions?",
+ SubmitLabel = "What have you tried already from the suggestions?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ }
+ }
+ },
+ new()
+ {
+ Label = "Using the app",
+ SubmitLabel = "Using the app",
+ Suggestions =
+ new List
+ {
+ new() { Text = "Log out and log back in." },
+ new() { Text = "Restart the app." }
+ },
+ InputFields = new List
+ {
+ new()
+ {
+ Label = "What went wrong?",
+ SubmitLabel = "What went wrong?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What are the exact steps you performed? Include any error you experienced.",
+ SubmitLabel = "What are the exact steps you performed? Include any error you experienced.",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "Have you tried any quick fixes? If so, which ones?",
+ SubmitLabel = "Have you tried any quick fixes? If so, which ones?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ }
+ }
+ },
+ new()
+ {
+ Label = "Streaming",
+ SubmitLabel = "Streaming",
+ Suggestions =
+ new List
+ {
+ new()
+ {
+ Text = "Try a different PLUS server. Just so you know, streaming is only available on a PLUS subscription."
+ },
+ new()
+ {
+ Text = "Clear your cache. You can do this in your browser settings.",
+ Link = "https://protonvpn.com/support/clear-browser-cache-cookies"
+ },
+ new()
+ {
+ Text = "Try a different web browser."
+ },
+ new()
+ {
+ Text = "Turn off any ad blockers or proxies. If you use NetShield, set it to \"don’t block\"."
+ },
+ },
+ InputFields = new List
+ {
+ new()
+ {
+ Label = "Which streaming service are you trying to use?",
+ SubmitLabel = "Which streaming service are you trying to use?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true,
+ Placeholder = "Netflix, Disney+, HBO"
+ },
+ new()
+ {
+ Label = "Are you using a custom DNS, proxy, or NetShield?",
+ SubmitLabel = "Are you using a custom DNS, proxy, or NetShield?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ },
+ new()
+ {
+ Label = "What went wrong? Include any error messages you received.",
+ SubmitLabel = "What went wrong? Include any error messages you received.",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true
+ },
+ }
+ },
+ new()
+ {
+ Label = "Something else",
+ SubmitLabel = "Something else",
+ Suggestions = new List(),
+ InputFields = new List
+ {
+ new()
+ {
+ Label = "What went wrong?",
+ SubmitLabel = "What went wrong?",
+ Type = InputType.MultiLineInput,
+ IsMandatory = true,
+ Placeholder = "Please describe the problem in as much detail as you can. If there was an error message, let us know what it said."
+ }
+ }
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/ReportAnIssue/IReportAnIssueFormDataProvider.cs b/src/ProtonVPN.Core/ReportAnIssue/IReportAnIssueFormDataProvider.cs
new file mode 100644
index 000000000..af804c4df
--- /dev/null
+++ b/src/ProtonVPN.Core/ReportAnIssue/IReportAnIssueFormDataProvider.cs
@@ -0,0 +1,33 @@
+/*
+ * 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.Threading.Tasks;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
+
+namespace ProtonVPN.Core.ReportAnIssue
+{
+ public interface IReportAnIssueFormDataProvider
+ {
+ Task FetchData();
+ List GetCategories();
+ List GetSuggestions(string category);
+ List GetInputs(string categorySubmitName);
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/ReportAnIssue/InputType.cs b/src/ProtonVPN.Core/ReportAnIssue/InputType.cs
new file mode 100644
index 000000000..1aa2bded2
--- /dev/null
+++ b/src/ProtonVPN.Core/ReportAnIssue/InputType.cs
@@ -0,0 +1,27 @@
+/*
+ * 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.Core.ReportAnIssue
+{
+ public class InputType
+ {
+ public const string SingleLineInput = "TextSingleLine";
+ public const string MultiLineInput = "TextMultiLine";
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/ReportAnIssue/ReportAnIssueFormDataProvider.cs b/src/ProtonVPN.Core/ReportAnIssue/ReportAnIssueFormDataProvider.cs
new file mode 100644
index 000000000..fe45b842a
--- /dev/null
+++ b/src/ProtonVPN.Core/ReportAnIssue/ReportAnIssueFormDataProvider.cs
@@ -0,0 +1,108 @@
+/*
+ * 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.ComponentModel;
+using System.Linq;
+using System.Net.Http;
+using System.Threading.Tasks;
+using ProtonVPN.Common.Extensions;
+using ProtonVPN.Core.Api;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
+using ProtonVPN.Core.Settings;
+
+namespace ProtonVPN.Core.ReportAnIssue
+{
+ public class ReportAnIssueFormDataProvider : IReportAnIssueFormDataProvider, ISettingsAware
+ {
+ private readonly IApiClient _apiClient;
+ private readonly IAppSettings _appSettings;
+ private readonly List _validInputTypes = new() { InputType.SingleLineInput, InputType.MultiLineInput };
+ private List _categories = new();
+
+ public ReportAnIssueFormDataProvider(IApiClient apiClient, IAppSettings appSettings)
+ {
+ _apiClient = apiClient;
+ _appSettings = appSettings;
+ }
+
+ public async Task FetchData()
+ {
+ try
+ {
+ ApiResponseResult response = await _apiClient.GetReportAnIssueFormData();
+ if (response.Success && IsDataValid(response.Value.Categories))
+ {
+ _categories = response.Value.Categories;
+ _appSettings.ReportAnIssueFormData = response.Value.Categories;
+ }
+ else
+ {
+ LoadCategoriesFromCache();
+ }
+ }
+ catch (HttpRequestException)
+ {
+ LoadCategoriesFromCache();
+ }
+ }
+
+ public List GetCategories()
+ {
+ return _categories.ToList();
+ }
+
+ public List GetSuggestions(string category)
+ {
+ return _categories.FirstOrDefault(c => c.SubmitLabel == category)?.Suggestions;
+ }
+
+ public List GetInputs(string categorySubmitName)
+ {
+ return _categories.FirstOrDefault(c => c.SubmitLabel == categorySubmitName)?.InputFields;
+ }
+
+ public async void OnAppSettingsChanged(PropertyChangedEventArgs e)
+ {
+ if (e.PropertyName == nameof(IAppSettings.Language))
+ {
+ await FetchData();
+ }
+ }
+
+ private bool IsDataValid(IList categories)
+ {
+ return categories.All(category =>
+ !category.Label.IsNullOrEmpty() &&
+ !category.SubmitLabel.IsNullOrEmpty() &&
+ category.InputFields != null &&
+ !category.InputFields.Any(
+ input => input.Label.IsNullOrEmpty() ||
+ input.SubmitLabel.IsNullOrEmpty() ||
+ !_validInputTypes.Contains(input.Type)));
+ }
+
+ private void LoadCategoriesFromCache()
+ {
+ _categories = _appSettings.ReportAnIssueFormData.Count > 0
+ ? _appSettings.ReportAnIssueFormData
+ : DefaultCategoryProvider.GetCategories();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Core/Settings/IAppSettings.cs b/src/ProtonVPN.Core/Settings/IAppSettings.cs
index 70295ff20..53ec6dc14 100644
--- a/src/ProtonVPN.Core/Settings/IAppSettings.cs
+++ b/src/ProtonVPN.Core/Settings/IAppSettings.cs
@@ -24,11 +24,12 @@
using ProtonVPN.Common;
using ProtonVPN.Common.KillSwitch;
using ProtonVPN.Common.Networking;
-using ProtonVPN.Core.Announcements;
+using ProtonVPN.Core.Api.Contracts.ReportAnIssue;
using ProtonVPN.Core.Models;
using ProtonVPN.Core.Native.Structures;
using ProtonVPN.Core.Profiles.Cached;
using ProtonVPN.Core.Settings.Contracts;
+using Announcement = ProtonVPN.Core.Announcements.Announcement;
namespace ProtonVPN.Core.Settings
{
@@ -82,6 +83,7 @@ public interface IAppSettings
bool DoNotShowEnableSmartProtocolDialog { get; set; }
bool FeatureSmartProtocolWireGuardEnabled { get; set; }
IReadOnlyList Announcements { get; set; }
+ List ReportAnIssueFormData { get; set; }
int[] OpenVpnTcpPorts { get; set; }
int[] OpenVpnUdpPorts { get; set; }
int[] WireGuardPorts { get; set; }
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.Core/User/TruncatedLocation.cs b/src/ProtonVPN.Core/User/TruncatedLocation.cs
index c56fb2b8f..b0bed6695 100644
--- a/src/ProtonVPN.Core/User/TruncatedLocation.cs
+++ b/src/ProtonVPN.Core/User/TruncatedLocation.cs
@@ -39,14 +39,14 @@ public TruncatedLocation(IUserStorage userStorage)
public string Ip()
{
- var ip = _userStorage.Location().Ip;
+ string ip = _userStorage.Location().Ip;
if (string.IsNullOrEmpty(ip))
{
return string.Empty;
}
- var parts = ip.Split('.');
+ string[] parts = ip.Split('.');
if (parts.Length >= 3)
{
return string.Join(".", parts[0], parts[1], parts[2], 0);
diff --git a/src/ProtonVPN.Core/Vpn/VpnState.cs b/src/ProtonVPN.Core/Vpn/VpnState.cs
index 0e4dbbffd..e17e592fd 100644
--- a/src/ProtonVPN.Core/Vpn/VpnState.cs
+++ b/src/ProtonVPN.Core/Vpn/VpnState.cs
@@ -36,16 +36,17 @@ public VpnState(VpnStatus status, string entryIp, VpnProtocol vpnProtocol, OpenV
{
Status = status;
EntryIp = entryIp;
- NetworkAdapterType = networkAdapterType;
VpnProtocol = vpnProtocol;
+ NetworkAdapterType = networkAdapterType;
Label = label;
}
- public VpnState(VpnStatus status, Server server = null, VpnProtocol vpnProtocol = VpnProtocol.Smart)
+ public VpnState(VpnStatus status, Server server = null, VpnProtocol vpnProtocol = VpnProtocol.Smart, OpenVpnAdapter? networkAdapterType = null)
{
Status = status;
Server = server ?? Server.Empty();
VpnProtocol = vpnProtocol;
+ NetworkAdapterType = networkAdapterType;
}
public override string ToString()
diff --git a/src/ProtonVPN.Crypto/Ed25519Asn1KeyGenerator.cs b/src/ProtonVPN.Crypto/Ed25519Asn1KeyGenerator.cs
index 5111119cb..ca7b452ed 100644
--- a/src/ProtonVPN.Crypto/Ed25519Asn1KeyGenerator.cs
+++ b/src/ProtonVPN.Crypto/Ed25519Asn1KeyGenerator.cs
@@ -39,7 +39,9 @@ public AsymmetricKeyPair Generate()
byte[] asn1SecretKey = SecretKeyAsn1Header.Concat(secretKeyParams.GetEncoded()).ToArray();
byte[] asn1PublicKey = PublicKeyAsn1Header.Concat(publicKeyParams.GetEncoded()).ToArray();
- return new AsymmetricKeyPair(new SecretKey(asn1SecretKey, KeyAlgorithm.Ed25519), new PublicKey(asn1PublicKey, KeyAlgorithm.Ed25519));
+ return new AsymmetricKeyPair(
+ new SecretKey(asn1SecretKey, KeyAlgorithm.Ed25519),
+ new PublicKey(asn1PublicKey, KeyAlgorithm.Ed25519));
}
}
}
diff --git a/src/ProtonVPN.Resources/Automation/Config.xaml b/src/ProtonVPN.Resources/Automation/Config.xaml
index 6de66ba32..8499afb88 100644
--- a/src/ProtonVPN.Resources/Automation/Config.xaml
+++ b/src/ProtonVPN.Resources/Automation/Config.xaml
@@ -72,8 +72,6 @@ along with ProtonVPN. If not, see .
CreateAccountButton
AutoConnectComboBox
ReportBugEmailTextBlock
- ReportBugIssueTextBlock
- ReportBugStepsTextBlock
SecureCoreButton
SecureCoreOnButton
SecureCoreOffButton
@@ -84,4 +82,4 @@ along with ProtonVPN. If not, see .
IncludeModeRadioButton
SplitTunnelTextBlock
AddIpAddressButton
-
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Controls/ProgressBar.xaml b/src/ProtonVPN.Resources/Controls/ProgressBar.xaml
new file mode 100644
index 000000000..657cf6df9
--- /dev/null
+++ b/src/ProtonVPN.Resources/Controls/ProgressBar.xaml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Controls/ProgressBar.xaml.cs b/src/ProtonVPN.Resources/Controls/ProgressBar.xaml.cs
new file mode 100644
index 000000000..295159cb6
--- /dev/null
+++ b/src/ProtonVPN.Resources/Controls/ProgressBar.xaml.cs
@@ -0,0 +1,94 @@
+/*
+ * 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;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace ProtonVPN.Resource.Controls
+{
+ public partial class ProgressBar
+ {
+ public ProgressBar()
+ {
+ InitializeComponent();
+ }
+
+ public static readonly DependencyProperty StepProperty =
+ DependencyProperty.Register("Step", typeof(int), typeof(ProgressBar), new PropertyMetadata(1, Draw));
+
+ public static readonly DependencyProperty TotalStepsProperty =
+ DependencyProperty.Register("TotalSteps", typeof(int), typeof(ProgressBar), new PropertyMetadata(1, Draw));
+
+ public static readonly DependencyProperty BarColorProperty =
+ DependencyProperty.Register("BarColor", typeof(Brush), typeof(ProgressBar),
+ new PropertyMetadata(Brushes.Black, Draw));
+
+ public static readonly DependencyProperty CompleteBarColorProperty =
+ DependencyProperty.Register("CompleteBarColor", typeof(Brush), typeof(ProgressBar),
+ new PropertyMetadata(Brushes.White, Draw));
+
+ public int Step
+ {
+ get => (int)GetValue(StepProperty);
+ set => SetValue(StepProperty, value);
+ }
+
+ public int TotalSteps
+ {
+ get => (int)GetValue(TotalStepsProperty);
+ set => SetValue(TotalStepsProperty, value);
+ }
+
+ public Brush BarColor
+ {
+ get => (Brush)GetValue(BarColorProperty);
+ set => SetValue(BarColorProperty, value);
+ }
+
+ public Brush CompleteBarColor
+ {
+ get => (Brush)GetValue(CompleteBarColorProperty);
+ set => SetValue(CompleteBarColorProperty, value);
+ }
+
+ private static void Draw(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ ProgressBar control = (ProgressBar)d;
+ Grid grid = control.Grid;
+ GridLength width = new(1, GridUnitType.Star);
+ grid.Children.Clear();
+ grid.ColumnDefinitions.Clear();
+
+ for (int i = 1; i <= control.TotalSteps; i++)
+ {
+ grid.ColumnDefinitions.Add(new ColumnDefinition { Width = width });
+ grid.Children.Add(GetBar(i, control));
+ }
+ }
+
+ private static Border GetBar(int step, ProgressBar control)
+ {
+ Brush background = step <= control.Step ? control.CompleteBarColor : control.BarColor;
+ Border border = new Border { Background = background, Margin = new Thickness(2, 0, 2, 0) };
+ Grid.SetColumn(border, step - 1);
+ return border;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/ArrowLeft.xaml b/src/ProtonVPN.Resources/Icons/ArrowLeft.xaml
new file mode 100644
index 000000000..9a70bfdee
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/ArrowLeft.xaml
@@ -0,0 +1,31 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/ArrowLeft.xaml.cs b/src/ProtonVPN.Resources/Icons/ArrowLeft.xaml.cs
new file mode 100644
index 000000000..69b59dcd4
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/ArrowLeft.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.Resource.Icons
+{
+ public partial class ArrowLeft
+ {
+ public ArrowLeft()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml b/src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml
new file mode 100644
index 000000000..b50531e6f
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml
@@ -0,0 +1,140 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml.cs b/src/ProtonVPN.Resources/Icons/AssignVpnConnections.xaml.cs
new file mode 100644
index 000000000..8ca22b995
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/AssignVpnConnections.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.Resource.Icons
+{
+ public partial class AssignVpnConnections
+ {
+ public AssignVpnConnections()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/Bulb.xaml b/src/ProtonVPN.Resources/Icons/Bulb.xaml
new file mode 100644
index 000000000..ec2acc69c
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/Bulb.xaml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.App/BugReporting/Form.xaml.cs b/src/ProtonVPN.Resources/Icons/Bulb.xaml.cs
similarity index 85%
rename from src/ProtonVPN.App/BugReporting/Form.xaml.cs
rename to src/ProtonVPN.Resources/Icons/Bulb.xaml.cs
index d54e0d345..6dc26ddad 100644
--- a/src/ProtonVPN.App/BugReporting/Form.xaml.cs
+++ b/src/ProtonVPN.Resources/Icons/Bulb.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,13 +17,13 @@
* along with ProtonVPN. If not, see .
*/
-namespace ProtonVPN.BugReporting
+namespace ProtonVPN.Resource.Icons
{
- public partial class Form
+ public partial class Bulb
{
- public Form()
+ public Bulb()
{
InitializeComponent();
}
}
-}
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/Error.xaml b/src/ProtonVPN.Resources/Icons/Error.xaml
new file mode 100644
index 000000000..839c51f0e
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/Error.xaml
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/Error.xaml.cs b/src/ProtonVPN.Resources/Icons/Error.xaml.cs
new file mode 100644
index 000000000..f86218be7
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/Error.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.Resource.Icons
+{
+ public partial class Error
+ {
+ public Error()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/Thanks.xaml b/src/ProtonVPN.Resources/Icons/Thanks.xaml
new file mode 100644
index 000000000..9aef391fb
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/Thanks.xaml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/Thanks.xaml.cs b/src/ProtonVPN.Resources/Icons/Thanks.xaml.cs
new file mode 100644
index 000000000..ce6a5469f
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/Thanks.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.Resource.Icons
+{
+ public partial class Thanks
+ {
+ public Thanks()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/UpdateIcon.xaml b/src/ProtonVPN.Resources/Icons/UpdateIcon.xaml
new file mode 100644
index 000000000..02f64cbc4
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/UpdateIcon.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/Icons/UpdateIcon.xaml.cs b/src/ProtonVPN.Resources/Icons/UpdateIcon.xaml.cs
new file mode 100644
index 000000000..d7578f656
--- /dev/null
+++ b/src/ProtonVPN.Resources/Icons/UpdateIcon.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.Resource.Icons
+{
+ public partial class UpdateIcon
+ {
+ public UpdateIcon()
+ {
+ InitializeComponent();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ProtonVPN.Resources/ProtonVPN.Resource.csproj b/src/ProtonVPN.Resources/ProtonVPN.Resource.csproj
index b7f796e3d..99c179989 100644
--- a/src/ProtonVPN.Resources/ProtonVPN.Resource.csproj
+++ b/src/ProtonVPN.Resources/ProtonVPN.Resource.csproj
@@ -60,9 +60,30 @@
+
+ ProgressBar.xaml
+
ProtonAnimation.xaml
+
+ Error.xaml
+
+
+ Thanks.xaml
+
+
+ AssignVpnConnections.xaml
+
+
+ ArrowLeft.xaml
+
+
+ UpdateIcon.xaml
+
+
+ Bulb.xaml
+
WindowsClose.xaml
@@ -92,10 +113,38 @@
MSBuild:Compile
Designer
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
+
+ MSBuild:Compile
+ Designer
+
+
+ MSBuild:Compile
+ Designer
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
diff --git a/src/ProtonVPN.Resources/Styles/Window.xaml b/src/ProtonVPN.Resources/Styles/Window.xaml
index fba9f3b31..5ea766326 100644
--- a/src/ProtonVPN.Resources/Styles/Window.xaml
+++ b/src/ProtonVPN.Resources/Styles/Window.xaml
@@ -1,5 +1,5 @@