Moved source to subfolder
This commit is contained in:
25
src/App/Keychain.csproj
Normal file
25
src/App/Keychain.csproj
Normal file
@@ -0,0 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="GirCore.Adw-1" Version="0.6.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="UI/MainWindow/MainWindow.xml" />
|
||||
<EmbeddedResource Include="UI/AddShortcutWindow/AddShortcutWindow.xml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Logic/Logic.csproj" />
|
||||
<ProjectReference Include="../Repository/Repository.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
33
src/App/Program.cs
Normal file
33
src/App/Program.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Logic;
|
||||
using Repository;
|
||||
|
||||
namespace Keychain;
|
||||
|
||||
class Program
|
||||
{
|
||||
private static ServiceProvider? provider;
|
||||
|
||||
static int Main()
|
||||
{
|
||||
provider = SetupServices();
|
||||
|
||||
var application = Adw.Application.New("org.typomustakes.keychain", Gio.ApplicationFlags.FlagsNone);
|
||||
application.OnActivate += (sender, args) =>
|
||||
{
|
||||
var window = new UI.MainWindow().Window;
|
||||
window.Application = (Adw.Application)sender;
|
||||
window.Show();
|
||||
};
|
||||
|
||||
return application.RunWithSynchronizationContext(null);
|
||||
}
|
||||
|
||||
private static ServiceProvider SetupServices()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<IPasswordStoreService, PasswordStoreService>();
|
||||
services.AddSingleton<IRepository, JsonRepository>();
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
}
|
||||
93
src/App/UI/AddShortcutWindow/AddShortcutWindow.cs
Normal file
93
src/App/UI/AddShortcutWindow/AddShortcutWindow.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Adw;
|
||||
|
||||
namespace Keychain.UI;
|
||||
|
||||
public class AddShortcutWindow
|
||||
{
|
||||
public Dialog Dialog { get; }
|
||||
private Gtk.Button? closeButton;
|
||||
private Gtk.Button? iconPickerButton;
|
||||
private Gtk.Button? browseButton;
|
||||
private Gtk.Button? clearSelectedFolderButton;
|
||||
private Gtk.Button? saveButton;
|
||||
|
||||
public AddShortcutWindow()
|
||||
{
|
||||
var builder = new Gtk.Builder("Keychain.UI.AddShortcutWindow.AddShortcutWindow.xml");
|
||||
|
||||
Dialog = builder.GetObject("add_shortcut_dialog") as Dialog;
|
||||
if (Dialog == null)
|
||||
{
|
||||
throw new Exception("Failed to load embedded resource AddShortcutWindow.xml");
|
||||
}
|
||||
|
||||
closeButton = builder.GetObject("close_button") as Gtk.Button;
|
||||
if (closeButton == null)
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
saveButton = builder.GetObject("save_button") as Gtk.Button;
|
||||
if (saveButton == null)
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: save_button");
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenIconPicker(object sender, EventArgs e)
|
||||
{
|
||||
var chooser = new Gtk.EmojiChooser();
|
||||
chooser.SetParent(iconPickerButton);
|
||||
chooser.OnEmojiPicked += (s, e) =>
|
||||
{
|
||||
iconPickerButton.Label = e.Text;
|
||||
};
|
||||
chooser.Show();
|
||||
}
|
||||
|
||||
private void Close(object sender, EventArgs e)
|
||||
{
|
||||
Dialog.Close();
|
||||
}
|
||||
|
||||
private void ClearSelectedFolder(object sender, EventArgs e)
|
||||
{
|
||||
var buttonContent = (ButtonContent)browseButton.Child;
|
||||
buttonContent.Label = "Browse";
|
||||
clearSelectedFolderButton.Sensitive = false;
|
||||
}
|
||||
|
||||
private async void BrowseFolder(object sender, EventArgs e)
|
||||
{
|
||||
var fileDialog = new Gtk.FileDialog();
|
||||
var selectedFolder = await fileDialog.SelectFolderAsync((Window)Dialog.Parent.Parent);
|
||||
if (selectedFolder != null)
|
||||
{
|
||||
var buttonContent = (ButtonContent)browseButton.Child;
|
||||
buttonContent.Label = selectedFolder.GetPath();
|
||||
clearSelectedFolderButton.Sensitive = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/App/UI/AddShortcutWindow/AddShortcutWindow.xml
Normal file
97
src/App/UI/AddShortcutWindow/AddShortcutWindow.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="AdwDialog" id="add_shortcut_dialog">
|
||||
<property name="content-width">400</property>
|
||||
<property name="child">
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="title-widget">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title" translatable="yes" context="label" comments="Verb. Marks a preferences page where the user can add new password stores. Store as in storage">Add New Store</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="show-end-title-buttons">False</property>
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="close_button">
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes" context="label" comments="Verb">Cancel</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkButton" id="save_button">
|
||||
<style>
|
||||
<class name="suggested-action" />
|
||||
</style>
|
||||
<property name="valign">center</property>
|
||||
<property name="label" translatable="yes" context="label" comments="Verb">Save</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwPreferencesPage">
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<child>
|
||||
<object class="AdwEntryRow">
|
||||
<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>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title">Icon</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="icon_picker_button">
|
||||
<property name="valign">center</property>
|
||||
<property name="halign">center</property>
|
||||
<property name="icon-name">emoji-symbols-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<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">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="folder_browse_button">
|
||||
<property name="valign">center</property>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="can-shrink">True</property>
|
||||
<property name="icon-name">document-open-symbolic</property>
|
||||
<property name="label">Browse</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="clear_selected_folder_button">
|
||||
<property name="valign">center</property>
|
||||
<property name="sensitive">0</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">user-trash-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
||||
122
src/App/UI/MainWindow/MainWindow.cs
Normal file
122
src/App/UI/MainWindow/MainWindow.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Adw;
|
||||
using Keychain.ViewModels;
|
||||
|
||||
namespace Keychain.UI;
|
||||
|
||||
public class MainWindow
|
||||
{
|
||||
public Window Window { get; }
|
||||
private PreferencesGroup shortcutsGroup;
|
||||
private PasswordStoreShortcutCollection shortcuts;
|
||||
private Gtk.ToggleButton searchToggleButton;
|
||||
private Gtk.Stack titleStack;
|
||||
private Gtk.SearchEntry searchEntry;
|
||||
|
||||
private readonly string windowId = "main_window";
|
||||
private readonly string shortcutsGroupId = "shortcuts_group";
|
||||
private readonly string addShortcutButtonId = "add_shortcut_button";
|
||||
private readonly string searchToggleButtonId = "search_button";
|
||||
private readonly string titleStackId = "title_stack";
|
||||
private readonly string searchEntryId = "search_entry";
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
var builder = new Gtk.Builder("Keychain.UI.MainWindow.MainWindow.xml");
|
||||
|
||||
|
||||
Window = builder.GetObject(windowId) as Window;
|
||||
if (Window == null)
|
||||
{
|
||||
throw new Exception("Failed to load embedded resource MainWindow.xml");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
shortcutsGroup = builder.GetObject(shortcutsGroupId) as PreferencesGroup;
|
||||
if (shortcutsGroup == null)
|
||||
throw new Exception(shortcutsGroupId);
|
||||
|
||||
var addButton = builder.GetObject(addShortcutButtonId) as Gtk.Button;
|
||||
if (addButton == null)
|
||||
{
|
||||
throw new Exception(addShortcutButtonId);
|
||||
}
|
||||
addButton.OnClicked += OnAddShortcutClicked;
|
||||
|
||||
searchToggleButton = builder.GetObject(searchToggleButtonId) as Gtk.ToggleButton;
|
||||
if (searchToggleButton == null)
|
||||
{
|
||||
throw new Exception(searchToggleButtonId);
|
||||
}
|
||||
searchToggleButton.OnToggled += SetSearchBarVisible;
|
||||
|
||||
|
||||
titleStack = builder.GetObject(titleStackId) as Gtk.Stack;
|
||||
if (titleStack == null)
|
||||
{
|
||||
throw new Exception(titleStackId);
|
||||
}
|
||||
|
||||
searchEntry = builder.GetObject(searchEntryId) as Gtk.SearchEntry;
|
||||
if (searchEntry == null)
|
||||
{
|
||||
throw new Exception(searchEntryId);
|
||||
}
|
||||
var focusController = new Gtk.EventControllerFocus();
|
||||
focusController.OnLeave += (s, e) =>
|
||||
{
|
||||
searchToggleButton.Active = false;
|
||||
};
|
||||
searchEntry.AddController(focusController);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("Failed to load UI element with ID: " + e.Message);
|
||||
}
|
||||
|
||||
// Initialize the observable collection with property binding
|
||||
shortcuts = new PasswordStoreShortcutCollection(shortcutsGroup);
|
||||
|
||||
LoadDefaultShortcuts();
|
||||
}
|
||||
|
||||
private void OnAddShortcutClicked(object sender, EventArgs e)
|
||||
{
|
||||
var dialog = new AddShortcutWindow().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"));
|
||||
}
|
||||
|
||||
private void UpdateShortcutName(PasswordStoreViewModel shortcut, string newName)
|
||||
{
|
||||
shortcut.DisplayName = newName; // This will automatically update the UI row
|
||||
}
|
||||
|
||||
private void SetSearchBarVisible(object sender, EventArgs e)
|
||||
{
|
||||
if (searchToggleButton.Active)
|
||||
{
|
||||
titleStack.SetVisibleChildName("Search");
|
||||
searchEntry.GrabFocus();
|
||||
}
|
||||
else
|
||||
{
|
||||
titleStack.SetVisibleChildName("Passwords");
|
||||
}
|
||||
}
|
||||
}
|
||||
160
src/App/UI/MainWindow/MainWindow.xml
Normal file
160
src/App/UI/MainWindow/MainWindow.xml
Normal file
@@ -0,0 +1,160 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="AdwWindow" id="main_window">
|
||||
<property name="width-request">350</property>
|
||||
<property name="height-request">300</property>
|
||||
<property name="default-width">800</property>
|
||||
<property name="default-height">500</property>
|
||||
<child>
|
||||
<object class="AdwBreakpoint">
|
||||
<condition>max-width: 500sp</condition>
|
||||
<setter object="split_view" property="collapsed">True</setter>
|
||||
<setter object="show_sidebar_button" property="visible">True</setter>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwOverlaySplitView" id="split_view">
|
||||
<property name="min-sidebar-width">300</property>
|
||||
<property name="max-sidebar-width">300</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" context="label" comments="Noun. Marks a list of password collections.">Stores</property>
|
||||
<property name="child">
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="add_shortcut_button">
|
||||
<property name="valign">center</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwPreferencesPage">
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup" id="shortcuts_group">
|
||||
<!-- Dynamic rows will be added here via model binding -->
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="content">
|
||||
<object class="AdwNavigationPage">
|
||||
<property name="title" translatable="yes" context="label" comments="Noun, plural. Indicates the location of the actual decryptable passwords">Passwords</property>
|
||||
<property name="child">
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<child type="start">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">horizontal</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="search_button">
|
||||
<property name="icon-name">system-search-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="show_sidebar_button">
|
||||
<property name="icon-name">sidebar-show-symbolic</property>
|
||||
<property name="active">True</property>
|
||||
<property name="visible">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<property name="title-widget">
|
||||
<object class="GtkStack" id="title_stack">
|
||||
<property name="transition-type">slide-up-down</property>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">Passwords</property>
|
||||
<property name="child">
|
||||
<object class="AdwWindowTitle">
|
||||
<property name="title">Passwords</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStackPage">
|
||||
<property name="name">Search</property>
|
||||
<property name="child">
|
||||
<object class="AdwClamp">
|
||||
<property name="tightening-threshold">300</property>
|
||||
<property name="maximum-size">400</property>
|
||||
<property name="child">
|
||||
<object class="GtkSearchEntry" id="search_entry">
|
||||
<property name="hexpand">True</property>
|
||||
<property name="placeholder-text" translatable="yes">Search passwords</property>
|
||||
<!-- <signal name="search-started" handler="search_started_cb" swapped="yes"/> -->
|
||||
<!-- <signal name="search-changed" handler="search_changed_cb" swapped="yes"/> -->
|
||||
<!-- <signal name="stop-search" handler="stop_search_cb" swapped="yes"/> -->
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwPreferencesPage">
|
||||
<child>
|
||||
<object class="AdwPreferencesGroup">
|
||||
<property name="title">Default</property>
|
||||
<property name="description">/home/typo/.password-store</property>
|
||||
<property name="header-suffix">
|
||||
<object class="GtkButton">
|
||||
<property name="valign">center</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
</style>
|
||||
<child>
|
||||
<object class="AdwButtonContent">
|
||||
<property name="icon-name">list-add-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title">Sample password</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwActionRow">
|
||||
<property name="title">Sample password 2</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
||||
94
src/App/ViewModels/PasswordStoreShortcutCollection.cs
Normal file
94
src/App/ViewModels/PasswordStoreShortcutCollection.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
namespace Keychain.ViewModels;
|
||||
|
||||
using Adw;
|
||||
using Gtk;
|
||||
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);
|
||||
|
||||
// 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);
|
||||
|
||||
row.SetActivatable(true);
|
||||
row.OnActivated += (sender, args) => {
|
||||
Console.WriteLine($"[DEBUG] Opening: {shortcut.Path}");
|
||||
};
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private void UpdateRowFromItem(PasswordStoreViewModel 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);
|
||||
}
|
||||
|
||||
// Edit button
|
||||
var edit = new Button();
|
||||
edit.AddCssClass("flat");
|
||||
edit.SetValign(Align.Center);
|
||||
edit.IconName = "document-edit-symbolic";
|
||||
row.AddSuffix(edit);
|
||||
}
|
||||
}
|
||||
30
src/App/ViewModels/PasswordStoreViewModel.cs
Normal file
30
src/App/ViewModels/PasswordStoreViewModel.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.ComponentModel;
|
||||
using Models;
|
||||
|
||||
namespace Keychain.ViewModels;
|
||||
|
||||
public class PasswordStoreViewModel : INotifyPropertyChanged
|
||||
{
|
||||
private PasswordStore _model;
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
public string? DisplayName
|
||||
{
|
||||
get => _model.DisplayName;
|
||||
}
|
||||
|
||||
public string? IconName
|
||||
{
|
||||
get => _model.IconName;
|
||||
}
|
||||
|
||||
public string Path
|
||||
{
|
||||
get => _model.Path;
|
||||
}
|
||||
|
||||
public PasswordStoreViewModel(PasswordStore item)
|
||||
{
|
||||
_model = item;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user