Implemented proper viewModels
This commit is contained in:
@@ -15,7 +15,8 @@ class Program
|
||||
var application = Adw.Application.New("org.typomustakes.keychain", Gio.ApplicationFlags.FlagsNone);
|
||||
application.OnActivate += (sender, args) =>
|
||||
{
|
||||
var window = new UI.MainWindow().Window;
|
||||
var passwordStoreService = provider.GetRequiredService<IPasswordStoreService>();
|
||||
var window = new UI.MainWindow(passwordStoreService).Window;
|
||||
window.Application = (Adw.Application)sender;
|
||||
window.Show();
|
||||
};
|
||||
|
||||
@@ -1,59 +1,109 @@
|
||||
using Adw;
|
||||
using Keychain.ViewModels;
|
||||
|
||||
namespace Keychain.UI;
|
||||
|
||||
public class AddShortcutWindow
|
||||
{
|
||||
private readonly PasswordStoreShortcutCollection shortcuts;
|
||||
public Dialog Dialog { get; }
|
||||
private const string dialogId = "add_shortcut_dialog";
|
||||
private Gtk.Button? closeButton;
|
||||
private const string closeButtonId = "close_button";
|
||||
private Gtk.Button? iconPickerButton;
|
||||
private const string iconPickerButtonId = "icon_picker_button";
|
||||
private EntryRow? displayNameEntryRow;
|
||||
private const string displayNameEntryRowId = "display_name_entry_row";
|
||||
private ActionRow? folderActionRow;
|
||||
private const string folderActionRowId = "folder_action_row";
|
||||
private Gtk.Button? browseButton;
|
||||
private const string browseButtonId = "folder_browse_button";
|
||||
private Gtk.Button? clearSelectedFolderButton;
|
||||
private const string clearSelectedFolderButtonId = "clear_selected_folder_button";
|
||||
private Gtk.Button? saveButton;
|
||||
private const string saveButtonId = "save_button";
|
||||
|
||||
public AddShortcutWindow()
|
||||
public AddShortcutWindow(PasswordStoreShortcutCollection shortcuts)
|
||||
{
|
||||
this.shortcuts = shortcuts;
|
||||
var builder = new Gtk.Builder("Keychain.UI.AddShortcutWindow.AddShortcutWindow.xml");
|
||||
|
||||
Dialog = builder.GetObject("add_shortcut_dialog") as Dialog;
|
||||
Dialog = builder.GetObject(dialogId) as Dialog;
|
||||
if (Dialog == null)
|
||||
{
|
||||
throw new Exception("Failed to load embedded resource AddShortcutWindow.xml");
|
||||
throw new NullReferenceException("Failed to load embedded resource AddShortcutWindow.xml");
|
||||
}
|
||||
|
||||
closeButton = builder.GetObject("close_button") as Gtk.Button;
|
||||
if (closeButton == null)
|
||||
try
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: close_button");
|
||||
}
|
||||
closeButton.OnClicked += Close;
|
||||
|
||||
iconPickerButton = builder.GetObject("icon_picker_button") as Gtk.Button;
|
||||
if (iconPickerButton == null)
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: icon_picker_button");
|
||||
}
|
||||
iconPickerButton.OnClicked += OpenIconPicker;
|
||||
closeButton = builder.GetObject(closeButtonId) as Gtk.Button;
|
||||
if (closeButton == null)
|
||||
{
|
||||
throw new NullReferenceException(closeButtonId);
|
||||
}
|
||||
closeButton.OnClicked += Close;
|
||||
|
||||
clearSelectedFolderButton = builder.GetObject("clear_selected_folder_button") as Gtk.Button;
|
||||
if (clearSelectedFolderButton == null)
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: icon_picker_button");
|
||||
}
|
||||
clearSelectedFolderButton.OnClicked += ClearSelectedFolder;
|
||||
iconPickerButton = builder.GetObject(iconPickerButtonId) as Gtk.Button;
|
||||
if (iconPickerButton == null)
|
||||
{
|
||||
throw new NullReferenceException(iconPickerButtonId);
|
||||
}
|
||||
iconPickerButton.OnClicked += OpenIconPicker;
|
||||
|
||||
browseButton = builder.GetObject("folder_browse_button") as Gtk.Button;
|
||||
if (browseButton == null)
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: folder_browse_button");
|
||||
}
|
||||
browseButton.OnClicked += BrowseFolder;
|
||||
displayNameEntryRow = builder.GetObject(displayNameEntryRowId) as EntryRow;
|
||||
if (displayNameEntryRow == null)
|
||||
{
|
||||
throw new NullReferenceException(displayNameEntryRowId);
|
||||
}
|
||||
|
||||
folderActionRow = builder.GetObject(folderActionRowId) as ActionRow;
|
||||
if (folderActionRow == null)
|
||||
{
|
||||
throw new NullReferenceException(folderActionRowId);
|
||||
}
|
||||
|
||||
saveButton = builder.GetObject("save_button") as Gtk.Button;
|
||||
if (saveButton == null)
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: save_button");
|
||||
clearSelectedFolderButton = builder.GetObject(clearSelectedFolderButtonId) as Gtk.Button;
|
||||
if (clearSelectedFolderButton == null)
|
||||
{
|
||||
throw new NullReferenceException(clearSelectedFolderButtonId);
|
||||
}
|
||||
clearSelectedFolderButton.OnClicked += ClearSelectedFolder;
|
||||
|
||||
browseButton = builder.GetObject(browseButtonId) as Gtk.Button;
|
||||
if (browseButton == null)
|
||||
{
|
||||
throw new NullReferenceException(browseButtonId);
|
||||
}
|
||||
browseButton.OnClicked += BrowseFolder;
|
||||
|
||||
saveButton = builder.GetObject(saveButtonId) as Gtk.Button;
|
||||
if (saveButton == null)
|
||||
{
|
||||
throw new NullReferenceException(saveButtonId);
|
||||
}
|
||||
saveButton.OnClicked += (sender, e) => CreateShortcut();
|
||||
}
|
||||
catch (NullReferenceException e)
|
||||
{
|
||||
throw new Exception($"Failed to load UI element: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateShortcut()
|
||||
{
|
||||
if (displayNameEntryRow == null || browseButton == null || iconPickerButton == null)
|
||||
return;
|
||||
|
||||
var displayName = displayNameEntryRow.GetText();
|
||||
var path = ((ButtonContent)browseButton.Child).Label;
|
||||
var iconName = iconPickerButton.Label;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(displayName) || path.Equals("Browse") || string.IsNullOrWhiteSpace(path) || string.IsNullOrWhiteSpace(iconName))
|
||||
return;
|
||||
|
||||
shortcuts.Add(path, displayName, iconName);
|
||||
Dialog.Close();
|
||||
}
|
||||
|
||||
private void OpenIconPicker(object sender, EventArgs e)
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwEntryRow">
|
||||
<object class="AdwEntryRow" id="display_name_entry_row">
|
||||
<property name="title" translatable="yes" context="Input field placeholder" comments="Noun. Tells the user that the display name of the new password store is to be supplied here">Name</property>
|
||||
</object>
|
||||
</child>
|
||||
@@ -51,7 +51,7 @@
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<object class="AdwActionRow" id="folder_action_row">
|
||||
<property name="title" translatable="yes" context="Label" comments="Noun. Marks a button that allows the user to pick a folder where the new store will be.">Location</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Adw;
|
||||
using Keychain.ViewModels;
|
||||
using Logic;
|
||||
|
||||
namespace Keychain.UI;
|
||||
|
||||
@@ -12,6 +13,8 @@ public class MainWindow
|
||||
private Gtk.Stack titleStack;
|
||||
private Gtk.SearchEntry searchEntry;
|
||||
|
||||
private readonly IPasswordStoreService passwordStoreService;
|
||||
|
||||
private readonly string windowId = "main_window";
|
||||
private readonly string shortcutsGroupId = "shortcuts_group";
|
||||
private readonly string addShortcutButtonId = "add_shortcut_button";
|
||||
@@ -19,8 +22,9 @@ public class MainWindow
|
||||
private readonly string titleStackId = "title_stack";
|
||||
private readonly string searchEntryId = "search_entry";
|
||||
|
||||
public MainWindow()
|
||||
public MainWindow(IPasswordStoreService passwordStoreService)
|
||||
{
|
||||
this.passwordStoreService = passwordStoreService;
|
||||
var builder = new Gtk.Builder("Keychain.UI.MainWindow.MainWindow.xml");
|
||||
|
||||
|
||||
@@ -74,38 +78,27 @@ public class MainWindow
|
||||
throw new Exception("Failed to load UI element with ID: " + e.Message);
|
||||
}
|
||||
|
||||
// Initialize the observable collection with property binding
|
||||
shortcuts = new PasswordStoreShortcutCollection(shortcutsGroup);
|
||||
// Initialize the observable collection with property binding
|
||||
shortcuts = new PasswordStoreShortcutCollection(shortcutsGroup, passwordStoreService);
|
||||
|
||||
LoadDefaultShortcuts();
|
||||
}
|
||||
|
||||
private void OnAddShortcutClicked(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new AddShortcutWindow().Dialog;
|
||||
var dialog = new AddShortcutWindow(shortcuts).Dialog;
|
||||
dialog.Present(Window);
|
||||
}
|
||||
|
||||
private void AddShortcut(string path)
|
||||
{
|
||||
var newShortcut = new PasswordStoreShortcut(path: path);
|
||||
shortcuts.Add(newShortcut); // This will automatically update the UI
|
||||
}
|
||||
|
||||
private void RemoveShortcut(PasswordStoreViewModel shortcut)
|
||||
{
|
||||
shortcuts.Remove(shortcut); // This will automatically update the UI
|
||||
}
|
||||
|
||||
private void LoadDefaultShortcuts()
|
||||
{
|
||||
shortcuts.Add(new PasswordStoreShortcut(displayName: "Default", path: Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/.password_store"));
|
||||
shortcuts.Add(new PasswordStoreViewModel(displayName: "Default", path: Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + "/.password_store"));
|
||||
}
|
||||
|
||||
private void UpdateShortcutName(PasswordStoreViewModel shortcut, string newName)
|
||||
{
|
||||
shortcut.DisplayName = newName; // This will automatically update the UI row
|
||||
}
|
||||
// private void UpdateShortcutName(PasswordStoreViewModel shortcut, string newName)
|
||||
// {
|
||||
// shortcut.DisplayName = newName; // This will automatically update the UI row
|
||||
// }
|
||||
|
||||
private void SetSearchBarVisible(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -2,68 +2,74 @@ namespace Keychain.ViewModels;
|
||||
|
||||
using Adw;
|
||||
using Gtk;
|
||||
using Logic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
|
||||
public class PasswordStoreShortcutCollection : ObservableCollection<PasswordStoreViewModel>
|
||||
{
|
||||
private readonly PreferencesGroup shortcutsGroup;
|
||||
private readonly Dictionary<PasswordStoreViewModel, ActionRow> itemToRowMap = new();
|
||||
|
||||
public PasswordStoreShortcutCollection(PreferencesGroup shortcutsGroup)
|
||||
{
|
||||
this.shortcutsGroup = shortcutsGroup;
|
||||
CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (PasswordStoreViewModel item in e.NewItems)
|
||||
{
|
||||
var row = CreateShortcutRow(item);
|
||||
itemToRowMap[item] = row;
|
||||
shortcutsGroup.Add(row);
|
||||
|
||||
public class PasswordStoreShortcutCollection : ObservableCollection<PasswordStoreViewModel>
|
||||
{
|
||||
private IPasswordStoreService _passwordStoreService;
|
||||
|
||||
private readonly PreferencesGroup shortcutsGroup;
|
||||
private readonly Dictionary<PasswordStoreViewModel, ActionRow> itemToRowMap = new();
|
||||
|
||||
public PasswordStoreShortcutCollection(PreferencesGroup shortcutsGroup, IPasswordStoreService passwordStoreService)
|
||||
: base()
|
||||
{
|
||||
this.shortcutsGroup = shortcutsGroup;
|
||||
_passwordStoreService = passwordStoreService;
|
||||
CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (PasswordStoreViewModel item in e.NewItems)
|
||||
{
|
||||
var row = CreateShortcutRow(item);
|
||||
itemToRowMap[item] = row;
|
||||
shortcutsGroup.Add(row);
|
||||
|
||||
// Subscribe to property changes for reactive updates
|
||||
item.PropertyChanged += (sender, args) => UpdateRowFromItem(item, ref row);
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (PasswordStoreViewModel item in e.OldItems)
|
||||
{
|
||||
if (itemToRowMap.TryGetValue(item, out var row))
|
||||
{
|
||||
shortcutsGroup.Remove(row);
|
||||
itemToRowMap.Remove(item);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
foreach (var row in itemToRowMap.Values)
|
||||
{
|
||||
shortcutsGroup.Remove(row);
|
||||
}
|
||||
itemToRowMap.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private ActionRow CreateShortcutRow(PasswordStoreViewModel shortcut)
|
||||
{
|
||||
var row = new ActionRow();
|
||||
UpdateRowFromItem(shortcut, ref row);
|
||||
|
||||
item.PropertyChanged += (sender, args) => UpdateRowFromItem(item, ref row);
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (PasswordStoreViewModel item in e.OldItems)
|
||||
{
|
||||
if (itemToRowMap.TryGetValue(item, out var row))
|
||||
{
|
||||
shortcutsGroup.Remove(row);
|
||||
itemToRowMap.Remove(item);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
foreach (var row in itemToRowMap.Values)
|
||||
{
|
||||
shortcutsGroup.Remove(row);
|
||||
}
|
||||
itemToRowMap.Clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private ActionRow CreateShortcutRow(PasswordStoreViewModel shortcut)
|
||||
{
|
||||
var row = new ActionRow();
|
||||
UpdateRowFromItem(shortcut, ref row);
|
||||
|
||||
row.SetActivatable(true);
|
||||
row.OnActivated += (sender, args) => {
|
||||
Console.WriteLine($"[DEBUG] Opening: {shortcut.Path}");
|
||||
};
|
||||
|
||||
return row;
|
||||
row.OnActivated += (sender, args) =>
|
||||
{
|
||||
Console.WriteLine($"[DEBUG] Opening: {shortcut.Path}");
|
||||
};
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private void UpdateRowFromItem(PasswordStoreViewModel shortcut, ref ActionRow row)
|
||||
@@ -91,4 +97,11 @@ public class PasswordStoreShortcutCollection : ObservableCollection<PasswordStor
|
||||
edit.IconName = "document-edit-symbolic";
|
||||
row.AddSuffix(edit);
|
||||
}
|
||||
|
||||
public void Add(string path, string? displayName = null, string? iconName = null)
|
||||
{
|
||||
PasswordStoreViewModel item = new PasswordStoreViewModel(path, displayName, iconName);
|
||||
Add(item);
|
||||
_passwordStoreService.Create(item.Model);
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,20 @@ public class PasswordStoreViewModel : INotifyPropertyChanged
|
||||
get => _model.Path;
|
||||
}
|
||||
|
||||
public PasswordStore Model { get => _model; }
|
||||
|
||||
public PasswordStoreViewModel(PasswordStore item)
|
||||
{
|
||||
_model = item;
|
||||
}
|
||||
|
||||
public PasswordStoreViewModel(string path, string? displayName = "New Shortcut", string? iconName = "text-x-generic-symbolic")
|
||||
{
|
||||
_model = new PasswordStore
|
||||
{
|
||||
DisplayName = displayName,
|
||||
Path = path,
|
||||
IconName = iconName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,11 @@ public class PasswordStoreService : IPasswordStoreService
|
||||
{
|
||||
private readonly IRepository repository;
|
||||
|
||||
public PasswordStoreService(IRepository repository)
|
||||
{
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void Create(PasswordStore item)
|
||||
{
|
||||
repository.Create(item);
|
||||
|
||||
Reference in New Issue
Block a user