/* * Copyright (c) 2025 Proton 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 CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.UI.Xaml.Controls; using ProtonVPN.Client.Common.Collections; using ProtonVPN.Client.Common.Models; using ProtonVPN.Client.Contracts.Services.Browsing; using ProtonVPN.Client.Core.Bases; using ProtonVPN.Client.Core.Bases.ViewModels; using ProtonVPN.Client.Core.Services.Activation; using ProtonVPN.Client.EventMessaging.Contracts; using ProtonVPN.Client.Factories; using ProtonVPN.Client.Logic.Profiles.Contracts.Models; using ProtonVPN.Client.Logic.Users.Contracts.Messages; using ProtonVPN.Client.Models.Settings; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Client.Settings.Contracts.Enums; using ProtonVPN.Client.Settings.Contracts.Messages; using ProtonVPN.Client.Settings.Contracts.Observers; using ProtonVPN.Client.Settings.Contracts.RequiredReconnections; using ProtonVPN.Client.UI.Main.Profiles.Contracts; using ProtonVPN.Common.Core.Networking; namespace ProtonVPN.Client.UI.Main.Profiles.Components; public partial class ProfileSettingsSelectorViewModel : ViewModelBase, IProfileSettingsSelector, IEventMessageReceiver, IEventMessageReceiver, IEventMessageReceiver { private readonly ISettings _settings; private readonly ICommonItemFactory _commonItemFactory; private readonly IMainWindowOverlayActivator _mainWindowOverlayActivator; private readonly IUrlsBrowser _urlsBrowser; private readonly IFeatureFlagsObserver _featureFlagsObserver; private IProfileSettings _originalProfileSettings = ProfileSettings.Default; [ObservableProperty] private ProtocolItem? _selectedProtocol; [ObservableProperty] private NetShieldModeItem? _selectedNetShieldMode; [ObservableProperty] private NatTypeItem? _selectedNatType; [ObservableProperty] private PortForwardingItem? _selectedPortForwardingState; [ObservableProperty] private bool _isCustomSettingsSectionExpanded; public string NetShieldLearnMoreUrl => _urlsBrowser.NetShieldLearnMore; public SmartObservableCollection Protocols { get; } = []; public SmartObservableCollection NetShieldModes { get; } = []; public SmartObservableCollection NatTypes { get; } = []; public SmartObservableCollection PortForwardingStates { get; } = []; protected bool ProtocolHasChanged => _originalProfileSettings.VpnProtocol != SelectedProtocol?.Protocol; protected bool NetShieldStateHasChanged => _originalProfileSettings.IsNetShieldEnabled != SelectedNetShieldMode?.IsEnabled; protected bool NetShieldModeHasChanged => _originalProfileSettings.NetShieldMode != SelectedNetShieldMode?.Mode; protected bool NetShieldHasChanged => NetShieldStateHasChanged || NetShieldModeHasChanged; protected bool NatTypeHasChanged => _originalProfileSettings.NatType != SelectedNatType?.NatType; protected bool PortForwardingHasChanged => _originalProfileSettings.IsPortForwardingEnabled != SelectedPortForwardingState?.IsEnabled; public bool IsNetShieldLevelThreeAvailable => !_settings.VpnPlan.IsB2B; public bool AreProtonProtocolsVisible => _featureFlagsObserver.IsProTunEnabled && _settings.AreProtonProtocolsEnabled; public ProfileSettingsSelectorViewModel( IViewModelHelper viewModelHelper, ISettings settings, ICommonItemFactory commonItemFactory, IRequiredReconnectionSettings requiredReconnectionSettings, IMainWindowOverlayActivator mainWindowOverlayActivator, IUrlsBrowser urlsBrowser, IFeatureFlagsObserver featureFlagsObserver) : base(viewModelHelper) { _settings = settings; _commonItemFactory = commonItemFactory; _mainWindowOverlayActivator = mainWindowOverlayActivator; _urlsBrowser = urlsBrowser; _featureFlagsObserver = featureFlagsObserver; } public IProfileSettings GetProfileSettings() { return new ProfileSettings() { VpnProtocol = SelectedProtocol?.Protocol ?? DefaultSettings.VpnProtocol, IsNetShieldEnabled = SelectedNetShieldMode?.IsEnabled ?? DefaultSettings.IsNetShieldEnabled(true), NetShieldMode = SelectedNetShieldMode?.Mode ?? DefaultSettings.NetShieldMode, NatType = SelectedNatType?.NatType ?? DefaultSettings.NatType, IsPortForwardingEnabled = SelectedPortForwardingState?.IsEnabled ?? DefaultSettings.IsPortForwardingEnabled }; } public void SetProfileSettings(IProfileSettings settings) { IsCustomSettingsSectionExpanded = false; _originalProfileSettings = settings ?? ProfileSettings.Default; InvalidateCollections(); SelectedProtocol = Protocols.FirstOrDefault(p => p.Protocol == _originalProfileSettings.VpnProtocol); SelectedNetShieldMode = _originalProfileSettings.IsNetShieldEnabled ? NetShieldModes.FirstOrDefault(p => p.IsEnabled && _originalProfileSettings.NetShieldMode == p.Mode) : NetShieldModes.FirstOrDefault(p => !p.IsEnabled); SelectedNatType = NatTypes.FirstOrDefault(p => p.NatType == _originalProfileSettings.NatType); SelectedPortForwardingState = PortForwardingStates.First(m => m.IsEnabled == _originalProfileSettings.IsPortForwardingEnabled); } public bool HasChanged() { return ProtocolHasChanged || NetShieldHasChanged || NatTypeHasChanged || PortForwardingHasChanged; } public bool IsReconnectionRequired() { return ProtocolHasChanged || (NetShieldStateHasChanged && _settings.IsCustomDnsServersEnabled); } public void Receive(VpnPlanChangedMessage message) { ExecuteOnUIThread(() => { OnPropertyChanged(nameof(IsNetShieldLevelThreeAvailable)); }); } public void Receive(FeatureFlagsChangedMessage message) { ExecuteOnUIThread(() => { OnPropertyChanged(nameof(AreProtonProtocolsVisible)); }); } public void Receive(SettingChangedMessage message) { if (message.PropertyName == nameof(ISettings.AreProtonProtocolsEnabled)) { ExecuteOnUIThread(() => { OnPropertyChanged(nameof(AreProtonProtocolsVisible)); }); } } private static IEnumerable GetProtocolsByOrder() { yield return VpnProtocol.Smart; yield return VpnProtocol.WireGuardUdp; yield return VpnProtocol.ProTunUdp; yield return VpnProtocol.WireGuardTcp; yield return VpnProtocol.WireGuardTls; yield return VpnProtocol.ProTunTcp; yield return VpnProtocol.ProTunTls; yield return VpnProtocol.OpenVpnUdp; yield return VpnProtocol.OpenVpnTcp; } private static IEnumerable GetNetShieldModesByOrder() { yield return null; yield return NetShieldMode.BlockMalwareOnly; yield return NetShieldMode.BlockAdsMalwareTrackers; yield return NetShieldMode.BlockAdsMalwareTrackersAdultContent; } private static IEnumerable GetNatTypesByOrder() { yield return NatType.Strict; yield return NatType.Moderate; } private static IEnumerable GetPortForwardingModesByOrder() { yield return false; yield return true; } private void InvalidateCollections() { Protocols.Reset(GetProtocolsByOrder().Select(_commonItemFactory.GetProtocol)); NetShieldModes.Reset(GetNetShieldModesByOrder().Select(_commonItemFactory.GetNetShieldMode)); NatTypes.Reset(GetNatTypesByOrder().Select(_commonItemFactory.GetNatType)); PortForwardingStates.Reset(GetPortForwardingModesByOrder().Select(_commonItemFactory.GetPortForwardingMode)); } [RelayCommand] private void ToggleExpander() { IsCustomSettingsSectionExpanded = !IsCustomSettingsSectionExpanded; } [RelayCommand] private Task DisableNetShieldAsync() { return TryChangeNetShieldModeAsync(false); } [RelayCommand] private Task EnableNetShieldLevelOneAsync() { return TryChangeNetShieldModeAsync(true, NetShieldMode.BlockMalwareOnly); } [RelayCommand] private Task EnableNetShieldLevelTwoAsync() { return TryChangeNetShieldModeAsync(true, NetShieldMode.BlockAdsMalwareTrackers); } [RelayCommand] private Task EnableNetShieldLevelThreeAsync() { return TryChangeNetShieldModeAsync(true, NetShieldMode.BlockAdsMalwareTrackersAdultContent); } private async Task TryChangeNetShieldModeAsync(bool isEnabled, NetShieldMode? netShieldMode = null) { if (isEnabled && SelectedNetShieldMode?.IsEnabled != isEnabled && _settings.IsCustomDnsServersEnabled) { ContentDialogResult result = await _mainWindowOverlayActivator.ShowMessageAsync(new() { Title = Localizer.Get("Settings_Connection_NetShield_Conflict_Title"), Message = Localizer.Get("Profile_NetShield_Conflict_Description"), PrimaryButtonText = Localizer.Get("Common_Actions_Enable"), CloseButtonText = Localizer.Get("Common_Actions_Cancel"), MessageType = DialogMessageType.RichText, TrailingInlineButton = new() { Text = Localizer.Get("Common_Links_LearnMore"), Url = _urlsBrowser.NetShieldLearnMore, }, }); if (result != ContentDialogResult.Primary) { return false; } } SelectedNetShieldMode = isEnabled ? NetShieldModes.FirstOrDefault(nsm => nsm.IsEnabled && nsm.Mode == netShieldMode) : NetShieldModes.FirstOrDefault(nsm => !nsm.IsEnabled); return true; } [RelayCommand] private Task DisablePortForwardingAsync() { return TryChangePortForwardingAsync(false); } [RelayCommand] private Task EnablePortForwardingAsync() { return TryChangePortForwardingAsync(true); } private async Task TryChangePortForwardingAsync(bool isEnabled) { if (isEnabled && SelectedPortForwardingState?.IsEnabled != isEnabled && SelectedNatType?.NatType == NatType.Moderate) { ContentDialogResult result = await _mainWindowOverlayActivator.ShowMessageAsync(new() { Title = Localizer.Get("Settings_Connection_PortForwarding_Conflict_Title"), Message = Localizer.Get("Profile_PortForwarding_Conflict_Description"), PrimaryButtonText = Localizer.Get("Common_Actions_Enable"), CloseButtonText = Localizer.Get("Common_Actions_Cancel"), MessageType = DialogMessageType.RichText, TrailingInlineButton = new() { Text = Localizer.Get("Common_Links_LearnMore"), Url = _urlsBrowser.PortForwardingLearnMore, }, }); if (result != ContentDialogResult.Primary) { return false; } SelectedNatType = NatTypes.FirstOrDefault(nt => nt.NatType == NatType.Strict); } SelectedPortForwardingState = PortForwardingStates.First(pfs => pfs.IsEnabled == isEnabled); return true; } [RelayCommand] private Task EnableStrictNatAsync() { return TryChangeNatTypeAsync(NatType.Strict); } [RelayCommand] private Task EnableModerateNatAsync() { return TryChangeNatTypeAsync(NatType.Moderate); } private async Task TryChangeNatTypeAsync(NatType natType) { if (natType == NatType.Moderate && SelectedNatType?.NatType != natType && SelectedPortForwardingState?.IsEnabled == true) { ContentDialogResult result = await _mainWindowOverlayActivator.ShowMessageAsync(new() { Title = Localizer.Get("Settings_Connection_Advanced_NatType_Conflict_Title"), Message = Localizer.Get("Profile_NatType_Conflict_Description"), PrimaryButtonText = Localizer.Get("Common_Actions_Enable"), CloseButtonText = Localizer.Get("Common_Actions_Cancel"), MessageType = DialogMessageType.RichText, TrailingInlineButton = new() { Text = Localizer.Get("Common_Links_LearnMore"), Url = _urlsBrowser.NatTypeLearnMore, }, }); if (result != ContentDialogResult.Primary) { return false; } SelectedPortForwardingState = PortForwardingStates.First(s => !s.IsEnabled); } SelectedNatType = NatTypes.FirstOrDefault(nt => nt.NatType == natType); return true; } [RelayCommand] private void SelectSmartProtocol() { SelectProtocol(VpnProtocol.Smart); } [RelayCommand] private void SelectWireGuardUdpProtocol() { SelectProtocol(VpnProtocol.WireGuardUdp); } [RelayCommand] private void SelectProTunUdpProtocol() { SelectProtocol(VpnProtocol.ProTunUdp); } [RelayCommand] private void SelectProTunTcpProtocol() { SelectProtocol(VpnProtocol.ProTunTcp); } [RelayCommand] private void SelectProTunTlsProtocol() { SelectProtocol(VpnProtocol.ProTunTls); } [RelayCommand] private void SelectWireGuardTcpProtocol() { SelectProtocol(VpnProtocol.WireGuardTcp); } [RelayCommand] private void SelectWireGuardTlsProtocol() { SelectProtocol(VpnProtocol.WireGuardTls); } [RelayCommand] private void SelectOpenVpnUdpProtocol() { SelectProtocol(VpnProtocol.OpenVpnUdp); } [RelayCommand] private void SelectOpenVpnTcpProtocol() { SelectProtocol(VpnProtocol.OpenVpnTcp); } private void SelectProtocol(VpnProtocol protocol) { SelectedProtocol = Protocols.FirstOrDefault(p => p.Protocol == protocol); } }