/*
* 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.Dispatching;
using ProtonVPN.Client.Core.Bases;
using ProtonVPN.Client.Core.Bases.ViewModels;
using ProtonVPN.Client.EventMessaging.Contracts;
using ProtonVPN.Client.Localization.Extensions;
using ProtonVPN.Client.Logic.Connection.Contracts;
using ProtonVPN.Client.Logic.Connection.Contracts.Messages;
using ProtonVPN.Client.Services.PortForwarding;
namespace ProtonVPN.Client.UI.Main.Components;
public partial class ActivePortComponentViewModel : ActivatableViewModelBase,
IEventMessageReceiver,
IEventMessageReceiver,
IEventMessageReceiver
{
private const string PORT_NUMBER_PLACEHOLDER = "-";
private readonly IPortForwardingManager _portForwardingManager;
private readonly IPortForwardingClipboardService _portForwardingClipboardService;
private readonly IDispatcherTimer _timer;
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(CopyPortNumberCommand))]
[NotifyPropertyChangedFor(nameof(HasActivePortNumber))]
[NotifyPropertyChangedFor(nameof(IsFetchingPort))]
[NotifyPropertyChangedFor(nameof(IsPortUnavailable))]
[NotifyPropertyChangedFor(nameof(Header))]
private int? _activePortNumber;
[ObservableProperty]
private string _lastPortChangeTagline = string.Empty;
public string Header => HasActivePortNumber
? ActivePortNumber?.ToString() ?? PORT_NUMBER_PLACEHOLDER
: IsFetchingPort
? Localizer.Get("Settings_Connection_PortForwarding_Loading")
: Localizer.Get("Settings_Connection_PortForwarding_Unavailable");
public bool HasActivePortNumber => ActivePortNumber.HasValue;
public bool IsPortUnavailable => !_portForwardingManager.IsFetchingPort && !HasActivePortNumber;
public bool IsFetchingPort => !HasActivePortNumber && _portForwardingManager.IsFetchingPort;
public ActivePortComponentViewModel(
IPortForwardingManager portForwardingManager,
IPortForwardingClipboardService portForwardingClipboardService,
IViewModelHelper viewModelHelper)
: base(viewModelHelper)
{
_portForwardingManager = portForwardingManager;
_portForwardingClipboardService = portForwardingClipboardService;
_timer = UIThreadDispatcher.GetTimer(TimeSpan.FromSeconds(1));
_timer.Tick += OnTimerTick;
}
private void OnTimerTick(object? sender, object e)
{
InvalidateLastPortChangeTagline();
InvalidateTimerInterval();
}
[RelayCommand(CanExecute = nameof(CanCopyPortNumber))]
private async Task CopyPortNumberAsync()
{
int? activePortNumber = ActivePortNumber;
if (activePortNumber is not null)
{
await _portForwardingClipboardService.CopyActivePortToClipboardAsync();
}
}
private bool CanCopyPortNumber()
{
return HasActivePortNumber;
}
public void Receive(PortForwardingStatusChangedMessage message)
{
ExecuteOnUIThread(InvalidateStatusAndActivePortNumber);
}
public void Receive(PortForwardingPortChangedMessage message)
{
ExecuteOnUIThread(InvalidateStatusAndActivePortNumber);
}
public void Receive(ConnectionStatusChangedMessage message)
{
ExecuteOnUIThread(InvalidateStatusAndActivePortNumber);
}
protected override void OnActivated()
{
base.OnActivated();
InvalidateStatusAndActivePortNumber();
InvalidateTimer();
}
protected override void OnDeactivated()
{
base.OnDeactivated();
InvalidateTimer();
}
protected override void OnLanguageChanged()
{
base.OnLanguageChanged();
OnPropertyChanged(nameof(Header));
}
private void InvalidateStatusAndActivePortNumber()
{
ActivePortNumber = _portForwardingManager.ActivePort;
InvalidateTimer();
InvalidateLastPortChangeTagline();
OnPropertyChanged(nameof(Header));
OnPropertyChanged(nameof(HasActivePortNumber));
OnPropertyChanged(nameof(IsPortUnavailable));
OnPropertyChanged(nameof(IsFetchingPort));
}
private void InvalidateTimer()
{
if (IsActive && HasActivePortNumber)
{
if (!_timer.IsEnabled)
{
InvalidateTimerInterval();
_timer.Start();
}
}
else
{
if (_timer.IsEnabled)
{
_timer.Stop();
}
}
}
private void InvalidateTimerInterval()
{
DateTime? lastPortChangeTimeUtc = _portForwardingManager.LastPortChangeTimeUtc;
if (lastPortChangeTimeUtc.HasValue)
{
TimeSpan elapsedTime = DateTime.UtcNow - lastPortChangeTimeUtc.Value;
_timer.Interval = elapsedTime switch
{
TimeSpan when elapsedTime < TimeSpan.FromMinutes(1) => TimeSpan.FromSeconds(1),
TimeSpan when elapsedTime < TimeSpan.FromHours(1) => TimeSpan.FromSeconds(20),
TimeSpan when elapsedTime < TimeSpan.FromDays(1) => TimeSpan.FromMinutes(20),
_ => TimeSpan.FromHours(1)
};
}
else
{
_timer.Interval = TimeSpan.FromSeconds(1);
}
}
private void InvalidateLastPortChangeTagline()
{
DateTime? lastPortChangeTimeUtc = _portForwardingManager.LastPortChangeTimeUtc;
if (lastPortChangeTimeUtc.HasValue)
{
LastPortChangeTagline = Localizer.GetFormat("Settings_Connection_PortForwarding_LastChanged", Localizer.GetFormattedElapsedTime(lastPortChangeTimeUtc.Value));
}
else
{
LastPortChangeTagline = string.Empty;
}
}
}