commit 46e16bc871282c4ffa7c50a563acf660123e01ab
parent c31162f69a68d11b9f79f137733bcfab3eace569
Author: Carlosokumu <carlosokumu254@gmail.com>
Date: Sat, 7 Mar 2026 17:36:12 +0300
pass toast_overlay
Diffstat:
1 file changed, 140 insertions(+), 126 deletions(-)
diff --git a/dummy/usawa/gui/views/entry_list_view.py b/dummy/usawa/gui/views/entry_list_view.py
@@ -1,6 +1,6 @@
from datetime import datetime
import logging
-from gi.repository import Gtk, Gio,Pango
+from gi.repository import Gtk, Gio, Pango
from usawa.gui.models.entry_item import EntryItem
from usawa.gui.views.create_entry_view import create_entry_page
@@ -8,22 +8,31 @@ from usawa.gui.views.entry_details_view import create_entry_details_page
logg = logging.getLogger("gui.entrylist")
-class EntryListView(Gtk.Box):
+class EntryListView(Gtk.Box):
"""The entry list view with filters, table, and FAB"""
- def __init__(self, nav_view,entry_controller,entries = None,refresh_callback=None, **kwargs):
+
+ def __init__(
+ self,
+ nav_view,
+ entry_controller,
+ toast_overlay,
+ entries=None,
+ refresh_callback=None,
+ **kwargs,
+ ):
super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=0, **kwargs)
-
- self.nav_view = nav_view
+
+ self.nav_view = nav_view
self.entry_controller = entry_controller
self.entries = entries or []
self.refresh_callback = refresh_callback
- self.active_filter = None
+ self.toast_overlay = toast_overlay
+ self.active_filter = None
overlay = Gtk.Overlay()
self.append(overlay)
-
-
+
content = Gtk.Box(
orientation=Gtk.Orientation.VERTICAL,
spacing=12,
@@ -33,19 +42,19 @@ class EntryListView(Gtk.Box):
content.set_margin_start(12)
content.set_margin_end(12)
overlay.set_child(content)
-
+
# Sort Section
sort_section = self._create_sort_section()
content.append(sort_section)
-
+
# Filter Section
filter_section = self._create_filter_section()
content.append(filter_section)
-
+
# Table Section
table_section = self._create_table_section()
content.append(table_section)
-
+
# Floating Action Button
fab = Gtk.Button()
fab.set_icon_name("list-add-symbolic")
@@ -58,54 +67,53 @@ class EntryListView(Gtk.Box):
overlay.add_overlay(fab)
fab.connect("clicked", self.on_fab_clicked)
-
def _create_sort_section(self):
"""Create the Sort by section with toggle buttons"""
sort_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
-
+
sort_label = Gtk.Label(label="Sort by")
sort_label.set_halign(Gtk.Align.START)
sort_label.add_css_class("heading")
sort_box.append(sort_label)
-
+
button_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
button_container.set_margin_top(4)
button_container.set_margin_bottom(4)
button_container.set_margin_start(8)
button_container.set_margin_end(8)
button_container.add_css_class("card")
-
+
self.sort_serial_btn = Gtk.ToggleButton(label="Serial Number")
- self.sort_serial_btn.set_active(True)
+ self.sort_serial_btn.set_active(True)
self.sort_serial_btn.connect("toggled", self.on_sort_changed, "serial")
button_container.append(self.sort_serial_btn)
-
+
self.sort_datetime_btn = Gtk.ToggleButton(label="Transaction Datetime")
self.sort_datetime_btn.connect("toggled", self.on_sort_changed, "datetime")
button_container.append(self.sort_datetime_btn)
-
+
self.sort_serial_btn.set_group(self.sort_datetime_btn)
-
+
sort_box.append(button_container)
-
+
return sort_box
-
+
def _create_filter_section(self):
"""Create the Filter section with account type, keyword, and date range"""
filter_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
-
+
filter_label = Gtk.Label(label="Filter")
filter_label.set_halign(Gtk.Align.START)
filter_label.add_css_class("heading")
filter_box.append(filter_label)
-
+
filter_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=16)
filter_container.set_margin_top(8)
filter_container.set_margin_bottom(8)
filter_container.set_margin_start(8)
filter_container.set_margin_end(8)
filter_container.add_css_class("card")
-
+
account_type_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
account_type_box.set_hexpand(True)
account_type_box.set_margin_start(8)
@@ -115,85 +123,84 @@ class EntryListView(Gtk.Box):
account_type_label.set_margin_top(4)
account_type_box.set_margin_bottom(4)
account_type_box.append(account_type_label)
-
+
account_types = Gtk.StringList()
account_types.append("All types")
account_types.append("Asset")
account_types.append("Liability")
account_types.append("Income")
account_types.append("Expense")
-
+
self.account_type_dropdown = Gtk.DropDown(model=account_types)
- self.account_type_dropdown.set_selected(0)
+ self.account_type_dropdown.set_selected(0)
self.account_type_dropdown.connect("notify::selected", self.on_filter_changed)
account_type_box.append(self.account_type_dropdown)
-
+
filter_container.append(account_type_box)
-
+
keyword_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
keyword_box.set_hexpand(True)
-
+
keyword_label = Gtk.Label(label="Keyword or Account path")
keyword_label.set_halign(Gtk.Align.START)
keyword_label.add_css_class("caption")
keyword_label.set_margin_top(4)
keyword_box.set_margin_bottom(4)
keyword_box.append(keyword_label)
-
+
self.keyword_entry = Gtk.Entry()
self.keyword_entry.set_placeholder_text("Search in description or path...")
self.keyword_entry.set_text("rent/apartment")
self.keyword_entry.connect("changed", self.on_filter_changed)
keyword_label.set_margin_top(4)
keyword_box.append(self.keyword_entry)
-
+
filter_container.append(keyword_box)
-
+
date_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
date_box.set_hexpand(True)
-
+
date_label = Gtk.Label(label="Date range")
date_label.set_halign(Gtk.Align.START)
date_label.add_css_class("caption")
date_label.set_margin_top(4)
date_box.set_margin_bottom(4)
date_box.append(date_label)
-
date_range_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
-
+
self.date_start_entry = Gtk.Entry()
self.date_start_entry.set_placeholder_text("MM-DD")
self.date_start_entry.set_text("02-10")
self.date_start_entry.set_max_width_chars(10)
self.date_start_entry.connect("changed", self.on_filter_changed)
date_range_box.append(self.date_start_entry)
-
+
to_label = Gtk.Label(label="to")
to_label.add_css_class("dim-label")
date_range_box.append(to_label)
-
+
self.date_end_entry = Gtk.Entry()
self.date_end_entry.set_placeholder_text("MM-DD")
self.date_end_entry.set_text("02-11")
self.date_end_entry.set_max_width_chars(10)
self.date_end_entry.connect("changed", self.on_filter_changed)
date_range_box.append(self.date_end_entry)
-
+
calendar_btn = Gtk.Button()
calendar_btn.set_icon_name("x-office-calendar-symbolic")
calendar_btn.add_css_class("flat")
calendar_btn.connect("clicked", self.on_calendar_clicked)
date_range_box.append(calendar_btn)
-
+
date_box.append(date_range_box)
-
+
filter_container.append(date_box)
-
+
filter_box.append(filter_container)
-
+
return filter_box
-
+
def on_filter_changed(self, widget, *args):
"""Handle filter changes - track which filter was last updated"""
if widget == self.account_type_dropdown:
@@ -226,7 +233,9 @@ class EntryListView(Gtk.Box):
main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=16)
content.append(main_box)
- instruction = Gtk.Label(label="Click once for start date, click again for end date")
+ instruction = Gtk.Label(
+ label="Click once for start date, click again for end date"
+ )
instruction.add_css_class("dim-label")
instruction.set_wrap(True)
instruction.set_halign(Gtk.Align.START)
@@ -257,7 +266,9 @@ class EntryListView(Gtk.Box):
def _on_calendar_day_selected(self, cal):
date = cal.get_date()
- date_str = "{}-{:02d}-{:02d}".format(date.get_year(), date.get_month(), date.get_day_of_month())
+ date_str = "{}-{:02d}-{:02d}".format(
+ date.get_year(), date.get_month(), date.get_day_of_month()
+ )
if self._start_date is None or self._end_date is not None:
self._start_date = date_str
self._end_date = None
@@ -271,18 +282,20 @@ class EntryListView(Gtk.Box):
def _on_calendar_response(self, dialog, response):
if response == Gtk.ResponseType.OK and self._start_date and self._end_date:
- logg.info("Date range selected: {} to {}".format(self._start_date, self._end_date))
+ logg.info(
+ "Date range selected: {} to {}".format(self._start_date, self._end_date)
+ )
start = datetime.strptime(self._start_date, "%Y-%m-%d").date()
end = datetime.strptime(self._end_date, "%Y-%m-%d").date()
filtered = [
- e for e in self.entries
+ e
+ for e in self.entries
if e.tx_date and start <= e.tx_date.date() <= end
]
self._reload_store(filtered)
self._calendar.unmark_day(self._calendar.get_date().get_day_of_month())
dialog.destroy()
-
def on_sort_changed(self, button, sort_type):
"""Handle sort option changes"""
if button.get_active():
@@ -291,20 +304,22 @@ class EntryListView(Gtk.Box):
def on_fab_clicked(self, button):
logg.info("FAB clicked - opening create entry window")
-
+
create_page = create_entry_page(self.nav_view, self.entry_controller)
self.nav_view.push(create_page)
-
def refresh_data(self):
filtered = list(self.entries)
if self.active_filter == "account_type":
account_type_idx = self.account_type_dropdown.get_selected()
- account_type = self.account_type_dropdown.get_model().get_string(account_type_idx)
+ account_type = self.account_type_dropdown.get_model().get_string(
+ account_type_idx
+ )
if account_type != "All types":
filtered = [
- e for e in filtered
+ e
+ for e in filtered
if e.source_type.lower() == account_type.lower()
or e.dest_type.lower() == account_type.lower()
]
@@ -313,7 +328,8 @@ class EntryListView(Gtk.Box):
keyword = self.keyword_entry.get_text().strip().lower()
if keyword:
filtered = [
- e for e in filtered
+ e
+ for e in filtered
if keyword in (e.description or "").lower()
or keyword in (e.source_path or "").lower()
or keyword in (e.dest_path or "").lower()
@@ -332,91 +348,87 @@ class EntryListView(Gtk.Box):
if sort_type == "serial":
filtered = sorted(filtered, key=lambda e: e.serial)
else:
- filtered = sorted(filtered, key=lambda e: (e.tx_date, e.serial), reverse=True)
+ filtered = sorted(
+ filtered, key=lambda e: (e.tx_date, e.serial), reverse=True
+ )
self._reload_store(filtered)
-
def on_create_window_closed(self, window):
logg.info("Create entry window closed")
if self.refresh_callback:
- self.refresh_callback()
- return False
-
+ self.refresh_callback()
+ return False
def _create_table_section(self):
"""Create the entry list table with all columns"""
table_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
-
+
self.entry_store = Gio.ListStore.new(EntryItem)
selection_model = Gtk.SingleSelection.new(self.entry_store)
-
+
column_view = Gtk.ColumnView(model=selection_model)
column_view.add_css_class("data-table")
column_view.set_show_row_separators(True)
column_view.set_show_column_separators(True)
-
+
serial_factory = Gtk.SignalListItemFactory()
serial_factory.connect("setup", self._on_serial_setup)
serial_factory.connect("bind", self._on_serial_bind)
serial_col = Gtk.ColumnViewColumn(title="Serial No", factory=serial_factory)
- serial_col.set_fixed_width(70)
+ serial_col.set_fixed_width(70)
column_view.append_column(serial_col)
-
+
date_factory = Gtk.SignalListItemFactory()
date_factory.connect("setup", self._on_date_setup)
date_factory.connect("bind", self._on_date_bind)
date_col = Gtk.ColumnViewColumn(title="Transaction date", factory=date_factory)
- date_col.set_fixed_width(100)
+ date_col.set_fixed_width(100)
column_view.append_column(date_col)
-
+
desc_factory = Gtk.SignalListItemFactory()
desc_factory.connect("setup", self._on_desc_setup)
desc_factory.connect("bind", self._on_desc_bind)
desc_col = Gtk.ColumnViewColumn(title="Description", factory=desc_factory)
- desc_col.set_expand(True)
+ desc_col.set_expand(True)
column_view.append_column(desc_col)
-
+
auth_factory = Gtk.SignalListItemFactory()
auth_factory.connect("setup", self._on_auth_setup)
auth_factory.connect("bind", self._on_auth_bind)
auth_col = Gtk.ColumnViewColumn(title="Auth state", factory=auth_factory)
- auth_col.set_fixed_width(120)
+ auth_col.set_fixed_width(120)
column_view.append_column(auth_col)
-
source_factory = Gtk.SignalListItemFactory()
source_factory.connect("setup", self._on_source_setup)
source_factory.connect("bind", self._on_source_bind)
source_col = Gtk.ColumnViewColumn(title="Source", factory=source_factory)
- source_col.set_expand(True)
+ source_col.set_expand(True)
column_view.append_column(source_col)
-
+
dest_factory = Gtk.SignalListItemFactory()
dest_factory.connect("setup", self._on_dest_setup)
dest_factory.connect("bind", self._on_dest_bind)
dest_col = Gtk.ColumnViewColumn(title="Destination", factory=dest_factory)
- dest_col.set_expand(True)
+ dest_col.set_expand(True)
column_view.append_column(dest_col)
-
-
+
action_factory = Gtk.SignalListItemFactory()
action_factory.connect("setup", self._on_action_setup)
action_factory.connect("bind", self._on_action_bind)
action_col = Gtk.ColumnViewColumn(title="Action", factory=action_factory)
- action_col.set_fixed_width(80)
+ action_col.set_fixed_width(80)
column_view.append_column(action_col)
-
+
scrolled = Gtk.ScrolledWindow()
scrolled.set_vexpand(True)
scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolled.set_child(column_view)
-
+
table_box.append(scrolled)
-
+
return table_box
-
-
def _on_serial_setup(self, factory, list_item):
label = Gtk.Label()
@@ -440,7 +452,7 @@ class EntryListView(Gtk.Box):
label = list_item.get_child()
date_only = str(entry.tx_date)[:10]
label.set_label(date_only)
-
+
def _on_desc_setup(self, factory, list_item):
label = Gtk.Label()
label.set_halign(Gtk.Align.START)
@@ -455,21 +467,21 @@ class EntryListView(Gtk.Box):
def _on_auth_setup(self, factory, list_item):
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
box.set_halign(Gtk.Align.CENTER)
-
+
badge = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=4)
badge.set_margin_top(4)
badge.set_margin_bottom(4)
badge.set_margin_start(8)
badge.set_margin_end(8)
badge.add_css_class("badge")
-
+
icon = Gtk.Label()
badge.append(icon)
-
+
text = Gtk.Label()
text.add_css_class("caption")
badge.append(text)
-
+
box.append(badge)
list_item.set_child(box)
@@ -478,16 +490,16 @@ class EntryListView(Gtk.Box):
entry = list_item.get_item()
box = list_item.get_child()
badge = box.get_first_child()
-
+
badge.remove_css_class("auth-trusted")
badge.remove_css_class("auth-not-trusted")
badge.remove_css_class("auth-unknown")
badge.remove_css_class("auth-invalid")
badge.remove_css_class("auth-unsigned")
-
+
icon_label = badge.get_first_child()
text_label = icon_label.get_next_sibling()
-
+
auth_state = entry.auth_state
if auth_state == "trusted":
badge.add_css_class("auth-trusted")
@@ -505,7 +517,7 @@ class EntryListView(Gtk.Box):
badge.add_css_class("auth-invalid")
icon_label.set_text("✗")
text_label.set_text("Invalid")
- else:
+ else:
badge.add_css_class("auth-unsigned")
icon_label.set_text("○")
text_label.set_text("No Key")
@@ -523,7 +535,7 @@ class EntryListView(Gtk.Box):
"""Bind source data in compressed format: [BTC] Expense.deposit/rent"""
entry = list_item.get_item()
label = list_item.get_child()
-
+
# Format: [UNIT] Type.path
formatted = f"[{entry.source_unit}]\n{entry.source_type}.{entry.source_path}"
label.set_text(formatted)
@@ -541,17 +553,16 @@ class EntryListView(Gtk.Box):
"""Bind destination data in compressed format"""
entry = list_item.get_item()
label = list_item.get_child()
-
+
# Format: [UNIT] Type.path
formatted = f"[{entry.dest_unit}]\n{entry.dest_type}.{entry.dest_path}"
label.set_text(formatted)
-
def _on_action_setup(self, factory, list_item):
"""Setup action cell"""
button = Gtk.Button(label="View")
- button.add_css_class("link")
- button.add_css_class("accent")
+ button.add_css_class("link")
+ button.add_css_class("accent")
button.set_halign(Gtk.Align.CENTER)
button.handler_id = None
list_item.set_child(button)
@@ -560,8 +571,8 @@ class EntryListView(Gtk.Box):
"""Bind action button"""
entry = list_item.get_item()
button = list_item.get_child()
-
- if hasattr(button, 'handler_id') and button.handler_id is not None:
+
+ if hasattr(button, "handler_id") and button.handler_id is not None:
button.disconnect(button.handler_id)
button.handler_id = button.connect("clicked", self._on_view_entry, entry)
@@ -569,12 +580,16 @@ class EntryListView(Gtk.Box):
def _on_view_entry(self, button, entry):
"""Handle view button click"""
logg.info(f"View entry clicked: {entry.serial}")
- details_page = create_entry_details_page(entry,self.nav_view,self.entry_controller.get_asset_bytes)
+ details_page = create_entry_details_page(
+ entry,
+ self.nav_view,
+ self.entry_controller,
+ self.toast_overlay,
+ self.entry_controller.get_asset_bytes,
+ )
self.nav_view.push(details_page)
-
-
- def format_attachments(self,assets):
+ def format_attachments(self, assets):
if not assets:
return ""
@@ -582,30 +597,30 @@ class EntryListView(Gtk.Box):
a.slug or a.uuid or (a.digest.hex()[:8] if a.digest else "unknown")
for a in assets
)
-
+
def _make_entry_item(self, entry) -> EntryItem:
signers_raw = entry.signer_pubkeys
signers_display = ", ".join([f"{k[:8]}...{k[-6:]}" for k in signers_raw])
return EntryItem(
- serial=entry.serial,
- parent_digest=entry.parent_digest,
- tx_date=entry.tx_date,
- tx_ref=entry.tx_reference,
- tx_date_rg=entry.date_registered,
- description=entry.description,
- auth_state="trusted",
- amount=entry.amount,
- source_path=entry.source_path,
- source_type= entry.source_type,
- source_unit=entry.source_unit,
- unit_index=entry.unit_index,
- signers=signers_display,
- signers_raw=signers_raw,
- dest_path=entry.dest_path,
- dest_unit=entry.dest_unit,
- dest_type=entry.dest_type,
- attachments=self.format_attachments(entry.attachments),
- attachments_raw=entry.attachments
+ serial=entry.serial,
+ parent_digest=entry.parent_digest,
+ tx_date=entry.tx_date,
+ tx_ref=entry.tx_reference,
+ tx_date_rg=entry.date_registered,
+ description=entry.description,
+ auth_state="trusted",
+ amount=entry.amount,
+ source_path=entry.source_path,
+ source_type=entry.source_type,
+ source_unit=entry.source_unit,
+ unit_index=entry.unit_index,
+ signers=signers_display,
+ signers_raw=signers_raw,
+ dest_path=entry.dest_path,
+ dest_unit=entry.dest_unit,
+ dest_type=entry.dest_type,
+ attachments=self.format_attachments(entry.attachments),
+ attachments_raw=entry.attachments,
)
def _reload_store(self, entries):
@@ -617,12 +632,11 @@ class EntryListView(Gtk.Box):
self.entries = self.entry_controller.get_all_entries()
self._sort_and_reload("serial")
-
def _sort_and_reload(self, sort_type):
if sort_type == "serial":
sorted_entries = sorted(self.entries, key=lambda e: e.serial)
elif sort_type == "datetime":
- sorted_entries = sorted(self.entries, key=lambda e: (e.tx_date, e.serial), reverse=True)
+ sorted_entries = sorted(
+ self.entries, key=lambda e: (e.tx_date, e.serial), reverse=True
+ )
self._reload_store(sorted_entries)
-
-