mirror of
https://github.com/KeyKeeperApp/KeyKeeper.git
synced 2026-05-05 22:36:31 +03:00
Feat/EditEntry
This commit is contained in:
@@ -39,6 +39,16 @@ public class UnlockedRepositoryViewModel : ViewModelBase
|
|||||||
OnPropertyChanged(nameof(Passwords));
|
OnPropertyChanged(nameof(Passwords));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void UpdateEntry(PassStoreEntryPassword updatedEntry)
|
||||||
|
{
|
||||||
|
var root = passStore.GetRootDirectory() as PassStoreEntryGroup;
|
||||||
|
if (root == null) return;
|
||||||
|
|
||||||
|
root.DeleteEntry(updatedEntry.Id);
|
||||||
|
root.ChildEntries.Add(updatedEntry);
|
||||||
|
OnPropertyChanged(nameof(Passwords));
|
||||||
|
}
|
||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
passStore.Save();
|
passStore.Save();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace KeyKeeper.Views;
|
|||||||
public partial class EntryEditWindow : Window
|
public partial class EntryEditWindow : Window
|
||||||
{
|
{
|
||||||
public PassStoreEntryPassword? EditedEntry;
|
public PassStoreEntryPassword? EditedEntry;
|
||||||
|
private PassStoreEntryPassword? _originalEntry;
|
||||||
|
|
||||||
public EntryEditWindow()
|
public EntryEditWindow()
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,14 @@ public partial class EntryEditWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetEntry(PassStoreEntryPassword entry)
|
||||||
|
{
|
||||||
|
_originalEntry = entry;
|
||||||
|
EntryNameEdit.Text = entry.Name;
|
||||||
|
UsernameEdit.Text = entry.Username.Value;
|
||||||
|
PasswordEdit.Text = entry.Password.Value;
|
||||||
|
}
|
||||||
|
|
||||||
private void PasswordTextChanged(object? sender, TextChangedEventArgs e)
|
private void PasswordTextChanged(object? sender, TextChangedEventArgs e)
|
||||||
{
|
{
|
||||||
string password = PasswordEdit?.Text ?? "";
|
string password = PasswordEdit?.Text ?? "";
|
||||||
@@ -39,49 +48,34 @@ public partial class EntryEditWindow : Window
|
|||||||
}
|
}
|
||||||
|
|
||||||
int strength = CalculatePasswordStrength(password);
|
int strength = CalculatePasswordStrength(password);
|
||||||
|
|
||||||
double maxWidth = PasswordStrengthIndicator.Bounds.Width;
|
double maxWidth = PasswordStrengthIndicator.Bounds.Width;
|
||||||
if (maxWidth <= 0) maxWidth = 200;
|
if (maxWidth <= 0) maxWidth = 200;
|
||||||
|
|
||||||
PasswordStrengthFill.Width = (strength / 100.0) * maxWidth;
|
PasswordStrengthFill.Width = (strength / 100.0) * maxWidth;
|
||||||
|
|
||||||
if (strength < 20)
|
if (strength < 20)
|
||||||
{
|
|
||||||
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Red);
|
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Red);
|
||||||
}
|
|
||||||
else if (strength < 50)
|
else if (strength < 50)
|
||||||
{
|
|
||||||
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Orange);
|
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Orange);
|
||||||
}
|
|
||||||
else if (strength < 70)
|
else if (strength < 70)
|
||||||
{
|
|
||||||
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Gold);
|
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Gold);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Green);
|
PasswordStrengthFill.Background = new SolidColorBrush(Colors.Green);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int CalculatePasswordStrength(string password)
|
private int CalculatePasswordStrength(string password)
|
||||||
{
|
{
|
||||||
int score = 0;
|
int score = 0;
|
||||||
|
|
||||||
if (password.Length >= 8) score += 20;
|
if (password.Length >= 8) score += 20;
|
||||||
if (password.Length >= 12) score += 20;
|
if (password.Length >= 12) score += 20;
|
||||||
if (password.Length >= 16) score += 15;
|
if (password.Length >= 16) score += 15;
|
||||||
|
|
||||||
if (Regex.IsMatch(password, @"\d")) score += 10;
|
if (Regex.IsMatch(password, @"\d")) score += 10;
|
||||||
|
|
||||||
if (Regex.IsMatch(password, @"[a-z]")) score += 15;
|
if (Regex.IsMatch(password, @"[a-z]")) score += 15;
|
||||||
|
|
||||||
if (Regex.IsMatch(password, @"[A-Z]")) score += 15;
|
if (Regex.IsMatch(password, @"[A-Z]")) score += 15;
|
||||||
|
|
||||||
if (Regex.IsMatch(password, @"[!@#$%^&*()_+\-=\[\]{};':""\\|,.<>\/?]")) score += 20;
|
if (Regex.IsMatch(password, @"[!@#$%^&*()_+\-=\[\]{};':""\\|,.<>\/?]")) score += 20;
|
||||||
|
|
||||||
var uniqueChars = new System.Collections.Generic.HashSet<char>(password).Count;
|
var uniqueChars = new System.Collections.Generic.HashSet<char>(password).Count;
|
||||||
score += Math.Min(20, uniqueChars * 2);
|
score += Math.Min(20, uniqueChars * 2);
|
||||||
|
|
||||||
return Math.Min(100, score);
|
return Math.Min(100, score);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,22 +90,17 @@ public partial class EntryEditWindow : Window
|
|||||||
string password = PasswordEdit?.Text ?? "";
|
string password = PasswordEdit?.Text ?? "";
|
||||||
if (string.IsNullOrEmpty(password)) return;
|
if (string.IsNullOrEmpty(password)) return;
|
||||||
|
|
||||||
|
Guid id = _originalEntry?.Id ?? Guid.NewGuid();
|
||||||
|
DateTime created = DateTime.UtcNow;
|
||||||
|
|
||||||
EditedEntry = new PassStoreEntryPassword(
|
EditedEntry = new PassStoreEntryPassword(
|
||||||
Guid.NewGuid(),
|
id,
|
||||||
DateTime.UtcNow,
|
created,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
EntryIconType.DEFAULT,
|
EntryIconType.DEFAULT,
|
||||||
name,
|
name,
|
||||||
new LoginField()
|
new LoginField() { Type = LOGIN_FIELD_USERNAME_ID, Value = username },
|
||||||
{
|
new LoginField() { Type = LOGIN_FIELD_PASSWORD_ID, Value = password },
|
||||||
Type = LOGIN_FIELD_USERNAME_ID,
|
|
||||||
Value = username
|
|
||||||
},
|
|
||||||
new LoginField()
|
|
||||||
{
|
|
||||||
Type = LOGIN_FIELD_PASSWORD_ID,
|
|
||||||
Value = password
|
|
||||||
},
|
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
Close();
|
Close();
|
||||||
|
|||||||
@@ -11,113 +11,133 @@
|
|||||||
Background="White"
|
Background="White"
|
||||||
x:DataType="vm:RepositoryWindowViewModel">
|
x:DataType="vm:RepositoryWindowViewModel">
|
||||||
|
|
||||||
<Window.DataTemplates>
|
<Window.DataTemplates>
|
||||||
<DataTemplate DataType="{x:Type vm:UnlockedRepositoryViewModel}">
|
<DataTemplate DataType="{x:Type vm:UnlockedRepositoryViewModel}">
|
||||||
<Grid>
|
<Grid>
|
||||||
<!-- Синий левый край -->
|
<!-- Синий левый край -->
|
||||||
<Border Width="200"
|
<Border Width="200"
|
||||||
Background="#2328C4"
|
Background="#2328C4"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Stretch"/>
|
||||||
|
<StackPanel Margin="20" HorizontalAlignment="Left">
|
||||||
|
<!-- Надпись KeyKeeper -->
|
||||||
|
<TextBlock Text="KeyKeeper"
|
||||||
|
FontSize="32"
|
||||||
|
FontWeight="Bold"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,0,0,20"/>
|
||||||
|
|
||||||
|
<!-- Рамочка -->
|
||||||
|
<Border BorderBrush="Gray"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="5"
|
||||||
|
Padding="20"
|
||||||
|
Background="#F5F5F5"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Left">
|
||||||
|
<Button Content="All Passwords"
|
||||||
|
Width="120"
|
||||||
|
Height="30"
|
||||||
|
HorizontalAlignment="Left"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Border>
|
||||||
|
<!-- Save Passwords -->
|
||||||
|
<Button Content="Save Passwords"
|
||||||
|
Classes="accentSidebarButton"
|
||||||
|
Click="SaveButton_Click"
|
||||||
|
Height="30"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Stretch"/>
|
Margin="0,20,0,0"/>
|
||||||
<StackPanel Margin="20" HorizontalAlignment="Left">
|
|
||||||
<!-- Надпись KeyKeeper -->
|
|
||||||
<TextBlock Text="KeyKeeper"
|
|
||||||
FontSize="32"
|
|
||||||
FontWeight="Bold"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Margin="0,0,0,20"/>
|
|
||||||
|
|
||||||
<!-- Рамочка -->
|
<!-- New Entry -->
|
||||||
<Border BorderBrush="Gray"
|
<Button Content="New Entry"
|
||||||
BorderThickness="1"
|
Classes="accentSidebarButton"
|
||||||
CornerRadius="5"
|
Click="AddEntryButton_Click"
|
||||||
Padding="20"
|
Height="30"
|
||||||
Background="#F5F5F5"
|
HorizontalAlignment="Left"
|
||||||
HorizontalAlignment="Left">
|
Margin="0,20,0,0"/>
|
||||||
|
|
||||||
<StackPanel HorizontalAlignment="Left">
|
<!-- Edit Selected Entry -->
|
||||||
<Button Content="All Passwords"
|
<Button Content="Edit Selected Entry"
|
||||||
Width="120"
|
Classes="accentSidebarButton"
|
||||||
Height="30"
|
Click="EditEntryButton_Click"
|
||||||
HorizontalAlignment="Left"/>
|
Height="30"
|
||||||
</StackPanel>
|
HorizontalAlignment="Left"
|
||||||
|
Margin="0,20,0,0"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
</Border>
|
<!-- ListBox с паролями -->
|
||||||
<!-- Save Passwords -->
|
<ListBox x:Name="PasswordsListBox"
|
||||||
<Button Content="Save Passwords"
|
Width="580"
|
||||||
Classes="accentSidebarButton"
|
Margin="210 10 10 10"
|
||||||
Click="SaveButton_Click"
|
ItemsSource="{Binding Passwords}"
|
||||||
Height="30"
|
Background="Transparent"
|
||||||
HorizontalAlignment="Left"
|
SelectionMode="Single">
|
||||||
Margin="0,20,0,0"/>
|
<ListBox.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<WrapPanel Orientation="Horizontal" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ListBox.ItemsPanel>
|
||||||
|
|
||||||
<Button Content="New Entry"
|
<ListBox.ItemTemplate>
|
||||||
Classes="accentSidebarButton"
|
<DataTemplate>
|
||||||
Click="AddEntryButton_Click"
|
<Border Background="Transparent" DoubleTapped="Entry_DoubleTapped">
|
||||||
Height="30"
|
<StackPanel Width="100"
|
||||||
HorizontalAlignment="Left"
|
Margin="10"
|
||||||
Margin="0,20,0,0"/>
|
HorizontalAlignment="Center">
|
||||||
</StackPanel>
|
<Svg Path="{Binding IconPath}" Width="48" Height="48"/>
|
||||||
<ListBox Width="580"
|
<TextBlock Text="{Binding Name}"
|
||||||
Margin="210 10 10 10"
|
HorizontalAlignment="Center"
|
||||||
ItemsSource="{Binding Passwords}"
|
Foreground="Black" />
|
||||||
Background="Transparent">
|
<TextBlock Text="{Binding Username.Value}"
|
||||||
<ListBox.ItemsPanel>
|
Foreground="#666"
|
||||||
<ItemsPanelTemplate>
|
HorizontalAlignment="Center" />
|
||||||
<WrapPanel Orientation="Horizontal" />
|
</StackPanel>
|
||||||
</ItemsPanelTemplate>
|
<Border.ContextMenu>
|
||||||
</ListBox.ItemsPanel>
|
<ContextMenu>
|
||||||
|
<MenuItem Name="entryCtxMenuCopyUsername" Header="Copy username" Click="EntryContextMenuItem_Click"/>
|
||||||
|
<MenuItem Name="entryCtxMenuCopyPassword" Header="Copy password" Click="EntryContextMenuItem_Click"/>
|
||||||
|
<!-- Новый пункт меню "Edit" -->
|
||||||
|
<MenuItem Name="entryCtxMenuEdit" Header="Edit" Click="EntryContextMenuItem_Click"/>
|
||||||
|
<MenuItem Name="entryCtxMenuDelete" Header="Delete" Click="EntryContextMenuItem_Click"/>
|
||||||
|
</ContextMenu>
|
||||||
|
</Border.ContextMenu>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
<ListBox.ItemTemplate>
|
<kkp:ToastNotificationHost x:Name="NotificationHost"
|
||||||
<DataTemplate>
|
HorizontalAlignment="Center"
|
||||||
<Border Background="Transparent" DoubleTapped="Entry_DoubleTapped">
|
VerticalAlignment="Center"
|
||||||
<StackPanel Width="100"
|
Margin="20"
|
||||||
Margin="10"
|
Duration="0:0:2" />
|
||||||
HorizontalAlignment="Center">
|
</Grid>
|
||||||
<Svg Path="{Binding IconPath}" Width="48" Height="48"
|
</DataTemplate>
|
||||||
/>
|
|
||||||
<TextBlock Text="{Binding Name}"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
Foreground="Black" />
|
|
||||||
<TextBlock Text="{Binding Username.Value}"
|
|
||||||
Foreground="#666"
|
|
||||||
HorizontalAlignment="Center" />
|
|
||||||
</StackPanel>
|
|
||||||
<Border.ContextMenu>
|
|
||||||
<ContextMenu>
|
|
||||||
<MenuItem Name="entryCtxMenuCopyUsername" Header="Copy username" Click="EntryContextMenuItem_Click"/>
|
|
||||||
<MenuItem Name="entryCtxMenuCopyPassword" Header="Copy password" Click="EntryContextMenuItem_Click"/>
|
|
||||||
<MenuItem Name="entryCtxMenuDelete" Header="Delete" Click="EntryContextMenuItem_Click"/>
|
|
||||||
</ContextMenu>
|
|
||||||
</Border.ContextMenu>
|
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
</ListBox>
|
|
||||||
<kkp:ToastNotificationHost x:Name="NotificationHost" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="20" Duration="0:0:2" />
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
<DataTemplate DataType="{x:Type vm:LockedRepositoryViewModel}">
|
|
||||||
<StackPanel Margin="20"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Spacing="10">
|
|
||||||
<TextBlock Text="Enter credentials to unlock"
|
|
||||||
Foreground="#2328C4"
|
|
||||||
FontSize="32" />
|
|
||||||
|
|
||||||
<TextBox x:Name="UnlockPasswordEdit"
|
<DataTemplate DataType="{x:Type vm:LockedRepositoryViewModel}">
|
||||||
Text="{Binding UnlockPassword, Mode=TwoWay}"
|
<StackPanel Margin="20"
|
||||||
PasswordChar="*"
|
HorizontalAlignment="Center"
|
||||||
Width="450" />
|
VerticalAlignment="Center"
|
||||||
|
Spacing="10">
|
||||||
|
<TextBlock Text="Enter credentials to unlock"
|
||||||
|
Foreground="#2328C4"
|
||||||
|
FontSize="32" />
|
||||||
|
|
||||||
<Button x:Name="UnlockButton"
|
<TextBox x:Name="UnlockPasswordEdit"
|
||||||
Command="{Binding TryUnlock}"
|
Text="{Binding UnlockPassword, Mode=TwoWay}"
|
||||||
HorizontalAlignment="Center"
|
PasswordChar="*"
|
||||||
Content="Unlock!" />
|
Width="450" />
|
||||||
</StackPanel>
|
|
||||||
</DataTemplate>
|
|
||||||
</Window.DataTemplates>
|
|
||||||
|
|
||||||
<ContentControl Content="{Binding CurrentPage}"/>
|
<Button x:Name="UnlockButton"
|
||||||
|
Command="{Binding TryUnlock}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Content="Unlock!" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</Window.DataTemplates>
|
||||||
|
|
||||||
|
<ContentControl Content="{Binding CurrentPage}"/>
|
||||||
</Window>
|
</Window>
|
||||||
@@ -10,7 +10,7 @@ using Avalonia.Controls.Presenters;
|
|||||||
|
|
||||||
namespace KeyKeeper.Views;
|
namespace KeyKeeper.Views;
|
||||||
|
|
||||||
public partial class RepositoryWindow: Window
|
public partial class RepositoryWindow : Window
|
||||||
{
|
{
|
||||||
public RepositoryWindow(RepositoryWindowViewModel model)
|
public RepositoryWindow(RepositoryWindowViewModel model)
|
||||||
{
|
{
|
||||||
@@ -39,6 +39,36 @@ public partial class RepositoryWindow: Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void EditEntryButton_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (DataContext is RepositoryWindowViewModel vm_ && vm_.CurrentPage is UnlockedRepositoryViewModel vm)
|
||||||
|
{
|
||||||
|
var listBox = this.FindControlRecursive<ListBox>("PasswordsListBox");
|
||||||
|
if (listBox == null)
|
||||||
|
{
|
||||||
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("ListBox not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedEntry = listBox.SelectedItem as PassStoreEntryPassword;
|
||||||
|
if (selectedEntry == null)
|
||||||
|
{
|
||||||
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("No entry selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EntryEditWindow dialog = new();
|
||||||
|
dialog.SetEntry(selectedEntry);
|
||||||
|
await dialog.ShowDialog(this);
|
||||||
|
|
||||||
|
if (dialog.EditedEntry != null)
|
||||||
|
{
|
||||||
|
vm.UpdateEntry(dialog.EditedEntry);
|
||||||
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Entry updated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void SaveButton_Click(object sender, RoutedEventArgs args)
|
private void SaveButton_Click(object sender, RoutedEventArgs args)
|
||||||
{
|
{
|
||||||
if (DataContext is RepositoryWindowViewModel vm && vm.CurrentPage is UnlockedRepositoryViewModel pageVm)
|
if (DataContext is RepositoryWindowViewModel vm && vm.CurrentPage is UnlockedRepositoryViewModel pageVm)
|
||||||
@@ -49,40 +79,46 @@ public partial class RepositoryWindow: Window
|
|||||||
|
|
||||||
private void Entry_DoubleTapped(object sender, TappedEventArgs args)
|
private void Entry_DoubleTapped(object sender, TappedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.Source is StyledElement s)
|
if (args.Source is StyledElement s && s.DataContext is PassStoreEntryPassword pwd)
|
||||||
{
|
{
|
||||||
if (s.DataContext is PassStoreEntryPassword pwd)
|
Clipboard!.SetTextAsync(pwd.Password.Value);
|
||||||
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Password copied to clipboard");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void EntryContextMenuItem_Click(object sender, RoutedEventArgs args)
|
||||||
|
{
|
||||||
|
if (args.Source is StyledElement s && s.DataContext is PassStoreEntryPassword pwd)
|
||||||
|
{
|
||||||
|
if (s.Name == "entryCtxMenuCopyUsername")
|
||||||
|
{
|
||||||
|
Clipboard!.SetTextAsync(pwd.Username.Value);
|
||||||
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Username copied to clipboard");
|
||||||
|
}
|
||||||
|
else if (s.Name == "entryCtxMenuCopyPassword")
|
||||||
{
|
{
|
||||||
Clipboard!.SetTextAsync(pwd.Password.Value);
|
Clipboard!.SetTextAsync(pwd.Password.Value);
|
||||||
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Password copied to clipboard");
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Password copied to clipboard");
|
||||||
}
|
}
|
||||||
}
|
else if (s.Name == "entryCtxMenuEdit")
|
||||||
}
|
|
||||||
|
|
||||||
private void EntryContextMenuItem_Click(object sender, RoutedEventArgs args) {
|
|
||||||
if (args.Source is StyledElement s)
|
|
||||||
{
|
|
||||||
if (s.DataContext is PassStoreEntryPassword pwd)
|
|
||||||
{
|
{
|
||||||
if (s.Name == "entryCtxMenuCopyUsername")
|
if (DataContext is RepositoryWindowViewModel vm && vm.CurrentPage is UnlockedRepositoryViewModel pageVm)
|
||||||
{
|
{
|
||||||
Clipboard!.SetTextAsync(pwd.Username.Value);
|
EntryEditWindow dialog = new();
|
||||||
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Username copied to clipboard");
|
dialog.SetEntry(pwd);
|
||||||
}
|
await dialog.ShowDialog(this);
|
||||||
else if (s.Name == "entryCtxMenuCopyPassword")
|
if (dialog.EditedEntry != null)
|
||||||
{
|
|
||||||
Clipboard!.SetTextAsync(pwd.Password.Value);
|
|
||||||
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Password copied to clipboard");
|
|
||||||
}
|
|
||||||
else if (s.Name == "entryCtxMenuDelete")
|
|
||||||
{
|
|
||||||
if (s.DataContext is PassStoreEntryPassword entry)
|
|
||||||
{
|
{
|
||||||
if (DataContext is RepositoryWindowViewModel vm && vm.CurrentPage is UnlockedRepositoryViewModel pageVm)
|
pageVm.UpdateEntry(dialog.EditedEntry);
|
||||||
{
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Entry updated");
|
||||||
pageVm.DeleteEntry(entry.Id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (s.Name == "entryCtxMenuDelete")
|
||||||
|
{
|
||||||
|
if (DataContext is RepositoryWindowViewModel vm && vm.CurrentPage is UnlockedRepositoryViewModel pageVm)
|
||||||
|
{
|
||||||
|
pageVm.DeleteEntry(pwd.Id);
|
||||||
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Entry deleted");
|
this.FindControlRecursive<ToastNotificationHost>("NotificationHost")?.Show("Entry deleted");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,31 +135,23 @@ public static class VisualTreeExtensions
|
|||||||
|
|
||||||
private static T? FindControlRecursiveInternal<T>(Visual parent, string name, int depth) where T : Visual
|
private static T? FindControlRecursiveInternal<T>(Visual parent, string name, int depth) where T : Visual
|
||||||
{
|
{
|
||||||
if (parent == null)
|
if (parent == null) return null;
|
||||||
return null;
|
if (parent is T t && parent.Name == name) return t;
|
||||||
|
|
||||||
if (parent is T t && parent.Name == name)
|
|
||||||
return t;
|
|
||||||
|
|
||||||
foreach (var child in parent.GetVisualChildren())
|
foreach (var child in parent.GetVisualChildren())
|
||||||
{
|
{
|
||||||
if (child == null)
|
if (child == null) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
var result = FindControlRecursiveInternal<T>(child, name, depth + 1);
|
var result = FindControlRecursiveInternal<T>(child, name, depth + 1);
|
||||||
if (result != null)
|
if (result != null) return result;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check logical children if they're not in visual tree
|
|
||||||
if (parent is ContentPresenter contentPresenter)
|
if (parent is ContentPresenter contentPresenter)
|
||||||
{
|
{
|
||||||
var content = contentPresenter.Content as Visual;
|
var content = contentPresenter.Content as Visual;
|
||||||
if (content != null)
|
if (content != null)
|
||||||
{
|
{
|
||||||
var result = FindControlRecursiveInternal<T>(content, name, depth + 1);
|
var result = FindControlRecursiveInternal<T>(content, name, depth + 1);
|
||||||
if (result != null)
|
if (result != null) return result;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user