Sidebar shortcuts bound

This commit is contained in:
2025-09-17 12:49:05 +02:00
parent 63ca15ff54
commit b0379fba05
4 changed files with 287 additions and 83 deletions

View File

@@ -1,21 +1,69 @@
using Adw;
using App.UI.Models;
namespace App.UI;
public class MainWindow
{
public Adw.Window Window { get; }
public Window Window { get; }
private PreferencesGroup shortcutsGroup;
private PasswordStoreShortcutCollection shortcuts;
public MainWindow()
{
var assembly = typeof(MainWindow).Assembly;
using var stream = assembly.GetManifestResourceStream("App.UI.MainWindow.xml");
if (stream == null)
throw new Exception("Failed to load embedded resource MainWindow.xml");
using var reader = new System.IO.StreamReader(stream);
var xml = reader.ReadToEnd();
var builder = Gtk.Builder.NewFromString(xml, -1);
var window = builder.GetObject("main_window") as Adw.Window;
var builder = new Gtk.Builder("App.UI.MainWindow.xml");
var window = builder.GetObject("main_window") as Window;
if (window == null)
throw new Exception("Failed to load main_window from MainWindow.ui");
{
throw new Exception("Failed to load embedded resource MainWindow.xml");
}
Window = window;
var group = builder.GetObject("shortcuts_group") as PreferencesGroup;
if (group == null)
{
throw new Exception("Failed to load UI element with ID: shortcuts_group");
}
shortcutsGroup = group;
var addButton = builder.GetObject("add_shortcut_button") as ActionRow;
if (addButton == null)
{
throw new Exception("Failed to load UI element with ID: add_shortcut_button");
}
addButton.OnActivated += OnAddShortcutClicked;
// Initialize the observable collection with property binding
shortcuts = new PasswordStoreShortcutCollection(shortcutsGroup);
LoadDefaultShortcuts();
}
private void OnAddShortcutClicked(object sender, EventArgs e)
{
AddShortcut("/path/to/location");
}
public void AddShortcut(string path)
{
var newShortcut = new PasswordStoreShortcut(path:path);
shortcuts.Add(newShortcut); // This will automatically update the UI
}
public void RemoveShortcut(PasswordStoreShortcut 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"));
}
// Example of reactive property updates
public void UpdateShortcutName(PasswordStoreShortcut shortcut, string newName)
{
shortcut.DisplayName = newName; // This will automatically update the UI row
}
}

View File

@@ -14,23 +14,43 @@
</child>
<property name="content">
<object class="AdwOverlaySplitView" id="split_view">
<property name="min-sidebar-width">200</property>
<property name="max-sidebar-width">200</property>
<property name="min-sidebar-width">250</property>
<property name="max-sidebar-width">250</property>
<property name="show-sidebar"
bind-source="show_sidebar_button"
bind-property="active"
bind-flags="sync-create|bidirectional"/>
<property name="sidebar">
<object class="AdwNavigationPage">
<property name="title" translatable="yes">Sidebar</property>
<property name="title" translatable="yes" context="label" comments="Noun. Marks a list of password collections.">Password stores</property>
<property name="child">
<object class="AdwToolbarView">
<child type="top">
<object class="AdwHeaderBar" />
</child>
<property name="content">
<object class="GtkLabel">
<property name="label">I am a sidebar</property>
<object class="AdwPreferencesPage">
<child>
<object class="AdwPreferencesGroup" id="shortcuts_group">
<!-- Dynamic rows will be added here via model binding -->
</object>
</child>
<child>
<object class="AdwPreferencesGroup">
<child>
<object class="AdwActionRow" id="add_shortcut_button">
<property name="title" translatable="yes" context="button" comments="Verb. This button allows the user to add additional password collections.">Add</property>
<property name="activatable">True</property>
<child type="suffix">
<object class="GtkImage">
<property name="icon-name">list-add-symbolic</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
</property>
</object>

View File

@@ -0,0 +1,50 @@
using System.ComponentModel;
namespace App.UI.Models;
public class PasswordStoreShortcut : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string displayName;
private bool displayNameSet = false;
private string? iconName;
private string path;
public bool DisplayNameSet { get => displayNameSet; }
public string DisplayName
{
get => displayName;
set
{
displayName = value;
displayNameSet = true;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DisplayName)));
}
}
public string? IconName
{
get => iconName;
set { iconName = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IconName))); }
}
public string Path
{
get => path;
}
public PasswordStoreShortcut(string path, string iconName = "text-x-generic-symbolic", string? displayName = null)
{
this.path = path;
this.iconName = iconName;
this.displayName = path;
if (displayName != null)
{
DisplayName = displayName;
}
}
}

View File

@@ -0,0 +1,86 @@
namespace App.UI.Models;
using Adw;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
public class PasswordStoreShortcutCollection : ObservableCollection<PasswordStoreShortcut>
{
private readonly PreferencesGroup shortcutsGroup;
private readonly Dictionary<PasswordStoreShortcut, 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 (PasswordStoreShortcut 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 (PasswordStoreShortcut 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(PasswordStoreShortcut shortcut)
{
var row = new ActionRow();
UpdateRowFromItem(shortcut, ref row);
row.SetActivatable(true);
row.OnActivated += (sender, args) => {
Console.WriteLine($"[DEBUG] Opening: {shortcut.Path}");
};
return row;
}
private void UpdateRowFromItem(PasswordStoreShortcut shortcut, ref ActionRow row)
{
row.SetTitle(shortcut.DisplayName);
row.SetSubtitle(shortcut.Path);
//Update icon
var existingIcon = row.GetFirstChild() as Gtk.Image;
if (existingIcon == null)
{
var icon = new Gtk.Image();
icon.SetFromIconName(shortcut.IconName);
row.AddPrefix(icon);
}
else
{
existingIcon.SetFromIconName(shortcut.IconName);
}
}
}