From 4fc8f316eec0dd3f17a003d61898a08c6436edad Mon Sep 17 00:00:00 2001 From: Slavasil Date: Fri, 8 May 2026 16:32:25 +0300 Subject: [PATCH] add lock timer setting --- src/KeyKeeper/AppSettings.cs | 5 +- src/KeyKeeper/SettingsWindow.cs | 51 ++++++++- .../ViewModels/RepositoryWindowViewModel.cs | 6 +- src/KeyKeeper/Views/MainWindow.axaml.cs | 107 ++++++++++++++---- 4 files changed, 141 insertions(+), 28 deletions(-) diff --git a/src/KeyKeeper/AppSettings.cs b/src/KeyKeeper/AppSettings.cs index 24af0f8..19ff4d2 100644 --- a/src/KeyKeeper/AppSettings.cs +++ b/src/KeyKeeper/AppSettings.cs @@ -9,6 +9,7 @@ public static class AppSettings private static readonly string FilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "KeyKeeper", "settings.json"); public static bool ExitOnRepositoryClose { get; set; } = false; + public static int LockTimerMinutes { get; set; } = 5; // Сохранение в файл public static void Save() @@ -17,7 +18,7 @@ public static class AppSettings if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory); - var data = new { ExitOnRepositoryClose }; + var data = new { ExitOnRepositoryClose, LockTimerMinutes }; string json = JsonSerializer.Serialize(data); File.WriteAllText(FilePath, json); } @@ -34,6 +35,7 @@ public static class AppSettings if (data != null) { ExitOnRepositoryClose = data.ExitOnRepositoryClose; + LockTimerMinutes = data.LockTimerMinutes ?? 5; } } catch { /* Если файл поврежден, просто используем значения по умолчанию */ } @@ -43,5 +45,6 @@ public static class AppSettings private class SettingsData { public bool ExitOnRepositoryClose { get; set; } + public int? LockTimerMinutes { get; set; } } } diff --git a/src/KeyKeeper/SettingsWindow.cs b/src/KeyKeeper/SettingsWindow.cs index 7009de0..84fac4d 100644 --- a/src/KeyKeeper/SettingsWindow.cs +++ b/src/KeyKeeper/SettingsWindow.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System; +using Avalonia; using Avalonia.Controls; using Avalonia.Layout; using Avalonia.Media; @@ -49,14 +50,60 @@ public class SettingsWindow : Window exitOnCloseCheckBox.IsCheckedChanged += (s, e) => { AppSettings.ExitOnRepositoryClose = exitOnCloseCheckBox.IsChecked ?? false; - AppSettings.Save(); }; + // Настройка таймера блокировки + var lockTimerDurationRow = new StackPanel + { + Orientation = Orientation.Horizontal, + Spacing = 12, + }; + + var lockTimerDurationLabel1 = new TextBlock + { + Text = "Lock the vault after", + VerticalAlignment = VerticalAlignment.Center, + }; + + var lockTimerDurationInput = new NumericUpDown + { + Value = AppSettings.LockTimerMinutes, + Increment = 1, + Minimum = 1, + Maximum = 90, + ClipValueToMinMax = true, + Width = 120, + }; + + var lockTimerDurationLabel2 = new TextBlock + { + Text = "minutes of inactivity", + VerticalAlignment = VerticalAlignment.Center, + }; + + lockTimerDurationInput.ValueChanged += (_, _) => + { + Console.WriteLine($"Set timer to {lockTimerDurationInput.Value} minutes"); + AppSettings.LockTimerMinutes = (int)(lockTimerDurationInput.Value ?? 5m); + }; + + lockTimerDurationRow.Children.Add(lockTimerDurationLabel1); + lockTimerDurationRow.Children.Add(lockTimerDurationInput); + lockTimerDurationRow.Children.Add(lockTimerDurationLabel2); + // Добавляем элементы в стек mainStack.Children.Add(titleText); mainStack.Children.Add(exitOnCloseCheckBox); + mainStack.Children.Add(lockTimerDurationRow); // Назначаем стек основным контентом окна this.Content = mainStack; } + + protected override void OnClosed(EventArgs e) + { + base.OnClosed(e); + Console.WriteLine("Saving application settings"); + AppSettings.Save(); + } } diff --git a/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs b/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs index c17d4db..0ca216c 100644 --- a/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs +++ b/src/KeyKeeper/ViewModels/RepositoryWindowViewModel.cs @@ -8,8 +8,6 @@ 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; @@ -94,7 +92,7 @@ public partial class RepositoryWindowViewModel : ViewModelBase private void OnLockTimerTick(object? sender, EventArgs e) { var elapsed = DateTime.UtcNow - _timerStart; - var remaining = LockTimeout - elapsed; + var remaining = TimeSpan.FromMinutes(AppSettings.LockTimerMinutes) - elapsed; if (remaining <= TimeSpan.Zero) { @@ -109,7 +107,7 @@ public partial class RepositoryWindowViewModel : ViewModelBase private void UpdateTimerDisplay(TimeSpan? remaining = null) { - var r = remaining ?? LockTimeout; + var r = remaining ?? TimeSpan.FromMinutes(AppSettings.LockTimerMinutes); LockTimerDisplay = $"{r:mm\\:ss}"; } } diff --git a/src/KeyKeeper/Views/MainWindow.axaml.cs b/src/KeyKeeper/Views/MainWindow.axaml.cs index b2edd6e..3d2deea 100644 --- a/src/KeyKeeper/Views/MainWindow.axaml.cs +++ b/src/KeyKeeper/Views/MainWindow.axaml.cs @@ -10,6 +10,7 @@ using KeyKeeper.Services; using KeyKeeper.ViewModels; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Threading.Tasks; @@ -39,19 +40,31 @@ namespace KeyKeeper.Views var path = createVaultDialog.FilePath; var password = createVaultDialog.Password; var compositeKey = new CompositeKey(password, null); - var passStoreAccessor = new PassStoreFileAccessor( - filename: path, - create: true, - createOptions: new StoreCreationOptions() - { - Key = compositeKey, - LockTimeoutSeconds = 800 - }); - recentFilesService.Remember(path); + try + { + var passStoreAccessor = new PassStoreFileAccessor( + filename: path, + create: true, + createOptions: new StoreCreationOptions() + { + Key = compositeKey, + LockTimeoutSeconds = 800 + }); - IPassStore passStore = passStoreAccessor; - OpenRepositoryWindow(passStore); + recentFilesService.Remember(path); + + IPassStore passStore = passStoreAccessor; + OpenRepositoryWindow(passStore); + } catch (IOException exception) + { + Console.WriteLine($"I/O error when creating \"{path}\": {exception}"); + await new ErrorDialog("Cannot create the password store", "File error").ShowDialog(this); + } catch (Exception exception) + { + Console.WriteLine($"Unknown error when creating \"{path}\": {exception}"); + await new ErrorDialog("Cannot create the password store", "Unknown error").ShowDialog(this); + } } } @@ -61,17 +74,17 @@ namespace KeyKeeper.Views { Title = "Открыть хранилище паролей", AllowMultiple = false, - FileTypeFilter = new[] - { + FileTypeFilter = + [ new FilePickerFileType("KeyKeeper files") { - Patterns = new[] { "*.kkp" } + Patterns = ["*.kkp"] }, new FilePickerFileType("All files") { - Patterns = new[] { "*.*" } + Patterns = ["*.*"] } - } + ] }); if (files.Count > 0) @@ -80,17 +93,69 @@ namespace KeyKeeper.Views if (file.TryGetLocalPath() is string path) { recentFilesService.Remember(path); - OpenRepositoryWindow(new PassStoreFileAccessor(path, false, null)); + IPassStore? passStore; + try + { + passStore = new PassStoreFileAccessor(path, false, null); + } catch (PassStoreFileException exc) + { + await new ErrorDialog($"This password store file has a problem: {exc.Message}", "File format error").ShowDialog(this); + Console.WriteLine($"Format error when opening \"{path}\": {exc}"); + recentFilesService.Forget(path); + return; + } catch (FileNotFoundException) + { + await new ErrorDialog("This password store no longer exists", "File error").ShowDialog(this); + recentFilesService.Forget(path); + return; + } catch (IOException exc) + { + Console.WriteLine($"I/O error when opening \"{path}\": {exc}"); + await new ErrorDialog("Cannot open this password store", "File error").ShowDialog(this); + return; + } catch (Exception exc) + { + Console.WriteLine($"Unknown error when opening \"{path}\": {exc}"); + await new ErrorDialog("Cannot open this password store", "File error").ShowDialog(this); + return; + } + OpenRepositoryWindow(passStore); } } } - private void RecentVaultsListItem_DoubleTapped(object sender, RoutedEventArgs e) + private async void RecentVaultsListItem_DoubleTapped(object sender, RoutedEventArgs e) { if (sender is Control c && c.DataContext is RecentFile recentFile) { recentFilesService.Remember(recentFile.Path); - OpenRepositoryWindow(new PassStoreFileAccessor(recentFile.Path, false, null)); + IPassStore? passStore; + try + { + passStore = new PassStoreFileAccessor(recentFile.Path, false, null); + } catch (PassStoreFileException exc) + { + await new ErrorDialog($"This password store file has a problem: {exc.Message}", "File format error").ShowDialog(this); + Console.WriteLine($"Format error when opening \"{recentFile.Path}\" from recents: {exc}"); + recentFilesService.Forget(recentFile.Path); + return; + } catch (FileNotFoundException) + { + await new ErrorDialog("This password store no longer exists", "File error").ShowDialog(this); + recentFilesService.Forget(recentFile.Path); + return; + } catch (IOException exc) + { + Console.WriteLine($"I/O error when opening \"{recentFile.Path}\" from recents: {exc}"); + await new ErrorDialog("Cannot open this password store", "File error").ShowDialog(this); + return; + } catch (Exception exc) + { + Console.WriteLine($"Unknown error when opening \"{recentFile.Path}\" from recents: {exc}"); + await new ErrorDialog("Cannot open this password store", "File error").ShowDialog(this); + return; + } + OpenRepositoryWindow(passStore); } } @@ -104,11 +169,11 @@ namespace KeyKeeper.Views { if (AppSettings.ExitOnRepositoryClose) { - this.Close(); + Close(); } else { - this.Show(); + Show(); } }; repositoryWindow.Show();