diff --git a/src/App/Program.cs b/src/App/Program.cs index 4d00e40..620929a 100644 --- a/src/App/Program.cs +++ b/src/App/Program.cs @@ -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(); + var window = new UI.MainWindow(passwordStoreService).Window; window.Application = (Adw.Application)sender; window.Show(); }; diff --git a/src/App/UI/AddShortcutWindow/AddShortcutWindow.cs b/src/App/UI/AddShortcutWindow/AddShortcutWindow.cs index 89996fb..2e5ff74 100644 --- a/src/App/UI/AddShortcutWindow/AddShortcutWindow.cs +++ b/src/App/UI/AddShortcutWindow/AddShortcutWindow.cs @@ -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) diff --git a/src/App/UI/AddShortcutWindow/AddShortcutWindow.xml b/src/App/UI/AddShortcutWindow/AddShortcutWindow.xml index 31f704a..db67b84 100644 --- a/src/App/UI/AddShortcutWindow/AddShortcutWindow.xml +++ b/src/App/UI/AddShortcutWindow/AddShortcutWindow.xml @@ -34,7 +34,7 @@ - + Name @@ -51,7 +51,7 @@ - + Location diff --git a/src/App/UI/MainWindow/MainWindow.cs b/src/App/UI/MainWindow/MainWindow.cs index 664728b..491dc4b 100644 --- a/src/App/UI/MainWindow/MainWindow.cs +++ b/src/App/UI/MainWindow/MainWindow.cs @@ -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) { diff --git a/src/App/ViewModels/PasswordStoreShortcutCollection.cs b/src/App/ViewModels/PasswordStoreShortcutCollection.cs index d81d041..01145f5 100644 --- a/src/App/ViewModels/PasswordStoreShortcutCollection.cs +++ b/src/App/ViewModels/PasswordStoreShortcutCollection.cs @@ -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 -{ - private readonly PreferencesGroup shortcutsGroup; - private readonly Dictionary 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 +{ + private IPasswordStoreService _passwordStoreService; + + private readonly PreferencesGroup shortcutsGroup; + private readonly Dictionary 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 _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 + }; + } } diff --git a/src/Logic/PasswordStoreService.cs b/src/Logic/PasswordStoreService.cs index 9796f82..34e6df4 100644 --- a/src/Logic/PasswordStoreService.cs +++ b/src/Logic/PasswordStoreService.cs @@ -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);