Remember last selection when navigating back

Pushed the selected index onto a stack on every drill-down (library →
series → season → episode) and pop it on back so the cursor lands on
the row the user just opened, instead of snapping to row 0. Clamps the
restored index to the current list length to survive a shrunk list.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
janisslsmandClaude Opus 4.7 committed 2026-05-16 22:01:02 +03:00
1 parent af43b21f5d
commit 79f3a2e13b
1 file changed
+23 -7
+23 -7
View File
@@ -35,6 +35,10 @@ pub struct App {
pub list_state: ListState, pub list_state: ListState,
pub current_title: Option<String>, pub current_title: Option<String>,
pub breadcrumbs: Vec<(String, String)>, // (id, name) pub breadcrumbs: Vec<(String, String)>, // (id, name)
/// Selected index at each level we navigated away from, so `back` can restore
/// the cursor where the user left it (e.g. Series → Season → Episode → back
/// brings you to the same season/series row, not row 0).
pub selection_stack: Vec<usize>,
pub status: String, pub status: String,
} }
@@ -66,6 +70,7 @@ impl App {
list_state, list_state,
current_title: None, current_title: None,
breadcrumbs: vec![], breadcrumbs: vec![],
selection_stack: vec![],
status, status,
}) })
} }
@@ -122,6 +127,9 @@ impl App {
let Some(entry) = self.home_entries.get(i).cloned() else { let Some(entry) = self.home_entries.get(i).cloned() else {
return Ok(None); return Ok(None);
}; };
// Remember which library/virtual entry was selected for restore on back.
self.selection_stack.clear();
self.selection_stack.push(i);
if entry.id == RESUME_VIRTUAL_ID { if entry.id == RESUME_VIRTUAL_ID {
self.items = self.resume.clone(); self.items = self.resume.clone();
self.view = View::Items; self.view = View::Items;
@@ -151,6 +159,8 @@ impl App {
return Ok(None); return Ok(None);
}; };
if item.is_folder { if item.is_folder {
// Remember position in this folder before drilling down.
self.selection_stack.push(i);
self.status = format!("Loading {}...", item.name); self.status = format!("Loading {}...", item.name);
self.items = self.client.items(&item.id).await?; self.items = self.client.items(&item.id).await?;
self.breadcrumbs.push((item.id.clone(), item.name.clone())); self.breadcrumbs.push((item.id.clone(), item.name.clone()));
@@ -182,6 +192,7 @@ impl App {
} }
// Pop the current breadcrumb (current folder) // Pop the current breadcrumb (current folder)
self.breadcrumbs.pop(); self.breadcrumbs.pop();
let restore = self.selection_stack.pop();
if let Some((parent_id, parent_name)) = self.breadcrumbs.last().cloned() { if let Some((parent_id, parent_name)) = self.breadcrumbs.last().cloned() {
if parent_id == RESUME_VIRTUAL_ID { if parent_id == RESUME_VIRTUAL_ID {
self.items = self.resume.clone(); self.items = self.resume.clone();
@@ -189,18 +200,14 @@ impl App {
self.items = self.client.items(&parent_id).await?; self.items = self.client.items(&parent_id).await?;
} }
self.current_title = Some(parent_name.clone()); self.current_title = Some(parent_name.clone());
self.list_state self.list_state.select(restore_selection(restore, self.items.len()));
.select(if self.items.is_empty() { None } else { Some(0) });
self.status = format!("{}: {} items", parent_name, self.items.len()); self.status = format!("{}: {} items", parent_name, self.items.len());
} else { } else {
self.view = View::Libraries; self.view = View::Libraries;
self.current_title = None; self.current_title = None;
self.items.clear(); self.items.clear();
self.list_state.select(if self.home_entries.is_empty() { self.list_state
None .select(restore_selection(restore, self.home_entries.len()));
} else {
Some(0)
});
self.status = format!( self.status = format!(
"{} libraries · {} in progress", "{} libraries · {} in progress",
self.libraries.len(), self.libraries.len(),
@@ -245,6 +252,15 @@ impl App {
} }
} }
/// Clamp the remembered selection to the current list size. Returns None if
/// the list is empty (so no row is highlighted).
fn restore_selection(saved: Option<usize>, len: usize) -> Option<usize> {
if len == 0 {
return None;
}
Some(saved.unwrap_or(0).min(len - 1))
}
fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec<Item> { fn build_home_entries(libraries: &[Item], resume: &[Item]) -> Vec<Item> {
let mut entries: Vec<Item> = Vec::with_capacity(libraries.len() + 1); let mut entries: Vec<Item> = Vec::with_capacity(libraries.len() + 1);
if !resume.is_empty() { if !resume.is_empty() {