/*
* 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.Input;
using Microsoft.UI.Xaml.Media;
using ProtonVPN.Client.Contracts.Services.Lifecycle;
using ProtonVPN.Client.Core.Bases;
using ProtonVPN.Client.Core.Bases.ViewModels;
using ProtonVPN.Client.Core.Messages;
using ProtonVPN.Client.Core.Services.Activation;
using ProtonVPN.Client.Core.Services.Selection;
using ProtonVPN.Client.EventMessaging.Contracts;
using ProtonVPN.Client.Logic.Auth.Contracts;
using ProtonVPN.Client.Logic.Auth.Contracts.Messages;
using ProtonVPN.Client.Logic.Connection.Contracts;
using ProtonVPN.Client.Logic.Connection.Contracts.Messages;
using ProtonVPN.Client.Logic.Connection.Contracts.Models.Intents;
using ProtonVPN.Client.Logic.Recents.Contracts;
using ProtonVPN.Client.Logic.Servers.Cache;
using ProtonVPN.Common.Core.Helpers;
using ProtonVPN.StatisticalEvents.Contracts.Dimensions;
namespace ProtonVPN.Client.UI.Tray;
public partial class TrayIconComponentViewModel : ViewModelBase,
IEventMessageReceiver,
IEventMessageReceiver,
IEventMessageReceiver,
IEventMessageReceiver
{
private readonly IAppExitInvoker _appExitInvoker;
private readonly IServersCache _serversCache;
private readonly IMainWindowActivator _mainWindowActivator;
private readonly ITrayAppWindowActivator _trayAppWindowActivator;
private readonly IRecentConnectionsManager _recentConnectionsManager;
private readonly IConnectionManager _connectionManager;
private readonly IUserAuthenticator _userAuthenticator;
private readonly IApplicationIconSelector _applicationIconSelector;
public ImageSource IconSource => _applicationIconSelector.GetStatusIcon();
// GUID value generated by hashing "ProtonVPN" with SHA1 and taking first 16 bytes
public Guid TrayIconGuid => OSVersion.IsWindows11OrHigher() ? new("{50487227-4f3b-071d-baec-cd22bfff900d}") : Guid.Empty;
public string ApplicationName { get; } = App.APPLICATION_NAME;
public string OpenApplicationLabel => Localizer.GetFormat("System_Tray_Actions_OpenApplication", ApplicationName);
public TrayIconComponentViewModel(
IAppExitInvoker appExitInvoker,
IServersCache serversCache,
IMainWindowActivator mainWindowActivator,
ITrayAppWindowActivator trayAppWindowActivator,
IRecentConnectionsManager recentConnectionsManager,
IConnectionManager connectionManager,
IUserAuthenticator userAuthenticator,
IApplicationIconSelector applicationIconSelector,
IViewModelHelper viewModelHelper)
: base(viewModelHelper)
{
_appExitInvoker = appExitInvoker;
_serversCache = serversCache;
_mainWindowActivator = mainWindowActivator;
_trayAppWindowActivator = trayAppWindowActivator;
_recentConnectionsManager = recentConnectionsManager;
_connectionManager = connectionManager;
_userAuthenticator = userAuthenticator;
_applicationIconSelector = applicationIconSelector;
}
[RelayCommand]
public void ShowApplication()
{
_mainWindowActivator.Activate();
}
[RelayCommand]
public void ShowTrayApplication()
{
// When not logged in, skip the tray app and open the application instead
if (!_userAuthenticator.IsLoggedIn || _serversCache.HasNoServers())
{
ShowApplication();
return;
}
_trayAppWindowActivator.Activate();
}
[RelayCommand]
public async Task ExitApplicationAsync()
{
await _appExitInvoker.ExitWithConfirmationAsync();
}
[RelayCommand(CanExecute = nameof(CanConnect))]
public async Task ConnectAsync()
{
IConnectionIntent defaultConnectionIntent = _recentConnectionsManager.GetDefaultConnection();
await _connectionManager.ConnectAsync(VpnTriggerDimension.Tray, defaultConnectionIntent);
}
[RelayCommand(CanExecute = nameof(CanDisconnect))]
public async Task DisconnectAsync()
{
await _connectionManager.DisconnectAsync(VpnTriggerDimension.Tray);
}
public void Receive(ConnectionStatusChangedMessage message)
{
ExecuteOnUIThread(InvalidateTray);
}
public void Receive(LoggedInMessage message)
{
ExecuteOnUIThread(InvalidateTray);
}
public void Receive(LoggedOutMessage message)
{
ExecuteOnUIThread(InvalidateTray);
}
public void Receive(AppIconStatusChangedMessage message)
{
ExecuteOnUIThread(InvalidateTray);
}
protected override void OnLanguageChanged()
{
base.OnLanguageChanged();
OnPropertyChanged(nameof(OpenApplicationLabel));
}
private bool CanConnect()
{
return _userAuthenticator.IsLoggedIn
&& _connectionManager.IsDisconnected
&& _connectionManager.IsConnectAllowed
&& !_serversCache.HasNoServers();
}
private bool CanDisconnect()
{
return _userAuthenticator.IsLoggedIn
&& !_connectionManager.IsDisconnected;
}
private void InvalidateTray()
{
ConnectCommand.NotifyCanExecuteChanged();
DisconnectCommand.NotifyCanExecuteChanged();
OnPropertyChanged(nameof(IconSource));
}
}