/* * Copyright (c) 2024 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 ProtonVPN.Client.Common.UI.Controls.Map; using ProtonVPN.Client.Contracts.Messages; 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.Localization.Extensions; using ProtonVPN.Client.Logic.Connection.Contracts; using ProtonVPN.Client.Logic.Connection.Contracts.Enums; using ProtonVPN.Client.Logic.Connection.Contracts.Messages; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents; using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents.Locations.Countries; using ProtonVPN.Client.Logic.Servers.Cache; using ProtonVPN.Client.Logic.Servers.Contracts.Messages; using ProtonVPN.Client.Settings.Contracts; using ProtonVPN.Client.Settings.Contracts.Messages; using ProtonVPN.StatisticalEvents.Contracts; using ProtonVPN.StatisticalEvents.Contracts.Dimensions; namespace ProtonVPN.Client.UI.Main.Map; public partial class MapComponentViewModel : ViewModelBase, IEventMessageReceiver, IEventMessageReceiver, IEventMessageReceiver, IEventMessageReceiver { private readonly ISettings _settings; private readonly IServersCache _serversCache; private readonly IConnectionManager _connectionManager; private readonly ICoordinatesProvider _coordinatesProvider; private readonly IUpsellCarouselWindowActivator _upsellCarouselWindowActivator; private readonly IMainWindowOverlayActivator _mainWindowOverlayActivator; [ObservableProperty] private bool _isMainWindowVisible; [ObservableProperty] private List _countries = []; [ObservableProperty] private Country? _currentCountry; public bool IsConnecting => _connectionManager.IsConnecting; public bool IsConnected => _connectionManager.IsConnected; public bool IsDisconnected => _connectionManager.IsDisconnected; public MapComponentViewModel( ISettings settings, IServersCache serversCache, IConnectionManager connectionManager, ICoordinatesProvider coordinatesProvider, IUpsellCarouselWindowActivator upsellCarouselWindowActivator, IMainWindowOverlayActivator mainWindowOverlayActivator, IViewModelHelper viewModelHelper) : base(viewModelHelper) { _settings = settings; _serversCache = serversCache; _connectionManager = connectionManager; _coordinatesProvider = coordinatesProvider; _upsellCarouselWindowActivator = upsellCarouselWindowActivator; _mainWindowOverlayActivator = mainWindowOverlayActivator; InvalidateActiveCountry(); } public void Receive(ConnectionStatusChangedMessage message) { ExecuteOnUIThread(() => { InvalidateActiveCountry(); OnPropertyChanged(nameof(IsConnecting)); OnPropertyChanged(nameof(IsConnected)); OnPropertyChanged(nameof(IsDisconnected)); }); } public void Receive(SettingChangedMessage message) { ExecuteOnUIThread(() => { if (message.PropertyName == nameof(ISettings.DeviceLocation)) { InvalidateActiveCountry(); } }); } public void Receive(MainWindowVisibilityChangedMessage message) { ExecuteOnUIThread(() => IsMainWindowVisible = message.IsMainWindowVisible); } public void Receive(ServerListChangedMessage message) { ExecuteOnUIThread(() => { InvalidateCountries(); InvalidateActiveCountry(); }); } protected override void OnLanguageChanged() { ExecuteOnUIThread(() => { InvalidateCountries(); InvalidateActiveCountry(); }); } private void InvalidateActiveCountry() { switch (_connectionManager.ConnectionStatus) { case ConnectionStatus.Connected: case ConnectionStatus.Connecting: { string? countryCode = _connectionManager.CurrentConnectionDetails?.ExitCountryCode; if (!string.IsNullOrEmpty(countryCode)) { CurrentCountry = Countries.FirstOrDefault(c => c.Code == countryCode); } break; } case ConnectionStatus.Disconnected: CurrentCountry = Countries.FirstOrDefault(c => c.Code == _settings.DeviceLocation?.CountryCode); break; } } private void InvalidateCountries() { Countries = _serversCache.Countries .Select(c => { (double Latitude, double Longitude)? coordinates = _coordinatesProvider.GetCoordinates(c); return coordinates != null ? new Country { Name = Localizer.GetCountryName(c.Code), Code = c.Code, Latitude = coordinates.Value.Latitude, Longitude = coordinates.Value.Longitude, IsUnderMaintenance = c.IsStandardUnderMaintenance } : null; }) .OfType() .ToList(); } [RelayCommand] private Task ConnectAsync(Country country) { if (!_settings.VpnPlan.IsPaid) { return _upsellCarouselWindowActivator.ActivateAsync(new UpsellModalContext(ModalSource.Countries, ModalTrigger.Map, country?.Code)); } if (country == null || country.IsUnderMaintenance) { return _mainWindowOverlayActivator.ShowMessageAsync(new() { Title = Localizer.Get("Connections_Country_UnderMaintenance_Title"), Message = Localizer.Get("Connections_Country_UnderMaintenance"), PrimaryButtonText = Localizer.Get("Common_Actions_GotIt"), }); } return _connectionManager.ConnectAsync(VpnTriggerDimension.Map, new ConnectionIntent(SingleCountryLocationIntent.From(country.Code))); } }