use super::*;
use crate::storage;
impl App {
pub fn pending_task_ids(&self) -> Vec<u64> {
storage::sorted_pending_tasks(&self.data)
.into_iter()
.map(|t| t.id)
.collect()
}
pub fn dashboard_selected_task_id(&self) -> Option<u64> {
let pending = self.dashboard_tasks();
if pending.is_empty() {
None
} else {
let idx = self.dashboard_task_selected.min(pending.len() - 1);
Some(pending[idx].id)
}
}
pub(crate) fn clamp_dashboard_task_selection(&mut self) {
let n = self.dashboard_tasks().len();
if n == 0 {
self.dashboard_task_selected = 0;
} else if self.dashboard_task_selected >= n {
self.dashboard_task_selected = n - 1;
}
}
pub(crate) fn clamp_task_selection_after_mutation(&mut self) {
let len = self.filtered_task_indices().len();
if len == 0 {
self.task_state.select(None);
} else {
let sel = self.task_state.selected().unwrap_or(0).min(len - 1);
self.task_state.select(Some(sel));
}
}
pub(crate) fn move_dashboard_task_selection(&mut self, delta: i32) {
let count = self.dashboard_tasks().len();
if count == 0 {
return;
}
let cur = self.dashboard_task_selected as i32;
let next = (cur + delta).rem_euclid(count as i32) as usize;
self.dashboard_task_selected = next;
}
pub fn pending_task_count(&self) -> u32 {
storage::pending_tasks(&self.data).count() as u32
}
pub fn active_task_pending_index(&self) -> Option<u32> {
let id = self.active_task?;
storage::sorted_pending_tasks(&self.data)
.iter()
.position(|t| t.id == id)
.map(|i| i as u32)
}
pub fn active_task_progress(&self) -> Option<f64> {
let id = self.active_task?;
let task = self.data.tasks.iter().find(|t| t.id == id)?;
Some(task.progress_ratio())
}
pub(crate) fn matches_filter(&self, t: &crate::model::Task) -> bool {
if !self.task_search.is_empty() {
let q = self.task_search.to_lowercase();
let title_match = t.title.to_lowercase().contains(&q);
let notes_match = t.notes.to_lowercase().contains(&q);
let tags_match = t.tags.iter().any(|tag| tag.to_lowercase().contains(&q));
if !title_match && !notes_match && !tags_match {
return false;
}
}
if let Some(ref tag) = self.active_tag_filter {
if !t.tags.iter().any(|t| t == tag) {
return false;
}
}
match self.task_filter {
TaskFilter::All => true,
TaskFilter::Pending => t.status != crate::model::TaskStatus::Done,
TaskFilter::Done => t.status == crate::model::TaskStatus::Done,
TaskFilter::Today => t.today && t.status != crate::model::TaskStatus::Done,
}
}
pub fn filtered_task_indices(&self) -> Vec<usize> {
self.data
.tasks
.iter()
.enumerate()
.filter(|(_, t)| self.matches_filter(t))
.map(|(i, _)| i)
.collect()
}
pub fn dashboard_tasks(&self) -> Vec<&crate::model::Task> {
let mut tasks: Vec<_> = self
.filtered_task_indices()
.into_iter()
.map(|i| &self.data.tasks[i])
.collect();
tasks.sort_by(|a, b| {
b.priority
.rank()
.cmp(&a.priority.rank())
.then(b.today.cmp(&a.today))
.then(a.sort_order.cmp(&b.sort_order))
});
tasks
}
pub fn set_active_task(&mut self, id: Option<u64>) {
if let Some(id) = id {
if self
.data
.tasks
.iter()
.find(|t| t.id == id)
.is_some_and(|t| t.status == crate::model::TaskStatus::Done)
{
self.set_status("That task is done — pick another.", true);
return;
}
self.persist_data(|db, data| storage::promote_task_on_activate(db, data, id));
}
self.active_task = id;
self.data.active_task_id = id;
self.persist(|db| db.persist_active_task(id));
}
pub fn cycle_active_task_status(&mut self) {
let Some(id) = self.active_task else {
self.set_status("No active task — set one on Tasks (Space).", true);
return;
};
self.persist_data(|db, data| storage::cycle_task_status(db, data, id));
let status = self
.data
.tasks
.iter()
.find(|t| t.id == id)
.map(|t| t.status);
let Some(status) = status else {
return;
};
if status == crate::model::TaskStatus::Done {
self.active_task = None;
self.data.active_task_id = None;
self.persist(|db| db.persist_active_task(None));
self.maybe_advance_task();
}
self.bump_data();
self.set_status(format!("Task status: {}", status.label()), false);
if status == crate::model::TaskStatus::Done {
self.check_queue_empty();
}
}
pub fn mark_active_task_done(&mut self) {
let Some(id) = self.active_task else {
self.set_status("No active task — set one on Tasks (Space).", true);
return;
};
self.persist_data(|db, data| storage::mark_task_done(db, data, id));
self.active_task = None;
self.data.active_task_id = None;
self.persist(|db| db.persist_active_task(None));
self.bump_data();
self.maybe_advance_task();
self.set_status("Task marked done.", false);
self.check_queue_empty();
}
pub(crate) fn auto_pick_task_if_needed(&mut self) {
if self.active_task.is_some() || !self.data.auto_pick_task {
return;
}
if let Some(id) = storage::pick_best_task(&self.data) {
self.set_active_task(Some(id));
}
}
pub(crate) fn maybe_advance_task(&mut self) {
if !self.data.auto_advance_task {
return;
}
let next = storage::advance_to_next_task(&self.data, self.active_task);
self.set_active_task(next);
if let Some(id) = next {
if let Some(t) = self.data.tasks.iter().find(|t| t.id == id) {
self.set_status(format!("Next task: {}", t.title), false);
}
}
}
pub fn start_focus_on_task(&mut self, id: u64) {
if self
.data
.tasks
.iter()
.find(|t| t.id == id)
.is_some_and(|t| t.status == crate::model::TaskStatus::Done)
{
self.set_status("That task is done — pick another.", true);
return;
}
self.set_active_task(Some(id));
self.tab = FocusTab::Dashboard;
if self.timer.mode != TimerMode::Focus {
self.timer.configure(TimerMode::Focus);
}
self.start_timer();
}
pub fn cycle_task_filter(&mut self) {
self.task_filter = self.task_filter.next();
self.task_state.select(Some(0));
self.set_status(format!("Filter: {}", self.task_filter.label()), false);
}
}