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
165 lines
3.8 KiB
Text
165 lines
3.8 KiB
Text
using Gtk 4.0;
|
|
using Adw 1;
|
|
using WebKit 6.0;
|
|
|
|
template $FeedTheMonkeyWindow : Adw.ApplicationWindow {
|
|
default-width: 900;
|
|
default-height: 600;
|
|
|
|
Adw.ToastOverlay toast_overlay {
|
|
Adw.NavigationSplitView split_view {
|
|
sidebar: Adw.NavigationPage {
|
|
title: _("FeedTheMonkey");
|
|
|
|
Adw.ToolbarView {
|
|
[top]
|
|
Adw.HeaderBar {
|
|
[start]
|
|
Stack refresh_stack {
|
|
StackPage {
|
|
name: "button";
|
|
child: Button refresh_button {
|
|
icon-name: "view-refresh-symbolic";
|
|
tooltip-text: _("Refresh");
|
|
action-name: "win.reload";
|
|
};
|
|
}
|
|
|
|
StackPage {
|
|
name: "spinner";
|
|
child: Adw.Spinner {};
|
|
}
|
|
}
|
|
|
|
[end]
|
|
MenuButton menu_button {
|
|
icon-name: "open-menu-symbolic";
|
|
primary: true;
|
|
menu-model: primary_menu;
|
|
}
|
|
}
|
|
|
|
Stack sidebar_content {
|
|
StackPage {
|
|
name: "placeholder";
|
|
child: Adw.StatusPage {
|
|
icon-name: "rss-symbolic";
|
|
title: _("FeedTheMonkey");
|
|
description: _("Log in to load your articles");
|
|
};
|
|
}
|
|
|
|
StackPage {
|
|
name: "loading";
|
|
child: Adw.StatusPage {
|
|
paintable: Adw.SpinnerPaintable {};
|
|
title: _("Loading…");
|
|
};
|
|
}
|
|
|
|
StackPage {
|
|
name: "empty";
|
|
child: Adw.StatusPage {
|
|
icon-name: "rss-symbolic";
|
|
title: _("No Unread Articles");
|
|
};
|
|
}
|
|
|
|
StackPage {
|
|
name: "error";
|
|
child: Adw.StatusPage error_status {
|
|
icon-name: "network-error-symbolic";
|
|
title: _("Could Not Load Articles");
|
|
|
|
Button {
|
|
label: _("Try Again");
|
|
halign: center;
|
|
action-name: "win.reload";
|
|
styles ["pill", "suggested-action"]
|
|
}
|
|
};
|
|
}
|
|
|
|
StackPage {
|
|
name: "list";
|
|
child: ScrolledWindow {
|
|
hscrollbar-policy: never;
|
|
ListView article_list_view {
|
|
single-click-activate: true;
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
content: Adw.NavigationPage content_page {
|
|
title: _("FeedTheMonkey");
|
|
|
|
Adw.ToolbarView {
|
|
top-bar-style: raised;
|
|
|
|
[top]
|
|
Adw.HeaderBar {
|
|
[end]
|
|
MenuButton article_menu_button {
|
|
icon-name: "view-more-symbolic";
|
|
menu-model: article_menu;
|
|
visible: false;
|
|
}
|
|
}
|
|
|
|
Stack content_stack {
|
|
StackPage {
|
|
name: "empty";
|
|
child: Adw.StatusPage {
|
|
icon-name: "document-open-symbolic";
|
|
title: _("No Article Selected");
|
|
};
|
|
}
|
|
|
|
StackPage {
|
|
name: "webview";
|
|
child: WebKit.WebView web_view {};
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
menu primary_menu {
|
|
section {
|
|
item {
|
|
label: _("Log Out");
|
|
action: "win.logout";
|
|
}
|
|
}
|
|
|
|
section {
|
|
item {
|
|
label: _("Keyboard Shortcuts");
|
|
action: "win.show-help-overlay";
|
|
}
|
|
|
|
item {
|
|
label: _("About FeedTheMonkey");
|
|
action: "app.about";
|
|
}
|
|
}
|
|
}
|
|
|
|
menu article_menu {
|
|
section {
|
|
item {
|
|
label: _("Mark Unread");
|
|
action: "win.mark-unread";
|
|
}
|
|
|
|
item {
|
|
label: _("Open in Browser");
|
|
action: "win.open-in-browser";
|
|
}
|
|
}
|
|
}
|