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.
76 lines
2.1 KiB
Rust
76 lines
2.1 KiB
Rust
use gtk4::glib;
|
|
use gtk4::prelude::*;
|
|
use gtk4::subclass::prelude::*;
|
|
use std::cell::RefCell;
|
|
|
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
|
pub struct Article {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub feed_title: String,
|
|
pub author: String,
|
|
pub link: String,
|
|
pub published: i64,
|
|
pub content: String,
|
|
pub excerpt: String,
|
|
pub unread: bool,
|
|
}
|
|
|
|
// ── GObject wrapper ──────────────────────────────────────────────────────────
|
|
|
|
glib::wrapper! {
|
|
pub struct ArticleObject(ObjectSubclass<imp::ArticleObject>);
|
|
}
|
|
|
|
impl ArticleObject {
|
|
pub fn new(article: Article) -> Self {
|
|
let obj: Self = glib::Object::new();
|
|
*obj.imp().article.borrow_mut() = article;
|
|
obj
|
|
}
|
|
|
|
pub fn article(&self) -> std::cell::Ref<'_, Article> {
|
|
self.imp().article.borrow()
|
|
}
|
|
|
|
pub fn set_unread(&self, unread: bool) {
|
|
self.imp().article.borrow_mut().unread = unread;
|
|
self.notify("unread");
|
|
}
|
|
}
|
|
|
|
mod imp {
|
|
use super::*;
|
|
|
|
#[derive(Default)]
|
|
pub struct ArticleObject {
|
|
pub article: RefCell<Article>,
|
|
}
|
|
|
|
#[glib::object_subclass]
|
|
impl ObjectSubclass for ArticleObject {
|
|
const NAME: &'static str = "ArticleObject";
|
|
type Type = super::ArticleObject;
|
|
}
|
|
|
|
impl ObjectImpl for ArticleObject {
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
|
use std::sync::OnceLock;
|
|
static PROPS: OnceLock<Vec<glib::ParamSpec>> = OnceLock::new();
|
|
PROPS.get_or_init(|| {
|
|
vec![
|
|
glib::ParamSpecBoolean::builder("unread")
|
|
.read_only()
|
|
.build(),
|
|
]
|
|
})
|
|
}
|
|
|
|
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
match pspec.name() {
|
|
"unread" => self.article.borrow().unread.to_value(),
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
}
|
|
}
|