Commit graph

7 commits

Author SHA1 Message Date
9bed643023 window: prefetch images and queue offline read/unread actions
After a successful article refresh, all images referenced in article
content are downloaded in the background so articles can be read
offline. The prefetch only runs when the cache-images setting is
enabled and the connection is not metered.

Read/unread state changes that fail to reach the server (e.g. when
offline) are now persisted to a local queue in
~/.cache/net.jeena.FeedTheMonkey/pending_sync.json. The queue is
flushed at the start of the next successful fetch.
2026-03-21 02:45:45 +00:00
fda441bebd feature: cache article images for offline reading
After fetching articles, all remote images referenced in article content
are downloaded to ~/.local/share/net.jeena.FeedTheMonkey/images/ and
their src attributes rewritten to file:// URIs. Subsequent loads of the
same article (including from the cache on the next startup) display
images without a network connection.

Metered-connection awareness: image caching is skipped automatically
when GIO reports the network connection as metered, regardless of the
preference setting.

A "Cache Images" toggle in Preferences lets the user disable caching
entirely (stored in the cache-images GSettings key).

After each refresh, images no longer referenced by any article in the
current unread list are deleted from the cache directory to prevent
unbounded disk growth.
2026-03-21 01:19:49 +00:00
183191727b window: persist article list and open article across restarts
On shutdown the full article list (including current read/unread state)
and the ID of the open article are saved to
~/.local/share/net.jeena.FeedTheMonkey/cache.json.

On next launch:
- The cached articles are loaded into the list immediately, before any
  network request, so the sidebar is populated and the previously open
  article is visible without waiting for the server.
- The article content is injected into the WebView once its base HTML
  finishes loading (LoadEvent::Finished), avoiding a race where
  window.setArticle() did not yet exist.
- A background refresh then fetches fresh data from the server; if the
  previously open article still exists its selection is preserved,
  otherwise the first item is selected.
- Network errors during a background refresh show a toast instead of
  replacing the visible article list with an error page.
2026-03-21 01:13:09 +00:00
5dee5cc52b fix: tokio runtime, Enter-to-login, and server URL handling
Three bugs fixed:

- No tokio reactor: glib::spawn_future_local does not provide a
  tokio context, so reqwest/hyper panicked at runtime. Introduce
  src/runtime.rs with a multi-thread tokio Runtime (init() called
  from main before the GTK app starts). runtime::spawn() posts the
  async result back to GTK via a tokio oneshot channel awaited by
  glib::spawn_future_local, which only polls a flag (no I/O).
  runtime::spawn_bg() is used for fire-and-forget background calls.

- Enter key didn't submit login: connect_apply on AdwEntryRow only
  fires when show-apply-button is true. Switch to connect_entry_activated
  which fires on Return in all three login rows.

- Wrong API URL: the app constructed /accounts/ClientLogin directly
  off the server host, yielding a 404. Add normalize_base_url() in
  api.rs that appends /api/greader.php when the URL doesn't already
  contain it, so users can enter just https://rss.example.com.
2026-03-20 12:17:27 +00:00
813dda3579 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
2026-03-20 11:57:06 +00:00
8db0b16954 scaffold: compile GSettings schema at build time for dev runs
build.rs now runs glib-compile-schemas on data/ so that debug builds
can find the schema without a system-wide install. main.rs sets
GSETTINGS_SCHEMA_DIR from the build-time constant when running in
debug mode.
2026-03-20 11:36:12 +00:00
3339bb5ec8 scaffold: Epic 1 — project scaffold
Add the full Rust + GTK4 + libadwaita project skeleton:
- Cargo.toml with all dependencies (gtk4 0.11, libadwaita 0.9,
  webkit6 0.6, reqwest, serde, tokio, libsecret)
- build.rs that compiles Blueprint .blp files and bundles a GResource
- data/ui/window.blp — AdwApplicationWindow with AdwNavigationSplitView,
  sidebar with refresh button/spinner and primary menu,
  content page with article menu
- data/resources.gresource.xml bundling UI, HTML, and CSS
- data/net.jeena.FeedTheMonkey.gschema.xml with all GSettings keys
- html/content.html and html/content.css (minimal placeholders)
- src/main.rs, src/app.rs — AdwApplication with APP_ID net.jeena.FeedTheMonkey
- src/window.rs — AdwApplicationWindow GObject subclass loading the
  Blueprint template and persisting window size in GSettings
- COPYING (GPL-3.0) restored from master

The app compiles and the binary is ready to open a blank window.
2026-03-20 11:22:19 +00:00