Added timer blocking

This commit is contained in:
Artem Bugaev
2026-03-01 17:28:19 +03:00
parent 2aeb67d14b
commit d77a39b98c
3 changed files with 106 additions and 4 deletions

View File

@@ -1,13 +1,19 @@
using System;
using System.Threading.Tasks;
using Avalonia.Threading;
using KeyKeeper.PasswordStore;
namespace KeyKeeper.ViewModels;
public partial class RepositoryWindowViewModel : ViewModelBase
{
private static readonly TimeSpan LockTimeout = TimeSpan.FromMinutes(5);
private object currentPage;
private IPassStore passStore;
private DispatcherTimer? _lockTimer;
private DateTime _timerStart;
private string _lockTimerDisplay = string.Empty;
public Func<string, Task> ShowErrorPopup;
@@ -17,6 +23,12 @@ public partial class RepositoryWindowViewModel : ViewModelBase
set { currentPage = value; OnPropertyChanged(nameof(CurrentPage)); }
}
public string LockTimerDisplay
{
get => _lockTimerDisplay;
private set { _lockTimerDisplay = value; OnPropertyChanged(nameof(LockTimerDisplay)); }
}
public RepositoryWindowViewModel(IPassStore store)
{
passStore = store;
@@ -31,13 +43,70 @@ public partial class RepositoryWindowViewModel : ViewModelBase
SwitchToLocked();
}
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> (<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>).
/// </summary>
public void ResetLockTimer()
{
if (_lockTimer != null && _lockTimer.IsEnabled)
_timerStart = DateTime.UtcNow;
}
private void SwitchToUnlocked()
{
CurrentPage = new UnlockedRepositoryViewModel(passStore);
StartLockTimer();
}
private void SwitchToLocked()
{
StopLockTimer();
CurrentPage = new LockedRepositoryViewModel(passStore, this);
}
private void StartLockTimer()
{
StopLockTimer();
_timerStart = DateTime.UtcNow;
_lockTimer = new DispatcherTimer
{
Interval = TimeSpan.FromSeconds(1)
};
_lockTimer.Tick += OnLockTimerTick;
_lockTimer.Start();
UpdateTimerDisplay();
}
private void StopLockTimer()
{
if (_lockTimer != null)
{
_lockTimer.Tick -= OnLockTimerTick;
_lockTimer.Stop();
_lockTimer = null;
}
LockTimerDisplay = string.Empty;
}
private void OnLockTimerTick(object? sender, EventArgs e)
{
var elapsed = DateTime.UtcNow - _timerStart;
var remaining = LockTimeout - elapsed;
if (remaining <= TimeSpan.Zero)
{
StopLockTimer();
passStore.Lock();
UpdateLockStatus();
return;
}
UpdateTimerDisplay(remaining);
}
private void UpdateTimerDisplay(TimeSpan? remaining = null)
{
var r = remaining ?? LockTimeout;
LockTimerDisplay = $"{r:mm\\:ss}";
}
}

View File

@@ -25,7 +25,30 @@
FontSize="32"
FontWeight="Bold"
HorizontalAlignment="Left"
Margin="0,0,0,20"/>
Margin="0,0,0,8"/>
<!-- Таймер блокировки под надписью KeyKeeper -->
<Border Background="#CC000000"
CornerRadius="6"
Padding="8,4"
HorizontalAlignment="Left"
Margin="0,0,0,12"
IsVisible="{Binding $parent[Window].DataContext.LockTimerDisplay,
Converter={x:Static StringConverters.IsNotNullOrEmpty}}">
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
<!-- Иконка замка (Material Design lock outline) -->
<Path Fill="White"
VerticalAlignment="Center"
Width="13" Height="13"
Stretch="Uniform"
Data="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
<TextBlock Text="{Binding $parent[Window].DataContext.LockTimerDisplay}"
Foreground="White"
FontSize="13"
FontWeight="SemiBold"
VerticalAlignment="Center"/>
</StackPanel>
</Border>
<!-- Рамочка -->
<Border BorderBrush="Gray"

View File

@@ -10,7 +10,7 @@ using Avalonia.Controls.Presenters;
namespace KeyKeeper.Views;
public partial class RepositoryWindow: Window
public partial class RepositoryWindow : Window
{
public RepositoryWindow(RepositoryWindowViewModel model)
{
@@ -21,10 +21,19 @@ public partial class RepositoryWindow: Window
await new ErrorDialog(message).ShowDialog(this);
};
}
protected override void OnOpened(EventArgs e)
{
base.OnOpened(e);
AddHandler(PointerMovedEvent, OnUserActivity, RoutingStrategies.Tunnel);
AddHandler(PointerPressedEvent, OnUserActivity, RoutingStrategies.Tunnel);
AddHandler(KeyDownEvent, OnUserActivity, RoutingStrategies.Tunnel);
}
private void OnUserActivity(object? sender, RoutedEventArgs e)
{
if (DataContext is RepositoryWindowViewModel vm)
vm.ResetLockTimer();
}
private async void AddEntryButton_Click(object sender, RoutedEventArgs args)
@@ -59,7 +68,8 @@ public partial class RepositoryWindow: Window
}
}
private void EntryContextMenuItem_Click(object sender, RoutedEventArgs args) {
private void EntryContextMenuItem_Click(object sender, RoutedEventArgs args)
{
if (args.Source is StyledElement s)
{
if (s.DataContext is PassStoreEntryPassword pwd)