Compare commits
3 commits
0917f57dbd
...
41312f48f3
| Author | SHA1 | Date | |
|---|---|---|---|
| 41312f48f3 | |||
| d39c7f824b | |||
| 07b41c7407 |
1 changed files with 76 additions and 26 deletions
102
src/window.rs
102
src/window.rs
|
|
@ -137,6 +137,7 @@ pub mod imp {
|
||||||
self.setup_capture_keys();
|
self.setup_capture_keys();
|
||||||
self.restore_from_cache();
|
self.restore_from_cache();
|
||||||
self.auto_login();
|
self.auto_login();
|
||||||
|
self.web_view.grab_focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,6 +206,8 @@ pub mod imp {
|
||||||
fn setup_list(&self) {
|
fn setup_list(&self) {
|
||||||
let store = gio::ListStore::new::<ArticleObject>();
|
let store = gio::ListStore::new::<ArticleObject>();
|
||||||
let selection = gtk4::SingleSelection::new(Some(store.clone()));
|
let selection = gtk4::SingleSelection::new(Some(store.clone()));
|
||||||
|
selection.set_autoselect(false);
|
||||||
|
selection.set_can_unselect(true);
|
||||||
*self.article_store.borrow_mut() = Some(store);
|
*self.article_store.borrow_mut() = Some(store);
|
||||||
|
|
||||||
let factory = gtk4::SignalListItemFactory::new();
|
let factory = gtk4::SignalListItemFactory::new();
|
||||||
|
|
@ -243,10 +246,12 @@ pub mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_article_selected(&self, obj: ArticleObject) {
|
fn on_article_selected(&self, obj: ArticleObject) {
|
||||||
// Mark previous article as read (unless guard is set)
|
// Mark the previous article as read — both on the server and in the
|
||||||
|
// sidebar — now that the user has navigated away from it.
|
||||||
if !*self.mark_unread_guard.borrow() {
|
if !*self.mark_unread_guard.borrow() {
|
||||||
if let Some(prev_id) = self.current_article_id.borrow().clone() {
|
if let Some(prev_id) = self.current_article_id.borrow().clone() {
|
||||||
if prev_id != obj.article().id {
|
if prev_id != obj.article().id {
|
||||||
|
self.mark_read_in_list(&prev_id);
|
||||||
self.bg_mark_read(prev_id);
|
self.bg_mark_read(prev_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -254,13 +259,30 @@ pub mod imp {
|
||||||
*self.mark_unread_guard.borrow_mut() = false;
|
*self.mark_unread_guard.borrow_mut() = false;
|
||||||
|
|
||||||
let article = obj.article().clone();
|
let article = obj.article().clone();
|
||||||
|
let same_article = self.current_article_id.borrow().as_deref() == Some(&*article.id);
|
||||||
*self.current_article_id.borrow_mut() = Some(article.id.clone());
|
*self.current_article_id.borrow_mut() = Some(article.id.clone());
|
||||||
|
|
||||||
self.article_menu_button.set_visible(true);
|
self.article_menu_button.set_visible(true);
|
||||||
|
|
||||||
// Load in webview
|
// Skip WebView reload when re-selecting the same article (e.g. after
|
||||||
self.load_article_in_webview(&article);
|
// a server refresh) so the user's scroll position is preserved.
|
||||||
obj.set_unread(false);
|
if !same_article {
|
||||||
|
self.load_article_in_webview(&article);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark an article as read in the sidebar list (UI only).
|
||||||
|
fn mark_read_in_list(&self, article_id: &str) {
|
||||||
|
if let Some(store) = self.article_store.borrow().as_ref() {
|
||||||
|
for i in 0..store.n_items() {
|
||||||
|
if let Some(obj) = store.item(i).and_downcast::<ArticleObject>() {
|
||||||
|
if obj.article().id == article_id {
|
||||||
|
obj.set_unread(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reload_current_article(&self) {
|
fn reload_current_article(&self) {
|
||||||
|
|
@ -714,39 +736,64 @@ pub mod imp {
|
||||||
store.append(&ArticleObject::new(a.clone()));
|
store.append(&ArticleObject::new(a.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save cache and clean up unreferenced images.
|
|
||||||
let sel_id = saved_id.as_deref().unwrap_or("");
|
|
||||||
crate::cache::save(&articles, sel_id);
|
|
||||||
crate::image_cache::cleanup(&articles);
|
crate::image_cache::cleanup(&articles);
|
||||||
|
|
||||||
if store.n_items() == 0 {
|
let n = store.n_items();
|
||||||
|
if n == 0 {
|
||||||
imp.sidebar_content.set_visible_child_name("empty");
|
imp.sidebar_content.set_visible_child_name("empty");
|
||||||
*imp.current_article_id.borrow_mut() = None;
|
*imp.current_article_id.borrow_mut() = None;
|
||||||
imp.article_menu_button.set_visible(false);
|
imp.article_menu_button.set_visible(false);
|
||||||
imp.content_stack.set_visible_child_name("empty");
|
imp.content_stack.set_visible_child_name("empty");
|
||||||
|
crate::cache::save(&articles, "");
|
||||||
} else {
|
} else {
|
||||||
// Try to re-select the same article; fall back to first.
|
// Try to re-select the same article the user was reading.
|
||||||
let mut select_idx = 0u32;
|
let found_idx = saved_id.as_ref().and_then(|id| {
|
||||||
if let Some(ref id) = saved_id {
|
(0..n).find(|&i| {
|
||||||
for i in 0..store.n_items() {
|
store.item(i).and_downcast::<ArticleObject>()
|
||||||
if store.item(i).and_downcast::<ArticleObject>()
|
|
||||||
.map(|o| o.article().id == *id)
|
.map(|o| o.article().id == *id)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
})
|
||||||
select_idx = i;
|
});
|
||||||
break;
|
|
||||||
}
|
if let Some(idx) = found_idx {
|
||||||
}
|
// Article still unread — re-select it without
|
||||||
|
// reloading the WebView (preserves scroll position).
|
||||||
|
*imp.mark_unread_guard.borrow_mut() = true;
|
||||||
|
sel.set_selected(idx);
|
||||||
|
imp.article_list_view.scroll_to(
|
||||||
|
idx,
|
||||||
|
gtk4::ListScrollFlags::SELECT,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
crate::cache::save(&articles,
|
||||||
|
saved_id.as_deref().unwrap_or(""));
|
||||||
|
} else if saved_id.is_some() {
|
||||||
|
// Article was read elsewhere — keep it in the
|
||||||
|
// WebView so the user can finish reading but
|
||||||
|
// leave the sidebar list unselected.
|
||||||
|
crate::cache::save(&articles, "");
|
||||||
|
} else {
|
||||||
|
// No previous article (first load) — select the
|
||||||
|
// first article.
|
||||||
|
sel.set_selected(0);
|
||||||
|
imp.article_list_view.scroll_to(
|
||||||
|
0,
|
||||||
|
gtk4::ListScrollFlags::SELECT,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
crate::cache::save(&articles, "");
|
||||||
}
|
}
|
||||||
*imp.mark_unread_guard.borrow_mut() = true;
|
|
||||||
sel.set_selected(select_idx);
|
|
||||||
imp.article_list_view.scroll_to(
|
|
||||||
select_idx,
|
|
||||||
gtk4::ListScrollFlags::SELECT,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
imp.sidebar_content.set_visible_child_name("list");
|
imp.sidebar_content.set_visible_child_name("list");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always notify the user that the fetch finished.
|
||||||
|
let msg = if n == 1 {
|
||||||
|
String::from("1 unread article")
|
||||||
|
} else {
|
||||||
|
format!("{n} unread articles")
|
||||||
|
};
|
||||||
|
let toast = libadwaita::Toast::new(&msg);
|
||||||
|
imp.toast_overlay.add_toast(toast);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// If we already have cached articles, just show a toast.
|
// If we already have cached articles, just show a toast.
|
||||||
|
|
@ -845,7 +892,10 @@ pub mod imp {
|
||||||
let n = sel.n_items();
|
let n = sel.n_items();
|
||||||
if n == 0 { return }
|
if n == 0 { return }
|
||||||
let current = sel.selected();
|
let current = sel.selected();
|
||||||
let next = if delta > 0 {
|
let next = if current == gtk4::INVALID_LIST_POSITION {
|
||||||
|
// Nothing selected — pick the first or last article.
|
||||||
|
if delta > 0 { 0 } else { n - 1 }
|
||||||
|
} else if delta > 0 {
|
||||||
(current + 1).min(n - 1)
|
(current + 1).min(n - 1)
|
||||||
} else {
|
} else {
|
||||||
current.saturating_sub(1)
|
current.saturating_sub(1)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue