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:
Jeena 2026-03-20 11:57:06 +00:00
parent 8db0b16954
commit 813dda3579
22 changed files with 1838 additions and 42 deletions

View file

@ -1,6 +1,11 @@
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
margin: 1em 2em;
margin: 0;
padding: 0;
line-height: 1.6;
color: #222;
background: #fff;
@ -11,8 +16,86 @@ body {
color: #ddd;
background: #1e1e1e;
}
a {
color: #78aeed;
}
img {
opacity: 0.85;
}
}
h1 {
font-size: 1.4em;
#header {
padding: 1.5em 2em 1em;
border-bottom: 1px solid rgba(0,0,0,0.1);
margin-bottom: 1em;
}
@media (prefers-color-scheme: dark) {
#header {
border-bottom-color: rgba(255,255,255,0.1);
}
}
#feed-title {
font-size: 0.8em;
opacity: 0.6;
margin-bottom: 0.25em;
text-transform: uppercase;
letter-spacing: 0.05em;
}
#title {
font-size: 1.5em;
margin: 0 0 0.5em;
line-height: 1.3;
}
#meta {
font-size: 0.85em;
opacity: 0.6;
margin-bottom: 0.5em;
}
#meta span + span::before {
content: ' · ';
}
#link {
font-size: 0.85em;
}
#content {
padding: 0 2em 2em;
max-width: 800px;
}
#content img {
max-width: 100%;
height: auto;
}
#content pre {
overflow-x: auto;
background: rgba(0,0,0,0.05);
padding: 1em;
border-radius: 4px;
}
@media (prefers-color-scheme: dark) {
#content pre {
background: rgba(255,255,255,0.05);
}
}
#content blockquote {
border-left: 3px solid rgba(0,0,0,0.2);
margin-left: 0;
padding-left: 1em;
opacity: 0.8;
}
@media (prefers-color-scheme: dark) {
#content blockquote {
border-left-color: rgba(255,255,255,0.2);
}
}