diff --git a/src/PassKey.Core/Services/CardTypeDetector.cs b/src/PassKey.Core/Services/CardTypeDetector.cs index 8f74c51..640b34f 100644 --- a/src/PassKey.Core/Services/CardTypeDetector.cs +++ b/src/PassKey.Core/Services/CardTypeDetector.cs @@ -4,6 +4,34 @@ namespace PassKey.Core.Services; public static class CardTypeDetector { + /// + /// BIN (Bank Identification Number) / IIN prefix ranges used for card-type detection. + /// Sources: EMVCo IIN Registry; updated ranges as of 2024. + /// + private static class BinRanges + { + // MasterCard: standard range 51-55 is checked inline with a 2-digit prefix; + // the expanded range covers re-issued BINs introduced in 2017. + internal const int MasterCardExpandedStart = 2221; + internal const int MasterCardExpandedEnd = 2720; + + // Discover: 3-digit prefix range (644-649) + internal const int DiscoverRange3Start = 644; + internal const int DiscoverRange3End = 649; + + // Discover: 6-digit IIN range (co-branded UnionPay; 622126-622925) + internal const int DiscoverRange6Start = 622126; + internal const int DiscoverRange6End = 622925; + + // JCB: 4-digit prefix range (3528-3589) + internal const int JcbRangeStart = 3528; + internal const int JcbRangeEnd = 3589; + + // Diners Club Classic: 3-digit prefix range (300-305) + internal const int DinersRange3Start = 300; + internal const int DinersRange3End = 305; + } + /// /// Detects the card type from the card number using BIN prefix tables. /// @@ -24,7 +52,7 @@ public static CardType Detect(string cardNumber) if (digits.StartsWith('4')) return CardType.Visa; - // MasterCard: 51-55 or 2221-2720 + // MasterCard: 51-55 or expanded range 2221-2720 if (digits.Length >= 2) { var prefix2 = int.Parse(digits[..2]); @@ -34,7 +62,7 @@ public static CardType Detect(string cardNumber) if (digits.Length >= 4) { var prefix4 = int.Parse(digits[..4]); - if (prefix4 >= 2221 && prefix4 <= 2720) + if (prefix4 >= BinRanges.MasterCardExpandedStart && prefix4 <= BinRanges.MasterCardExpandedEnd) return CardType.MasterCard; } @@ -44,13 +72,13 @@ public static CardType Detect(string cardNumber) if (digits.Length >= 3) { var prefix3 = int.Parse(digits[..3]); - if (prefix3 >= 644 && prefix3 <= 649) + if (prefix3 >= BinRanges.DiscoverRange3Start && prefix3 <= BinRanges.DiscoverRange3End) return CardType.Discover; } if (digits.Length >= 6) { var prefix6 = int.Parse(digits[..6]); - if (prefix6 >= 622126 && prefix6 <= 622925) + if (prefix6 >= BinRanges.DiscoverRange6Start && prefix6 <= BinRanges.DiscoverRange6End) return CardType.Discover; } @@ -58,7 +86,7 @@ public static CardType Detect(string cardNumber) if (digits.Length >= 4) { var prefix4 = int.Parse(digits[..4]); - if (prefix4 >= 3528 && prefix4 <= 3589) + if (prefix4 >= BinRanges.JcbRangeStart && prefix4 <= BinRanges.JcbRangeEnd) return CardType.JCB; } @@ -68,7 +96,7 @@ public static CardType Detect(string cardNumber) if (digits.Length >= 3) { var prefix3 = int.Parse(digits[..3]); - if (prefix3 >= 300 && prefix3 <= 305) + if (prefix3 >= BinRanges.DinersRange3Start && prefix3 <= BinRanges.DinersRange3End) return CardType.DinersClub; } diff --git a/src/PassKey.Core/Services/MergeService.cs b/src/PassKey.Core/Services/MergeService.cs index ede6de1..0e40955 100644 --- a/src/PassKey.Core/Services/MergeService.cs +++ b/src/PassKey.Core/Services/MergeService.cs @@ -11,82 +11,56 @@ public ImportResult MergeInto(Vault target, Vault source, ImportMergeStrategy st ArgumentNullException.ThrowIfNull(target); ArgumentNullException.ThrowIfNull(source); - int pwImported = 0, cardImported = 0, idImported = 0, noteImported = 0; - int skipped = 0, overwritten = 0; + var (pwImported, pwSkipped, pwOverwritten) = MergeCollection(target.Passwords, source.Passwords, strategy, ComputePasswordHash); + var (cardImported, cardSkipped, cardOverwritten) = MergeCollection(target.CreditCards, source.CreditCards, strategy, ComputeCardHash); + var (idImported, idSkipped, idOverwritten) = MergeCollection(target.Identities, source.Identities, strategy, ComputeIdentityHash); + var (noteImported, noteSkipped, noteOverwritten) = MergeCollection(target.SecureNotes, source.SecureNotes, strategy, ComputeNoteHash); - // Build hash sets of existing entries - var existingPwHashes = BuildHashSet(target.Passwords, ComputePasswordHash); - var existingCardHashes = BuildHashSet(target.CreditCards, ComputeCardHash); - var existingIdHashes = BuildHashSet(target.Identities, ComputeIdentityHash); - var existingNoteHashes = BuildHashSet(target.SecureNotes, ComputeNoteHash); + target.LastModified = DateTime.UtcNow; - // Merge passwords - foreach (var entry in source.Passwords) + return new ImportResult { - var hash = ComputePasswordHash(entry); - var result = MergeEntry(target.Passwords, entry, hash, existingPwHashes, strategy, ComputePasswordHash); - switch (result) - { - case MergeAction.Imported: pwImported++; break; - case MergeAction.Skipped: skipped++; break; - case MergeAction.Overwritten: overwritten++; break; - } - } + PasswordsImported = pwImported, + CardsImported = cardImported, + IdentitiesImported = idImported, + NotesImported = noteImported, + Skipped = pwSkipped + cardSkipped + idSkipped + noteSkipped, + Overwritten = pwOverwritten + cardOverwritten + idOverwritten + noteOverwritten + }; + } - // Merge credit cards - foreach (var entry in source.CreditCards) - { - var hash = ComputeCardHash(entry); - var result = MergeEntry(target.CreditCards, entry, hash, existingCardHashes, strategy, ComputeCardHash); - switch (result) - { - case MergeAction.Imported: cardImported++; break; - case MergeAction.Skipped: skipped++; break; - case MergeAction.Overwritten: overwritten++; break; - } - } + private enum MergeAction { Imported, Skipped, Overwritten } - // Merge identities - foreach (var entry in source.Identities) - { - var hash = ComputeIdentityHash(entry); - var result = MergeEntry(target.Identities, entry, hash, existingIdHashes, strategy, ComputeIdentityHash); - switch (result) - { - case MergeAction.Imported: idImported++; break; - case MergeAction.Skipped: skipped++; break; - case MergeAction.Overwritten: overwritten++; break; - } - } + private const int NoteHashSnippetLength = 256; + + /// + /// Merges all entries from into + /// applying the given for duplicates. + /// Returns a tuple of (imported, skipped, overwritten) counts. + /// + private static (int imported, int skipped, int overwritten) MergeCollection( + List targetList, + List sourceList, + ImportMergeStrategy strategy, + Func hashFunc) where T : class + { + int imported = 0, skipped = 0, overwritten = 0; + var existingHashes = BuildHashSet(targetList, hashFunc); - // Merge secure notes - foreach (var entry in source.SecureNotes) + foreach (var entry in sourceList) { - var hash = ComputeNoteHash(entry); - var result = MergeEntry(target.SecureNotes, entry, hash, existingNoteHashes, strategy, ComputeNoteHash); + var hash = hashFunc(entry); + var result = MergeEntry(targetList, entry, hash, existingHashes, strategy, hashFunc); switch (result) { - case MergeAction.Imported: noteImported++; break; - case MergeAction.Skipped: skipped++; break; + case MergeAction.Imported: imported++; break; + case MergeAction.Skipped: skipped++; break; case MergeAction.Overwritten: overwritten++; break; } } - - target.LastModified = DateTime.UtcNow; - - return new ImportResult - { - PasswordsImported = pwImported, - CardsImported = cardImported, - IdentitiesImported = idImported, - NotesImported = noteImported, - Skipped = skipped, - Overwritten = overwritten - }; + return (imported, skipped, overwritten); } - private enum MergeAction { Imported, Skipped, Overwritten } - private static MergeAction MergeEntry( List targetList, T entry, string hash, Dictionary existingHashes, @@ -165,8 +139,8 @@ internal static string ComputeIdentityHash(IdentityEntry entry) internal static string ComputeNoteHash(SecureNoteEntry entry) { - var contentSnippet = entry.Content.Length > 256 - ? entry.Content[..256] + var contentSnippet = entry.Content.Length > NoteHashSnippetLength + ? entry.Content[..NoteHashSnippetLength] : entry.Content; var input = $"{entry.Title.ToLowerInvariant()}|{contentSnippet}"; return ComputeSha256(input); diff --git a/src/PassKey.Core/Services/PasswordGenerator.cs b/src/PassKey.Core/Services/PasswordGenerator.cs index f8ad768..c2af863 100644 --- a/src/PassKey.Core/Services/PasswordGenerator.cs +++ b/src/PassKey.Core/Services/PasswordGenerator.cs @@ -10,11 +10,15 @@ public sealed class PasswordGenerator : IPasswordGenerator private const string Symbols = "!@#$%^&*()-_=+[]{}|;:',.<>?/~`"; private const string AmbiguousChars = "0O1lI|"; + private const int MinPasswordLength = 8; + private const int MaxPasswordLength = 128; + public string Generate(PasswordGeneratorOptions options) { ArgumentNullException.ThrowIfNull(options); - if (options.Length < 8 || options.Length > 128) - throw new ArgumentOutOfRangeException(nameof(options), "Length must be between 8 and 128."); + if (options.Length < MinPasswordLength || options.Length > MaxPasswordLength) + throw new ArgumentOutOfRangeException(nameof(options), + $"Length must be between {MinPasswordLength} and {MaxPasswordLength}."); var charPool = BuildCharPool(options); if (charPool.Length == 0) diff --git a/src/PassKey.Core/Services/PasswordStrengthAnalyzer.cs b/src/PassKey.Core/Services/PasswordStrengthAnalyzer.cs index bf81fa4..31a0435 100644 --- a/src/PassKey.Core/Services/PasswordStrengthAnalyzer.cs +++ b/src/PassKey.Core/Services/PasswordStrengthAnalyzer.cs @@ -104,6 +104,19 @@ private static bool ContainsCommonPattern(string password) return false; } + /// + /// Time conversion constants used when formatting the estimated crack-time string. + /// + private static class TimeConstants + { + internal const double SecondsPerMinute = 60; + internal const double SecondsPerHour = 3_600; // 60 * 60 + internal const double SecondsPerDay = 86_400; // 60 * 60 * 24 + internal const double SecondsPerYear = 31_536_000; // 365 * 24 * 60 * 60 + internal const double SecondsPerCentury = SecondsPerYear * 100; + internal const double SecondsPerMillennium = SecondsPerYear * 1_000; + } + private static string EstimateCrackTime(int length, bool hasUpper, bool hasLower, bool hasDigit, bool hasSymbol) { var charsetSize = 0; @@ -121,12 +134,12 @@ private static string EstimateCrackTime(int length, bool hasUpper, bool hasLower return seconds switch { < 1 => "instant", - < 60 => "seconds", - < 3600 => $"{(int)(seconds / 60)} minutes", - < 86400 => $"{(int)(seconds / 3600)} hours", - < 31536000 => $"{(int)(seconds / 86400)} days", - < 31536000.0 * 100 => $"{(int)(seconds / 31536000)} years", - < 31536000.0 * 1000 => "centuries", + < TimeConstants.SecondsPerMinute => "seconds", + < TimeConstants.SecondsPerHour => $"{(int)(seconds / TimeConstants.SecondsPerMinute)} minutes", + < TimeConstants.SecondsPerDay => $"{(int)(seconds / TimeConstants.SecondsPerHour)} hours", + < TimeConstants.SecondsPerYear => $"{(int)(seconds / TimeConstants.SecondsPerDay)} days", + < TimeConstants.SecondsPerCentury => $"{(int)(seconds / TimeConstants.SecondsPerYear)} years", + < TimeConstants.SecondsPerMillennium => "centuries", _ => "millennia" }; } diff --git a/src/PassKey.Desktop/Helpers/ListViewHelpers.cs b/src/PassKey.Desktop/Helpers/ListViewHelpers.cs new file mode 100644 index 0000000..728d287 --- /dev/null +++ b/src/PassKey.Desktop/Helpers/ListViewHelpers.cs @@ -0,0 +1,36 @@ +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; + +namespace PassKey.Desktop.Helpers; + +/// +/// Shared UI helpers for list-view code-behind classes. +/// Centralises boilerplate that would otherwise be duplicated across +/// PasswordsListView, CreditCardsListView, IdentitiesListView +/// and SecureNotesListView. +/// +internal static class ListViewHelpers +{ + /// + /// Opens for 2 seconds, then closes it automatically + /// via a . + /// + /// The to show (e.g. SavedTip). + /// + /// Optional action invoked immediately after opening the tip. + /// Used by SecureNotesListView to trigger the accessibility announcer. + /// + internal static void ShowSavedToast(TeachingTip tip, Action? extraAction = null) + { + tip.IsOpen = true; + extraAction?.Invoke(); + + var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; + timer.Tick += (s, _) => + { + tip.IsOpen = false; + ((DispatcherTimer)s!).Stop(); + }; + timer.Start(); + } +} diff --git a/src/PassKey.Desktop/ViewModels/DashboardViewModel.cs b/src/PassKey.Desktop/ViewModels/DashboardViewModel.cs index b2e48aa..aecde50 100644 --- a/src/PassKey.Desktop/ViewModels/DashboardViewModel.cs +++ b/src/PassKey.Desktop/ViewModels/DashboardViewModel.cs @@ -34,91 +34,143 @@ public partial class DashboardViewModel : ObservableObject private string _deletedIdentity = "Identità eliminata"; private string _deletedNote = "Nota eliminata"; - // Greeting + // ── Greeting ───────────────────────────────────────────────────────────── + + /// + /// Personalised greeting shown at the top of the dashboard. + /// Text is time-dependent: morning / afternoon / evening variant. + /// Updated each time runs. + /// [ObservableProperty] public partial string GreetingMessage { get; set; } = string.Empty; - // Stat card totals + // ── Stat card totals ───────────────────────────────────────────────────── + + /// Total count of password entries currently in the vault. [ObservableProperty] public partial int TotalPasswords { get; set; } + /// Total count of credit card entries currently in the vault. [ObservableProperty] public partial int TotalCards { get; set; } + /// Total count of identity profiles currently in the vault. [ObservableProperty] public partial int TotalIdentities { get; set; } + /// Total count of secure notes currently in the vault. [ObservableProperty] public partial int TotalNotes { get; set; } - // Per-card weekly activity (4 types × 3 actions = 12 properties) + // ── Weekly activity counters (4 types × 3 actions = 12 properties) ─────── + + /// Number of password entries added in the past 7 days. [ObservableProperty] public partial int PasswordsAdded { get; set; } + /// Number of password entries deleted in the past 7 days. [ObservableProperty] public partial int PasswordsRemoved { get; set; } + /// Number of password entries edited in the past 7 days. [ObservableProperty] public partial int PasswordsModified { get; set; } + /// Number of credit card entries added in the past 7 days. [ObservableProperty] public partial int CardsAdded { get; set; } + /// Number of credit card entries deleted in the past 7 days. [ObservableProperty] public partial int CardsRemoved { get; set; } + /// Number of credit card entries edited in the past 7 days. [ObservableProperty] public partial int CardsModified { get; set; } + /// Number of identity profiles added in the past 7 days. [ObservableProperty] public partial int IdentitiesAdded { get; set; } + /// Number of identity profiles deleted in the past 7 days. [ObservableProperty] public partial int IdentitiesRemoved { get; set; } + /// Number of identity profiles edited in the past 7 days. [ObservableProperty] public partial int IdentitiesModified { get; set; } + /// Number of secure notes added in the past 7 days. [ObservableProperty] public partial int NotesAdded { get; set; } + /// Number of secure notes deleted in the past 7 days. [ObservableProperty] public partial int NotesRemoved { get; set; } + /// Number of secure notes edited in the past 7 days. [ObservableProperty] public partial int NotesModified { get; set; } - // Password health + // ── Vault health ───────────────────────────────────────────────────────── + + /// + /// Overall vault health score (0–100). + /// Computed from the ratio of strong vs. weak passwords in the vault. + /// [ObservableProperty] public partial int VaultHealthScore { get; set; } + /// Count of password entries rated Weak or VeryWeak by the strength analyser. [ObservableProperty] public partial int WeakPasswordCount { get; set; } - // Expiring cards + // ── Expiring credit cards ───────────────────────────────────────────────── + + /// Number of credit cards expiring within the next 60 days. [ObservableProperty] public partial int ExpiringCardsCount { get; set; } + /// when at least one card is expiring soon; drives the warning banner visibility. [ObservableProperty] public partial bool HasExpiringCards { get; set; } - // Recent activity + // ── Recent activity ─────────────────────────────────────────────────────── + + /// Up to 15 most recent vault activity items, shown in the dashboard timeline. public ObservableCollection RecentItems { get; } = []; - // Empty state + // ── Empty state ─────────────────────────────────────────────────────────── + + /// + /// when the vault contains no entries of any type. + /// Drives the dashboard empty-state placeholder visibility. + /// [ObservableProperty] public partial bool IsVaultEmpty { get; set; } - // Search + // ── Search ──────────────────────────────────────────────────────────────── + + /// + /// Current search query entered by the user in the dashboard search box. + /// Setting this property triggers a reactive search across all vault entry types. + /// [ObservableProperty] public partial string SearchQuery { get; set; } = string.Empty; + /// Flat list of search results across all vault sections, bound to the search results panel. public ObservableCollection SearchResults { get; } = []; + /// when is non-empty; drives the results panel visibility. [ObservableProperty] public partial bool HasSearchResults { get; set; } - // Navigation event: fired when user clicks a recent item or search result + // ── Navigation ──────────────────────────────────────────────────────────── + + /// + /// Fired when the user clicks a recent-activity item or a search result. + /// Parameters: vault section identifier (e.g. "Passwords") and entry . + /// public event Action? NavigateToItemRequested; public DashboardViewModel( diff --git a/src/PassKey.Desktop/Views/CreditCardDetailView.xaml.cs b/src/PassKey.Desktop/Views/CreditCardDetailView.xaml.cs index 78bcd43..7a20144 100644 --- a/src/PassKey.Desktop/Views/CreditCardDetailView.xaml.cs +++ b/src/PassKey.Desktop/Views/CreditCardDetailView.xaml.cs @@ -16,7 +16,7 @@ public sealed partial class CreditCardDetailView : UserControl { private CreditCardDetailViewModel? _viewModel; private bool _updatingFromVm; - private readonly Microsoft.Windows.ApplicationModel.Resources.ResourceLoader _res = new(); + private readonly Microsoft.Windows.ApplicationModel.Resources.ResourceLoader _resourceLoader = new(); /// /// All 10 accent colors for the swatch selector. @@ -100,8 +100,8 @@ public void SetViewModel(CreditCardDetailViewModel vm) // Subtitle PanelSubtitle.Text = isEdit - ? _res.GetString("CardPanelSubtitleEdit") - : _res.GetString("CardPanelSubtitleNew"); + ? _resourceLoader.GetString("CardPanelSubtitleEdit") + : _resourceLoader.GetString("CardPanelSubtitleNew"); // Hook SecureInputBox events CvvInput.PasswordChanged += OnCvvChanged; @@ -265,7 +265,7 @@ private void UpdateCardTypeIndicator() CardType.JCB => "JCB", CardType.Maestro => "Maestro", CardType.DinersClub => "Diners Club", - _ => _res.GetString("CardTypeUnknown") + _ => _resourceLoader.GetString("CardTypeUnknown") }; // Luhn validation @@ -278,14 +278,14 @@ private void UpdateCardTypeIndicator() { LuhnIcon.Glyph = "\uE73E"; // checkmark LuhnIcon.Foreground = (Brush)Application.Current.Resources["LuhnValidBrush"]; - LuhnLabel.Text = _res.GetString("LuhnValid"); + LuhnLabel.Text = _resourceLoader.GetString("LuhnValid"); LuhnLabel.Foreground = (Brush)Application.Current.Resources["LuhnValidBrush"]; } else { LuhnIcon.Glyph = "\uE711"; // dismiss LuhnIcon.Foreground = (Brush)Application.Current.Resources["LuhnInvalidBrush"]; - LuhnLabel.Text = _res.GetString("LuhnInvalid"); + LuhnLabel.Text = _resourceLoader.GetString("LuhnInvalid"); LuhnLabel.Foreground = (Brush)Application.Current.Resources["LuhnInvalidBrush"]; } } @@ -318,7 +318,7 @@ private void UpdateSavingState(bool saving) { SaveProgress.IsActive = saving; SaveProgress.Visibility = saving ? Visibility.Visible : Visibility.Collapsed; - SaveButtonText.Text = saving ? _res.GetString("SaveInProgress") : _res.GetString("ButtonSaveLabel/Text"); + SaveButtonText.Text = saving ? _resourceLoader.GetString("SaveInProgress") : _resourceLoader.GetString("ButtonSaveLabel/Text"); SaveButton.IsEnabled = !saving; } diff --git a/src/PassKey.Desktop/Views/CreditCardsListView.xaml.cs b/src/PassKey.Desktop/Views/CreditCardsListView.xaml.cs index 58aa62e..cecd707 100644 --- a/src/PassKey.Desktop/Views/CreditCardsListView.xaml.cs +++ b/src/PassKey.Desktop/Views/CreditCardsListView.xaml.cs @@ -6,6 +6,7 @@ using PassKey.Core.Models; using PassKey.Core.Services; using PassKey.Desktop.Controls; +using PassKey.Desktop.Helpers; using PassKey.Desktop.ViewModels; namespace PassKey.Desktop.Views; @@ -16,7 +17,7 @@ namespace PassKey.Desktop.Views; public sealed partial class CreditCardsListView : UserControl { private CreditCardsListViewModel? _viewModel; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); public CreditCardsListView() { @@ -29,8 +30,8 @@ public async void SetViewModel(CreditCardsListViewModel vm) DataContext = vm; // Apply localized empty-state strings (guarantees correct language on every OS locale). - EmptyState.Title = _res.GetString("EmptyCardsTitle"); - EmptyState.Subtitle = _res.GetString("EmptyCardsSubtitle"); + EmptyState.Title = _resourceLoader.GetString("EmptyCardsTitle"); + EmptyState.Subtitle = _resourceLoader.GetString("EmptyCardsSubtitle"); vm.PropertyChanged += OnViewModelPropertyChanged; vm.SaveCompleted += ShowSavedToast; @@ -208,17 +209,7 @@ private void EditEntry_Click(object sender, RoutedEventArgs e) // --- Toast --- - public void ShowSavedToast() - { - SavedTip.IsOpen = true; - var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; - timer.Tick += (s, _) => - { - SavedTip.IsOpen = false; - ((DispatcherTimer)s!).Stop(); - }; - timer.Start(); - } + public void ShowSavedToast() => ListViewHelpers.ShowSavedToast(SavedTip); // Hover effects — show/hide action buttons private void Row_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e) diff --git a/src/PassKey.Desktop/Views/DashboardView.xaml.cs b/src/PassKey.Desktop/Views/DashboardView.xaml.cs index 663c8de..1249a7b 100644 --- a/src/PassKey.Desktop/Views/DashboardView.xaml.cs +++ b/src/PassKey.Desktop/Views/DashboardView.xaml.cs @@ -13,18 +13,18 @@ namespace PassKey.Desktop.Views; public sealed partial class DashboardView : UserControl { private DashboardViewModel? _viewModel; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); private DispatcherTimer? _searchTimer; public DashboardView() { InitializeComponent(); - EmptyState.Title = _res.GetString("EmptyDashboardTitle"); - EmptyState.Subtitle = _res.GetString("EmptyDashboardSubtitle"); - EmptyState.PrimaryActionText = _res.GetString("AddPassword"); - EmptyState.SecondaryActionText = _res.GetString("ImportData"); + EmptyState.Title = _resourceLoader.GetString("EmptyDashboardTitle"); + EmptyState.Subtitle = _resourceLoader.GetString("EmptyDashboardSubtitle"); + EmptyState.PrimaryActionText = _resourceLoader.GetString("AddPassword"); + EmptyState.SecondaryActionText = _resourceLoader.GetString("ImportData"); - VaultOverviewTitle.Text = _res.GetString("DashVaultOverview"); + VaultOverviewTitle.Text = _resourceLoader.GetString("DashVaultOverview"); } public async void SetViewModel(DashboardViewModel vm) @@ -36,20 +36,20 @@ public async void SetViewModel(DashboardViewModel vm) // Pass localized resources to ViewModel vm.SetGreetingResources( - _res.GetString("GreetingMorning"), - _res.GetString("GreetingAfternoon"), - _res.GetString("GreetingEvening")); + _resourceLoader.GetString("GreetingMorning"), + _resourceLoader.GetString("GreetingAfternoon"), + _resourceLoader.GetString("GreetingEvening")); vm.SetActionLabels( - _res.GetString("ActionCreated"), - _res.GetString("ActionModified"), - _res.GetString("ActionDeleted")); + _resourceLoader.GetString("ActionCreated"), + _resourceLoader.GetString("ActionModified"), + _resourceLoader.GetString("ActionDeleted")); vm.SetDeletedLabels( - _res.GetString("DeletedPassword"), - _res.GetString("DeletedCard"), - _res.GetString("DeletedIdentity"), - _res.GetString("DeletedNote")); + _resourceLoader.GetString("DeletedPassword"), + _resourceLoader.GetString("DeletedCard"), + _resourceLoader.GetString("DeletedIdentity"), + _resourceLoader.GetString("DeletedNote")); // Wire empty state buttons via PrimaryActionCommand/SecondaryActionCommand EmptyState.PrimaryActionCommand = new CommunityToolkit.Mvvm.Input.RelayCommand( @@ -102,13 +102,13 @@ private void UpdateUI() NotesCount.Text = _viewModel.TotalNotes.ToString(); // Stat card subtitles - PasswordsSubtitle.Text = _res.GetString("DashSubtitlePasswords"); - CardsSubtitle.Text = _res.GetString("DashSubtitleCards"); - IdentitiesSubtitle.Text = _res.GetString("DashSubtitleIdentities"); - NotesSubtitle.Text = _res.GetString("DashSubtitleNotes"); + PasswordsSubtitle.Text = _resourceLoader.GetString("DashSubtitlePasswords"); + CardsSubtitle.Text = _resourceLoader.GetString("DashSubtitleCards"); + IdentitiesSubtitle.Text = _resourceLoader.GetString("DashSubtitleIdentities"); + NotesSubtitle.Text = _resourceLoader.GetString("DashSubtitleNotes"); // Per-card compact activity (3 indicators each) - var noChangesLabel = _res.GetString("DashCardNoChanges"); + var noChangesLabel = _resourceLoader.GetString("DashCardNoChanges"); UpdateCardActivity(_viewModel.PasswordsAdded, _viewModel.PasswordsRemoved, _viewModel.PasswordsModified, PwActivityRow, PwNoChanges, PwAdded, PwRemoved, PwModified, noChangesLabel); UpdateCardActivity(_viewModel.CardsAdded, _viewModel.CardsRemoved, _viewModel.CardsModified, @@ -125,10 +125,10 @@ private void UpdateUI() HealthRing.Value = _viewModel.VaultHealthScore; HealthScoreText.Text = $"{_viewModel.VaultHealthScore}%"; HealthRing.Foreground = GetHealthBrush(_viewModel.VaultHealthScore); - HealthTitle.Text = _res.GetString("DashHealthTitle"); + HealthTitle.Text = _resourceLoader.GetString("DashHealthTitle"); HealthSubtitle.Text = _viewModel.WeakPasswordCount > 0 - ? string.Format(_res.GetString("DashHealthWeak"), _viewModel.WeakPasswordCount) - : _res.GetString("DashHealthGood"); + ? string.Format(_resourceLoader.GetString("DashHealthWeak"), _viewModel.WeakPasswordCount) + : _resourceLoader.GetString("DashHealthGood"); } else { @@ -141,7 +141,7 @@ private void UpdateUI() ExpiringCardsAlert.Visibility = Visibility.Visible; ExpiringCardsAlert.IsOpen = true; ExpiringCardsAlert.Message = string.Format( - _res.GetString("DashExpiringCards"), _viewModel.ExpiringCardsCount); + _resourceLoader.GetString("DashExpiringCards"), _viewModel.ExpiringCardsCount); } else { diff --git a/src/PassKey.Desktop/Views/IdentitiesListView.xaml.cs b/src/PassKey.Desktop/Views/IdentitiesListView.xaml.cs index 9eda370..3876d2e 100644 --- a/src/PassKey.Desktop/Views/IdentitiesListView.xaml.cs +++ b/src/PassKey.Desktop/Views/IdentitiesListView.xaml.cs @@ -7,6 +7,7 @@ using Microsoft.UI.Xaml.Media; using Microsoft.Windows.ApplicationModel.Resources; using PassKey.Core.Models; +using PassKey.Desktop.Helpers; using PassKey.Desktop.ViewModels; namespace PassKey.Desktop.Views; @@ -17,7 +18,7 @@ namespace PassKey.Desktop.Views; public sealed partial class IdentitiesListView : UserControl { private IdentitiesListViewModel? _viewModel; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); public IdentitiesListView() { @@ -33,8 +34,8 @@ public async void SetViewModel(IdentitiesListViewModel vm) vm.SaveCompleted += ShowSavedToast; // Apply localized empty-state strings (guarantees correct language on every OS locale). - EmptyState.Title = _res.GetString("EmptyIdentitiesTitle"); - EmptyState.Subtitle = _res.GetString("EmptyIdentitiesSubtitle"); + EmptyState.Title = _resourceLoader.GetString("EmptyIdentitiesTitle"); + EmptyState.Subtitle = _resourceLoader.GetString("EmptyIdentitiesSubtitle"); await vm.LoadEntriesCommand.ExecuteAsync(null); UpdateList(); UpdateEmptyState(); @@ -181,17 +182,7 @@ private void Card_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRo } // Save confirmation toast - public void ShowSavedToast() - { - SavedTip.IsOpen = true; - var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; - timer.Tick += (s, _) => - { - SavedTip.IsOpen = false; - ((DispatcherTimer)s!).Stop(); - }; - timer.Start(); - } + public void ShowSavedToast() => ListViewHelpers.ShowSavedToast(SavedTip); // ── Formatters ──────────────────────────────────────────────────────────── diff --git a/src/PassKey.Desktop/Views/IdentityDetailView.xaml.cs b/src/PassKey.Desktop/Views/IdentityDetailView.xaml.cs index ac77527..4bc878d 100644 --- a/src/PassKey.Desktop/Views/IdentityDetailView.xaml.cs +++ b/src/PassKey.Desktop/Views/IdentityDetailView.xaml.cs @@ -13,7 +13,7 @@ public sealed partial class IdentityDetailView : UserControl { private IdentityDetailViewModel? _viewModel; private bool _updatingFromVm; - private readonly Microsoft.Windows.ApplicationModel.Resources.ResourceLoader _res = new(); + private readonly Microsoft.Windows.ApplicationModel.Resources.ResourceLoader _resourceLoader = new(); public IdentityDetailView() { @@ -68,8 +68,8 @@ public void SetViewModel(IdentityDetailViewModel vm) // Subtitle PanelSubtitle.Text = isEdit - ? _res.GetString("IdPanelSubtitleEdit") - : _res.GetString("IdPanelSubtitleNew"); + ? _resourceLoader.GetString("IdPanelSubtitleEdit") + : _resourceLoader.GetString("IdPanelSubtitleNew"); // Expand sections that have data in edit mode if (!string.IsNullOrWhiteSpace(vm.Street) || !string.IsNullOrWhiteSpace(vm.City) || @@ -236,7 +236,7 @@ private void UpdateSavingState(bool saving) { SaveProgress.IsActive = saving; SaveProgress.Visibility = saving ? Visibility.Visible : Visibility.Collapsed; - SaveButtonText.Text = saving ? _res.GetString("SaveInProgress") : _res.GetString("ButtonSave"); + SaveButtonText.Text = saving ? _resourceLoader.GetString("SaveInProgress") : _resourceLoader.GetString("ButtonSave"); SaveButton.IsEnabled = !saving; } diff --git a/src/PassKey.Desktop/Views/PasswordDetailView.xaml.cs b/src/PassKey.Desktop/Views/PasswordDetailView.xaml.cs index 96d8cf2..7aac1e8 100644 --- a/src/PassKey.Desktop/Views/PasswordDetailView.xaml.cs +++ b/src/PassKey.Desktop/Views/PasswordDetailView.xaml.cs @@ -17,7 +17,7 @@ public sealed partial class PasswordDetailView : UserControl { private PasswordDetailViewModel? _viewModel; private bool _updatingFromVm; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); // 24 Segoe MDL2 Assets glyphs for the icon picker private static readonly string[] IconGlyphs = @@ -65,8 +65,8 @@ public void SetViewModel(PasswordDetailViewModel vm) // Subtitle PanelSubtitle.Text = isEdit - ? _res.GetString("PwPanelSubtitleEdit") - : _res.GetString("PwPanelSubtitleNew"); + ? _resourceLoader.GetString("PwPanelSubtitleEdit") + : _resourceLoader.GetString("PwPanelSubtitleNew"); // Strength indicator UpdateStrengthLabel(); @@ -219,11 +219,11 @@ private void UpdateStrengthLabel() StrengthLabel.Visibility = Visibility.Visible; StrengthLabel.Text = label switch { - "VeryWeak" => _res.GetString("StrengthVeryWeak"), - "Weak" => _res.GetString("StrengthWeak"), - "Medium" => _res.GetString("StrengthMedium"), - "Strong" => _res.GetString("StrengthStrong"), - "VeryStrong" => _res.GetString("StrengthVeryStrong"), + "VeryWeak" => _resourceLoader.GetString("StrengthVeryWeak"), + "Weak" => _resourceLoader.GetString("StrengthWeak"), + "Medium" => _resourceLoader.GetString("StrengthMedium"), + "Strong" => _resourceLoader.GetString("StrengthStrong"), + "VeryStrong" => _resourceLoader.GetString("StrengthVeryStrong"), _ => label }; StrengthLabel.Foreground = GetStrengthBrush(_viewModel.PasswordStrengthScore); diff --git a/src/PassKey.Desktop/Views/PasswordsListView.xaml.cs b/src/PassKey.Desktop/Views/PasswordsListView.xaml.cs index e3c31d7..cc25a6c 100644 --- a/src/PassKey.Desktop/Views/PasswordsListView.xaml.cs +++ b/src/PassKey.Desktop/Views/PasswordsListView.xaml.cs @@ -5,6 +5,7 @@ using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.Windows.ApplicationModel.Resources; using PassKey.Core.Models; +using PassKey.Desktop.Helpers; using PassKey.Desktop.ViewModels; using Windows.Storage.Streams; @@ -17,7 +18,7 @@ namespace PassKey.Desktop.Views; public sealed partial class PasswordsListView : UserControl { private PasswordsListViewModel? _viewModel; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); public PasswordsListView() { @@ -30,8 +31,8 @@ public async void SetViewModel(PasswordsListViewModel vm) DataContext = vm; // Apply localized empty-state strings (guarantees correct language on every OS locale). - EmptyState.Title = _res.GetString("EmptyPasswordsTitle"); - EmptyState.Subtitle = _res.GetString("EmptyPasswordsSubtitle"); + EmptyState.Title = _resourceLoader.GetString("EmptyPasswordsTitle"); + EmptyState.Subtitle = _resourceLoader.GetString("EmptyPasswordsSubtitle"); vm.PropertyChanged += OnViewModelPropertyChanged; vm.SaveCompleted += ShowSavedToast; @@ -99,19 +100,9 @@ private void UpdateDetailContent() } } - // --- Toast (Step F) --- + // --- Toast --- - public void ShowSavedToast() - { - SavedTip.IsOpen = true; - var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; - timer.Tick += (s, _) => - { - SavedTip.IsOpen = false; - ((DispatcherTimer)s!).Stop(); - }; - timer.Start(); - } + public void ShowSavedToast() => ListViewHelpers.ShowSavedToast(SavedTip); // --- Search --- diff --git a/src/PassKey.Desktop/Views/SecureNotesListView.xaml.cs b/src/PassKey.Desktop/Views/SecureNotesListView.xaml.cs index 3feca18..a0c41a8 100644 --- a/src/PassKey.Desktop/Views/SecureNotesListView.xaml.cs +++ b/src/PassKey.Desktop/Views/SecureNotesListView.xaml.cs @@ -7,6 +7,7 @@ using Microsoft.Windows.ApplicationModel.Resources; using PassKey.Core.Constants; using PassKey.Core.Models; +using PassKey.Desktop.Helpers; using PassKey.Desktop.ViewModels; namespace PassKey.Desktop.Views; @@ -19,7 +20,7 @@ namespace PassKey.Desktop.Views; public sealed partial class SecureNotesListView : UserControl { private SecureNotesListViewModel? _viewModel; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); public SecureNotesListView() { @@ -37,10 +38,10 @@ public async void SetViewModel(SecureNotesListViewModel vm) BuildCategoryFilter(); // Apply localized empty-state strings (guarantees correct language on every OS locale). - EmptyState.Title = _res.GetString("EmptyNotesTitle"); - EmptyState.Subtitle = _res.GetString("EmptyNotesSubtitle"); - FilteredEmptyState.Title = _res.GetString("EmptyFilteredTitle"); - FilteredEmptyState.Subtitle = _res.GetString("EmptyFilteredSubtitle"); + EmptyState.Title = _resourceLoader.GetString("EmptyNotesTitle"); + EmptyState.Subtitle = _resourceLoader.GetString("EmptyNotesSubtitle"); + FilteredEmptyState.Title = _resourceLoader.GetString("EmptyFilteredTitle"); + FilteredEmptyState.Subtitle = _resourceLoader.GetString("EmptyFilteredSubtitle"); await vm.LoadEntriesCommand.ExecuteAsync(null); UpdateList(); @@ -310,18 +311,7 @@ private void NotesList_ContainerContentChanging(ListViewBase sender, // --- Toast conferma salvataggio --- - public void ShowSavedToast() - { - SavedTip.IsOpen = true; - Announce("Nota salvata."); - var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(2) }; - timer.Tick += (s, _) => - { - SavedTip.IsOpen = false; - ((DispatcherTimer)s!).Stop(); - }; - timer.Start(); - } + public void ShowSavedToast() => ListViewHelpers.ShowSavedToast(SavedTip, () => Announce("Nota salvata.")); // --- Helpers --- diff --git a/src/PassKey.Desktop/Views/SettingsView.xaml.cs b/src/PassKey.Desktop/Views/SettingsView.xaml.cs index d6c0827..014b759 100644 --- a/src/PassKey.Desktop/Views/SettingsView.xaml.cs +++ b/src/PassKey.Desktop/Views/SettingsView.xaml.cs @@ -13,7 +13,7 @@ public sealed partial class SettingsView : UserControl { private SettingsViewModel? _viewModel; private bool _updatingFromVm; - private readonly ResourceLoader _res = new(); + private readonly ResourceLoader _resourceLoader = new(); private IDialogQueueService _dialogQueue = null!; /// Raised when the user clicks "Guida e scorciatoie" to navigate to HelpView. @@ -143,21 +143,21 @@ private async void ChangePwButton_Click(object sender, RoutedEventArgs e) var currentPwBox = new SecureInputBox { - PlaceholderText = _res.GetString("ChangePwCurrentPlaceholder"), + PlaceholderText = _resourceLoader.GetString("ChangePwCurrentPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; var newPwBox = new SecureInputBox { - PlaceholderText = _res.GetString("ChangePwNewPlaceholder"), + PlaceholderText = _resourceLoader.GetString("ChangePwNewPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; var confirmPwBox = new SecureInputBox { - PlaceholderText = _res.GetString("ChangePwConfirmPlaceholder"), + PlaceholderText = _resourceLoader.GetString("ChangePwConfirmPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; @@ -178,10 +178,10 @@ private async void ChangePwButton_Click(object sender, RoutedEventArgs e) var dialog = new ContentDialog { - Title = _res.GetString("ChangePwDialogTitle"), + Title = _resourceLoader.GetString("ChangePwDialogTitle"), Content = panel, - PrimaryButtonText = _res.GetString("ChangePwDialogPrimary"), - CloseButtonText = _res.GetString("ChangePwDialogClose"), + PrimaryButtonText = _resourceLoader.GetString("ChangePwDialogPrimary"), + CloseButtonText = _resourceLoader.GetString("ChangePwDialogClose"), DefaultButton = ContentDialogButton.Close, XamlRoot = XamlRoot }; @@ -196,13 +196,13 @@ private async void ChangePwButton_Click(object sender, RoutedEventArgs e) string? error = null; if (string.IsNullOrEmpty(currentPw)) - error = _res.GetString("ChangePwErrEmpty"); + error = _resourceLoader.GetString("ChangePwErrEmpty"); else if (newPw.Length < 8) - error = _res.GetString("ChangePwErrShort"); + error = _resourceLoader.GetString("ChangePwErrShort"); else if (newPw != confirmPw) - error = _res.GetString("ChangePwErrMismatch"); + error = _resourceLoader.GetString("ChangePwErrMismatch"); else if (newPw == currentPw) - error = _res.GetString("ChangePwErrSame"); + error = _resourceLoader.GetString("ChangePwErrSame"); if (error is not null) { @@ -226,14 +226,14 @@ private async void ChangePwButton_Click(object sender, RoutedEventArgs e) if (success) { await ShowInfoDialogAsync( - _res.GetString("ChangePwSuccessTitle"), - _res.GetString("ChangePwSuccessMessage")); + _resourceLoader.GetString("ChangePwSuccessTitle"), + _resourceLoader.GetString("ChangePwSuccessMessage")); } else { await ShowInfoDialogAsync( - _res.GetString("ChangePwErrorTitle"), - _res.GetString("ChangePwErrorMessage")); + _resourceLoader.GetString("ChangePwErrorTitle"), + _resourceLoader.GetString("ChangePwErrorMessage")); } } finally @@ -283,11 +283,11 @@ private async void CheckUpdateButton_Click(object sender, RoutedEventArgs e) private string FormatLastCheckTime(DateTime? utc) { - if (!utc.HasValue) return _res.GetString("UpdateNeverChecked"); + if (!utc.HasValue) return _resourceLoader.GetString("UpdateNeverChecked"); var diff = DateTime.Now - utc.Value.ToLocalTime(); - if (diff.TotalMinutes < 1) return _res.GetString("UpdateJustChecked"); - if (diff.TotalHours < 1) return string.Format(_res.GetString("UpdateCheckedMinutesAgo"), (int)diff.TotalMinutes); - if (diff.TotalDays < 1) return string.Format(_res.GetString("UpdateCheckedHoursAgo"), (int)diff.TotalHours); + if (diff.TotalMinutes < 1) return _resourceLoader.GetString("UpdateJustChecked"); + if (diff.TotalHours < 1) return string.Format(_resourceLoader.GetString("UpdateCheckedMinutesAgo"), (int)diff.TotalMinutes); + if (diff.TotalDays < 1) return string.Format(_resourceLoader.GetString("UpdateCheckedHoursAgo"), (int)diff.TotalHours); return utc.Value.ToLocalTime().ToString("d MMM yyyy, HH:mm"); } @@ -321,13 +321,13 @@ private async void ImportButton_Click(object sender, RoutedEventArgs e) { var pwBox = new SecureInputBox { - PlaceholderText = _res.GetString("BackupPwPlaceholder"), + PlaceholderText = _resourceLoader.GetString("BackupPwPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; var confirmBox = new SecureInputBox { - PlaceholderText = _res.GetString("BackupPwConfirmPlaceholder"), + PlaceholderText = _resourceLoader.GetString("BackupPwConfirmPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; @@ -341,7 +341,7 @@ private async void ImportButton_Click(object sender, RoutedEventArgs e) var descText = new TextBlock { - Text = _res.GetString("BackupPwDialogDesc"), + Text = _resourceLoader.GetString("BackupPwDialogDesc"), TextWrapping = TextWrapping.Wrap, Margin = new Thickness(0, 0, 0, 12) }; @@ -354,10 +354,10 @@ private async void ImportButton_Click(object sender, RoutedEventArgs e) var dialog = new ContentDialog { - Title = _res.GetString("BackupPwDialogTitle"), + Title = _resourceLoader.GetString("BackupPwDialogTitle"), Content = panel, PrimaryButtonText = "OK", - CloseButtonText = _res.GetString("RestoreWarningCancel"), + CloseButtonText = _resourceLoader.GetString("RestoreWarningCancel"), DefaultButton = ContentDialogButton.Primary, XamlRoot = XamlRoot }; @@ -366,11 +366,11 @@ private async void ImportButton_Click(object sender, RoutedEventArgs e) { string? error = null; if (string.IsNullOrEmpty(pwBox.Password)) - error = _res.GetString("BackupPwErrEmpty"); + error = _resourceLoader.GetString("BackupPwErrEmpty"); else if (pwBox.Password.Length < 8) - error = _res.GetString("BackupPwErrShort"); + error = _resourceLoader.GetString("BackupPwErrShort"); else if (pwBox.Password != confirmBox.Password) - error = _res.GetString("BackupPwErrMismatch"); + error = _resourceLoader.GetString("BackupPwErrMismatch"); if (error is not null) { @@ -390,10 +390,10 @@ private async Task OnRestoreWarningRequested() { var dialog = new ContentDialog { - Title = _res.GetString("RestoreWarningTitle"), - Content = _res.GetString("RestoreWarningMessage"), - PrimaryButtonText = _res.GetString("RestoreWarningConfirm"), - CloseButtonText = _res.GetString("RestoreWarningCancel"), + Title = _resourceLoader.GetString("RestoreWarningTitle"), + Content = _resourceLoader.GetString("RestoreWarningMessage"), + PrimaryButtonText = _resourceLoader.GetString("RestoreWarningConfirm"), + CloseButtonText = _resourceLoader.GetString("RestoreWarningCancel"), DefaultButton = ContentDialogButton.Close, XamlRoot = XamlRoot }; @@ -405,17 +405,17 @@ private async Task OnRestoreWarningRequested() { var pwBox = new SecureInputBox { - PlaceholderText = _res.GetString("RestorePwPlaceholder"), + PlaceholderText = _resourceLoader.GetString("RestorePwPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; var dialog = new ContentDialog { - Title = _res.GetString("RestorePwDialogTitle"), + Title = _resourceLoader.GetString("RestorePwDialogTitle"), Content = pwBox, PrimaryButtonText = "OK", - CloseButtonText = _res.GetString("RestoreWarningCancel"), + CloseButtonText = _resourceLoader.GetString("RestoreWarningCancel"), DefaultButton = ContentDialogButton.Primary, XamlRoot = XamlRoot }; @@ -429,18 +429,18 @@ private async Task OnRestoreWarningRequested() private async Task<(ImportFormat format, bool confirmed)> OnImportFormatRequested() { var combo = new ComboBox { Width = 320 }; - combo.Items.Add(new ComboBoxItem { Content = _res.GetString("ImportFormatCsv"), Tag = ImportFormat.Csv }); - combo.Items.Add(new ComboBoxItem { Content = _res.GetString("ImportFormatKdbx"), Tag = ImportFormat.Kdbx }); - combo.Items.Add(new ComboBoxItem { Content = _res.GetString("ImportFormatOnePux"), Tag = ImportFormat.OnePux }); - combo.Items.Add(new ComboBoxItem { Content = _res.GetString("ImportFormatBitwarden"), Tag = ImportFormat.Bitwarden }); + combo.Items.Add(new ComboBoxItem { Content = _resourceLoader.GetString("ImportFormatCsv"), Tag = ImportFormat.Csv }); + combo.Items.Add(new ComboBoxItem { Content = _resourceLoader.GetString("ImportFormatKdbx"), Tag = ImportFormat.Kdbx }); + combo.Items.Add(new ComboBoxItem { Content = _resourceLoader.GetString("ImportFormatOnePux"), Tag = ImportFormat.OnePux }); + combo.Items.Add(new ComboBoxItem { Content = _resourceLoader.GetString("ImportFormatBitwarden"), Tag = ImportFormat.Bitwarden }); combo.SelectedIndex = 0; var dialog = new ContentDialog { - Title = _res.GetString("ImportFormatDialogTitle"), + Title = _resourceLoader.GetString("ImportFormatDialogTitle"), Content = combo, PrimaryButtonText = "OK", - CloseButtonText = _res.GetString("RestoreWarningCancel"), + CloseButtonText = _resourceLoader.GetString("RestoreWarningCancel"), DefaultButton = ContentDialogButton.Primary, XamlRoot = XamlRoot }; @@ -458,17 +458,17 @@ private async Task OnRestoreWarningRequested() { var pwBox = new SecureInputBox { - PlaceholderText = _res.GetString("ImportKdbxPwPlaceholder"), + PlaceholderText = _resourceLoader.GetString("ImportKdbxPwPlaceholder"), ShowRevealButton = Visibility.Visible, Width = 320 }; var dialog = new ContentDialog { - Title = _res.GetString("ImportKdbxPwTitle"), + Title = _resourceLoader.GetString("ImportKdbxPwTitle"), Content = pwBox, PrimaryButtonText = "OK", - CloseButtonText = _res.GetString("RestoreWarningCancel"), + CloseButtonText = _resourceLoader.GetString("RestoreWarningCancel"), DefaultButton = ContentDialogButton.Primary, XamlRoot = XamlRoot }; @@ -488,15 +488,15 @@ private async Task OnRestoreWarningRequested() Margin = new Thickness(0, 0, 0, 16) }; var lines = new System.Text.StringBuilder(); - if (pwCount > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryPasswords"), pwCount)); - if (cardCount > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryCards"), cardCount)); - if (idCount > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryIdentities"), idCount)); - if (noteCount > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryNotes"), noteCount)); + if (pwCount > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryPasswords"), pwCount)); + if (cardCount > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryCards"), cardCount)); + if (idCount > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryIdentities"), idCount)); + if (noteCount > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryNotes"), noteCount)); summaryText.Text = lines.ToString().TrimEnd(); - var skipRadio = new RadioButton { Content = _res.GetString("ImportMergeSkip"), IsChecked = true }; - var overwriteRadio = new RadioButton { Content = _res.GetString("ImportMergeOverwrite") }; - var keepBothRadio = new RadioButton { Content = _res.GetString("ImportMergeKeepBoth") }; + var skipRadio = new RadioButton { Content = _resourceLoader.GetString("ImportMergeSkip"), IsChecked = true }; + var overwriteRadio = new RadioButton { Content = _resourceLoader.GetString("ImportMergeOverwrite") }; + var keepBothRadio = new RadioButton { Content = _resourceLoader.GetString("ImportMergeKeepBoth") }; var panel = new StackPanel { Spacing = 8 }; panel.Children.Add(summaryText); @@ -506,10 +506,10 @@ private async Task OnRestoreWarningRequested() var dialog = new ContentDialog { - Title = _res.GetString("ImportMergeTitle"), + Title = _resourceLoader.GetString("ImportMergeTitle"), Content = panel, PrimaryButtonText = "OK", - CloseButtonText = _res.GetString("RestoreWarningCancel"), + CloseButtonText = _resourceLoader.GetString("RestoreWarningCancel"), DefaultButton = ContentDialogButton.Primary, XamlRoot = XamlRoot }; @@ -529,37 +529,37 @@ private async Task OnRestoreWarningRequested() private async Task OnImportCompleted(ImportResult result) { var lines = new System.Text.StringBuilder(); - if (result.PasswordsImported > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryPasswords"), result.PasswordsImported)); - if (result.CardsImported > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryCards"), result.CardsImported)); - if (result.IdentitiesImported > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryIdentities"), result.IdentitiesImported)); - if (result.NotesImported > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummaryNotes"), result.NotesImported)); - if (result.Skipped > 0) lines.AppendLine(string.Format(_res.GetString("ImportSummarySkipped"), result.Skipped)); + if (result.PasswordsImported > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryPasswords"), result.PasswordsImported)); + if (result.CardsImported > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryCards"), result.CardsImported)); + if (result.IdentitiesImported > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryIdentities"), result.IdentitiesImported)); + if (result.NotesImported > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummaryNotes"), result.NotesImported)); + if (result.Skipped > 0) lines.AppendLine(string.Format(_resourceLoader.GetString("ImportSummarySkipped"), result.Skipped)); - await ShowInfoDialogAsync(_res.GetString("ImportSummaryTitle"), lines.ToString().TrimEnd()); + await ShowInfoDialogAsync(_resourceLoader.GetString("ImportSummaryTitle"), lines.ToString().TrimEnd()); } private async Task OnBackupCompleted(string path) { await ShowInfoDialogAsync( - _res.GetString("BackupSuccessTitle"), - _res.GetString("BackupSuccessMessage")); + _resourceLoader.GetString("BackupSuccessTitle"), + _resourceLoader.GetString("BackupSuccessMessage")); } private async Task OnRestoreCompleted() { await ShowInfoDialogAsync( - _res.GetString("RestoreSuccessTitle"), - _res.GetString("RestoreSuccessMessage")); + _resourceLoader.GetString("RestoreSuccessTitle"), + _resourceLoader.GetString("RestoreSuccessMessage")); } private async Task OnOperationError(string errorCode) { var message = errorCode switch { - "WRONG_PASSWORD" => _res.GetString("RestoreErrorWrongPw"), - "INVALID_FILE" => _res.GetString("RestoreErrorInvalid"), + "WRONG_PASSWORD" => _resourceLoader.GetString("RestoreErrorWrongPw"), + "INVALID_FILE" => _resourceLoader.GetString("RestoreErrorInvalid"), _ => errorCode }; - await ShowInfoDialogAsync(_res.GetString("ImportErrorTitle"), message); + await ShowInfoDialogAsync(_resourceLoader.GetString("ImportErrorTitle"), message); } }