forked from jeena/FeedTheMonkey
app: implement Epics 2–10
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
This commit is contained in:
parent
8db0b16954
commit
813dda3579
22 changed files with 1838 additions and 42 deletions
77
src/app.rs
77
src/app.rs
|
|
@ -1,4 +1,5 @@
|
|||
use gtk4::prelude::*;
|
||||
use libadwaita::prelude::*;
|
||||
|
||||
use crate::window::FeedTheMonkeyWindow;
|
||||
|
||||
|
|
@ -51,6 +52,46 @@ mod imp {
|
|||
gio::resources_register(&resource);
|
||||
|
||||
let window = FeedTheMonkeyWindow::new(app.upcast_ref());
|
||||
|
||||
// Shortcuts overlay
|
||||
let builder = gtk4::Builder::from_resource(
|
||||
"/net/jeena/FeedTheMonkey/ui/shortcuts.ui",
|
||||
);
|
||||
let overlay: gtk4::ShortcutsWindow = builder.object("help_overlay").unwrap();
|
||||
window.set_help_overlay(Some(&overlay));
|
||||
|
||||
setup_shortcuts(&window);
|
||||
|
||||
// About action on app
|
||||
let app_weak = app.downgrade();
|
||||
let about_action = gio::SimpleAction::new("about", None);
|
||||
about_action.connect_activate(move |_, _| {
|
||||
if let Some(app) = app_weak.upgrade() {
|
||||
let win = app.active_window();
|
||||
let dialog = libadwaita::AboutDialog::builder()
|
||||
.application_name("FeedTheMonkey")
|
||||
.application_icon("feedthemonkey")
|
||||
.version("3.0.0")
|
||||
.copyright("© Jeena Paradies")
|
||||
.license_type(gtk4::License::Gpl30)
|
||||
.website("https://github.com/jeena/FeedTheMonkey")
|
||||
.developer_name("Jeena Paradies")
|
||||
.build();
|
||||
dialog.present(win.as_ref().map(|w| w.upcast_ref::<gtk4::Widget>()));
|
||||
}
|
||||
});
|
||||
app.add_action(&about_action);
|
||||
|
||||
// Quit action
|
||||
let app_weak = app.downgrade();
|
||||
let quit_action = gio::SimpleAction::new("quit", None);
|
||||
quit_action.connect_activate(move |_, _| {
|
||||
if let Some(app) = app_weak.upgrade() {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
app.add_action(&quit_action);
|
||||
|
||||
window.present();
|
||||
}
|
||||
}
|
||||
|
|
@ -58,3 +99,39 @@ mod imp {
|
|||
impl GtkApplicationImpl for FeedTheMonkeyApp {}
|
||||
impl AdwApplicationImpl for FeedTheMonkeyApp {}
|
||||
}
|
||||
|
||||
fn setup_shortcuts(window: &FeedTheMonkeyWindow) {
|
||||
use gtk4::gdk::{Key, ModifierType};
|
||||
|
||||
let controller = gtk4::ShortcutController::new();
|
||||
controller.set_scope(gtk4::ShortcutScope::Global);
|
||||
|
||||
let add = |controller: >k4::ShortcutController,
|
||||
key: Key,
|
||||
mods: ModifierType,
|
||||
action_name: &str| {
|
||||
let trigger = gtk4::KeyvalTrigger::new(key, mods);
|
||||
let action = gtk4::NamedAction::new(action_name);
|
||||
let shortcut = gtk4::Shortcut::new(Some(trigger), Some(action));
|
||||
controller.add_shortcut(shortcut);
|
||||
};
|
||||
|
||||
add(&controller, Key::j, ModifierType::empty(), "win.next-article");
|
||||
add(&controller, Key::Right, ModifierType::empty(), "win.next-article");
|
||||
add(&controller, Key::k, ModifierType::empty(), "win.prev-article");
|
||||
add(&controller, Key::Left, ModifierType::empty(), "win.prev-article");
|
||||
add(&controller, Key::r, ModifierType::empty(), "win.reload");
|
||||
add(&controller, Key::u, ModifierType::empty(), "win.mark-unread");
|
||||
add(&controller, Key::Return, ModifierType::empty(), "win.open-in-browser");
|
||||
add(&controller, Key::n, ModifierType::empty(), "win.open-in-browser");
|
||||
add(&controller, Key::plus, ModifierType::CONTROL_MASK, "win.zoom-in");
|
||||
add(&controller, Key::equal, ModifierType::CONTROL_MASK, "win.zoom-in");
|
||||
add(&controller, Key::minus, ModifierType::CONTROL_MASK, "win.zoom-out");
|
||||
add(&controller, Key::_0, ModifierType::CONTROL_MASK, "win.zoom-reset");
|
||||
add(&controller, Key::F11, ModifierType::empty(), "win.toggle-fullscreen");
|
||||
add(&controller, Key::w, ModifierType::CONTROL_MASK, "window.close");
|
||||
add(&controller, Key::q, ModifierType::CONTROL_MASK, "app.quit");
|
||||
add(&controller, Key::F1, ModifierType::empty(), "win.show-help-overlay");
|
||||
|
||||
window.add_controller(controller);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue