Add the full application logic on top of the Epic 1 skeleton:
Epic 2 — Authentication
- LoginDialog (AdwDialog, Blueprint template) with server URL,
username, and password fields; emits logged-in signal on submit
- credentials.rs: store/load/clear via libsecret (password_store_sync /
password_search_sync / password_clear_sync, v0_19 feature)
- api.rs: Api::login() parses Auth= token from ClientLogin response;
fetch_write_token() fetches the write token
- Auto-login on startup from stored credentials; logout with
AdwAlertDialog confirmation; login errors shown in AdwAlertDialog
Epic 3 — Article fetching
- model.rs: Article struct and ArticleObject GObject wrapper with
unread property for list store binding
- Api::fetch_unread() deserializes Google Reader JSON, derives unread
from categories, generates plain-text excerpt
- Sidebar uses a GtkStack with placeholder / loading / empty / error /
list pages; AdwSpinnerPaintable while fetching; Try Again button
Epic 4 — Sidebar
- article_row.blp: composite template with feed title, date, title,
and excerpt labels
- ArticleRow GObject subclass; binds ArticleObject, watches unread
notify to apply .dim-label on the title; relative timestamp format
Epic 5 — Content pane
- content.html updated: setArticle(), checkKey(), feedthemonkey: URI
navigation scheme; dark mode via prefers-color-scheme
- content.css: proper article layout, dark mode, code blocks
- WebView loaded from GResource; decide-policy intercepts
feedthemonkey:{next,previous,open} and all external links
Epic 6 — Read state
- Api::mark_read() / mark_unread() via edit-tag endpoint
- Optimistic unread toggle on ArticleObject; background API calls;
mark_unread_guard prevents re-marking on navigation
- AdwToast shown on mark-unread
Epic 7 — Keyboard shortcuts
- GtkShortcutController on window for all shortcuts from the backlog
- shortcuts.blp: AdwShortcutsWindow documenting all shortcuts
- F1 opens shortcuts dialog; Ctrl+W closes window; Ctrl+Q quits
Epic 8 — Zoom
- zoom_in/zoom_out/zoom_reset wired to Ctrl+±/0; zoom level saved to
and restored from GSettings zoom-level key
Epic 9 — Window state persistence
- Window width/height/maximized saved on close, restored on open
- (Sidebar width deferred — AdwNavigationSplitView fraction binding)
Epic 10 — Polish
- AdwAboutDialog with app name, version, GPL-3.0, website
- Logout confirmation AdwAlertDialog with destructive button
- Win.toggle-fullscreen action (F11)
- Api dropped on window close to cancel in-flight requests
216 lines
No EOL
10 KiB
XML
216 lines
No EOL
10 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!--
|
|
DO NOT EDIT!
|
|
This file was @generated by blueprint-compiler. Instead, edit the
|
|
corresponding .blp file and regenerate this file with blueprint-compiler.
|
|
-->
|
|
<interface>
|
|
<requires lib="gtk" version="4.0"/>
|
|
<template class="FeedTheMonkeyWindow" parent="AdwApplicationWindow">
|
|
<property name="default-width">900</property>
|
|
<property name="default-height">600</property>
|
|
<child>
|
|
<object class="AdwToastOverlay" id="toast_overlay">
|
|
<child>
|
|
<object class="AdwNavigationSplitView" id="split_view">
|
|
<property name="sidebar">
|
|
<object class="AdwNavigationPage">
|
|
<property name="title" translatable="yes">FeedTheMonkey</property>
|
|
<child>
|
|
<object class="AdwToolbarView">
|
|
<child type="top">
|
|
<object class="AdwHeaderBar">
|
|
<child type="start">
|
|
<object class="GtkStack" id="refresh_stack">
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">button</property>
|
|
<property name="child">
|
|
<object class="GtkButton" id="refresh_button">
|
|
<property name="icon-name">view-refresh-symbolic</property>
|
|
<property name="tooltip-text" translatable="yes">Refresh</property>
|
|
<property name="action-name">win.reload</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">spinner</property>
|
|
<property name="child">
|
|
<object class="AdwSpinner"></object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
<child type="end">
|
|
<object class="GtkMenuButton" id="menu_button">
|
|
<property name="icon-name">open-menu-symbolic</property>
|
|
<property name="primary">true</property>
|
|
<property name="menu-model">primary_menu</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStack" id="sidebar_content">
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">placeholder</property>
|
|
<property name="child">
|
|
<object class="AdwStatusPage">
|
|
<property name="icon-name">rss-symbolic</property>
|
|
<property name="title" translatable="yes">FeedTheMonkey</property>
|
|
<property name="description" translatable="yes">Log in to load your articles</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">loading</property>
|
|
<property name="child">
|
|
<object class="AdwStatusPage">
|
|
<property name="paintable">
|
|
<object class="AdwSpinnerPaintable"></object>
|
|
</property>
|
|
<property name="title" translatable="yes">Loading…</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">empty</property>
|
|
<property name="child">
|
|
<object class="AdwStatusPage">
|
|
<property name="icon-name">rss-symbolic</property>
|
|
<property name="title" translatable="yes">No Unread Articles</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">error</property>
|
|
<property name="child">
|
|
<object class="AdwStatusPage" id="error_status">
|
|
<property name="icon-name">network-error-symbolic</property>
|
|
<property name="title" translatable="yes">Could Not Load Articles</property>
|
|
<child>
|
|
<object class="GtkButton">
|
|
<property name="label" translatable="yes">Try Again</property>
|
|
<property name="halign">3</property>
|
|
<property name="action-name">win.reload</property>
|
|
<style>
|
|
<class name="pill"/>
|
|
<class name="suggested-action"/>
|
|
</style>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">list</property>
|
|
<property name="child">
|
|
<object class="GtkScrolledWindow">
|
|
<property name="hscrollbar-policy">2</property>
|
|
<child>
|
|
<object class="GtkListView" id="article_list_view">
|
|
<property name="single-click-activate">true</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</property>
|
|
<property name="content">
|
|
<object class="AdwNavigationPage" id="content_page">
|
|
<property name="title" translatable="yes">FeedTheMonkey</property>
|
|
<child>
|
|
<object class="AdwToolbarView">
|
|
<property name="top-bar-style">1</property>
|
|
<child type="top">
|
|
<object class="AdwHeaderBar">
|
|
<child type="end">
|
|
<object class="GtkMenuButton" id="article_menu_button">
|
|
<property name="icon-name">view-more-symbolic</property>
|
|
<property name="menu-model">article_menu</property>
|
|
<property name="visible">false</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStack" id="content_stack">
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">empty</property>
|
|
<property name="child">
|
|
<object class="AdwStatusPage">
|
|
<property name="icon-name">document-open-symbolic</property>
|
|
<property name="title" translatable="yes">No Article Selected</property>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
<child>
|
|
<object class="GtkStackPage">
|
|
<property name="name">webview</property>
|
|
<property name="child">
|
|
<object class="WebKitWebView" id="web_view"></object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</property>
|
|
</object>
|
|
</child>
|
|
</object>
|
|
</child>
|
|
</template>
|
|
<menu id="primary_menu">
|
|
<section>
|
|
<item>
|
|
<attribute name="label" translatable="yes">Log Out</attribute>
|
|
<attribute name="action">win.logout</attribute>
|
|
</item>
|
|
</section>
|
|
<section>
|
|
<item>
|
|
<attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
|
|
<attribute name="action">win.show-help-overlay</attribute>
|
|
</item>
|
|
<item>
|
|
<attribute name="label" translatable="yes">About FeedTheMonkey</attribute>
|
|
<attribute name="action">app.about</attribute>
|
|
</item>
|
|
</section>
|
|
</menu>
|
|
<menu id="article_menu">
|
|
<section>
|
|
<item>
|
|
<attribute name="label" translatable="yes">Mark Unread</attribute>
|
|
<attribute name="action">win.mark-unread</attribute>
|
|
</item>
|
|
<item>
|
|
<attribute name="label" translatable="yes">Open in Browser</attribute>
|
|
<attribute name="action">win.open-in-browser</attribute>
|
|
</item>
|
|
</section>
|
|
</menu>
|
|
</interface> |