use crate::gui::message::Message;
use crate::gui::state::{Focus, GuiApp, SidebarMode};
use crate::gui::subscription::ACTIVE_FOCUS;
use crate::gui::update::common;
use crate::model::AppIntent;
use chrono::NaiveTime;
use iced::Task;
use iced::widget::text_editor;
fn dispatch_and_maintain_selection(app: &mut GuiApp, intent: AppIntent, focus_uid: &str) {
let was_selected = app.selected_uid.as_deref() == Some(focus_uid);
let old_idx = app.find_task_index_by_uid(focus_uid);
common::dispatch_intent(app, intent);
if was_selected
&& let Some(idx) = old_idx
&& app.find_task_index_by_uid(focus_uid).is_none()
{
let new_idx = idx.min(app.tasks.len().saturating_sub(1));
let mut fallback = None;
for i in new_idx..app.tasks.len() {
if let Some(t) = app.get_task_at_index(i) {
fallback = Some(t.uid.clone());
break;
}
}
if fallback.is_none() {
for i in (0..new_idx).rev() {
if let Some(t) = app.get_task_at_index(i) {
fallback = Some(t.uid.clone());
break;
}
}
}
if fallback.is_some() {
app.selected_uid = fallback;
}
}
}
fn dispatch_and_select_next_row(app: &mut GuiApp, intent: AppIntent, uid: String) {
let was_selected = app.selected_uid.as_ref() == Some(&uid);
let next_uid = if was_selected {
app.find_task_index_by_uid(&uid)
.and_then(|idx| {
app.get_task_at_index(idx + 1).or_else(|| {
if idx > 0 {
app.get_task_at_index(idx - 1)
} else {
None
}
})
})
.map(|task| task.uid.clone())
} else {
None
};
dispatch_and_maintain_selection(app, intent, &uid);
if was_selected
&& let Some(next_uid) = next_uid
&& app.find_task_index_by_uid(&next_uid).is_some()
{
app.selected_uid = Some(next_uid);
}
}
pub fn handle(app: &mut GuiApp, message: Message) -> Task<Message> {
match message {
Message::InputChanged(action) => {
app.active_focus = Focus::AddTaskInput;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::AddTaskInput;
}
if let text_editor::Action::Edit(text_editor::Edit::Enter) = action {
return handle_submit(app);
}
if let text_editor::Action::Edit(text_editor::Edit::Insert('\t')) = action {
return Task::none();
}
app.input_value.perform(action);
Task::none()
}
Message::DescriptionChanged(action) => {
app.description_value.perform(action);
Task::none()
}
Message::StartCreateChild(parent_uid) => {
app.creating_child_of = Some(parent_uid.clone());
app.selected_uid = Some(parent_uid.clone());
app.creating_with_desc = false;
let mut initial_input = String::new();
if let Some(parent) = app.store.get_task_ref(&parent_uid) {
for cat in &parent.categories {
initial_input
.push_str(&format!("#{} ", crate::model::parser::quote_value(cat)));
}
if let Some(loc) = &parent.location {
initial_input
.push_str(&format!("@@{} ", crate::model::parser::quote_value(loc)));
}
}
app.input_value = text_editor::Content::with_text(&initial_input);
app.input_value
.perform(text_editor::Action::Move(text_editor::Motion::DocumentEnd));
app.active_focus = Focus::AddTaskInput;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::AddTaskInput;
}
iced::widget::operation::focus(iced::widget::Id::new("main_input"))
}
Message::StartCreateWithDescription => {
app.creating_with_desc = true;
app.active_focus = Focus::AddTaskInput;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::AddTaskInput;
}
if app.input_value.text().trim().is_empty() {
iced::widget::operation::focus(iced::widget::Id::new("main_input"))
} else {
iced::widget::operation::focus(iced::widget::Id::new("description_input"))
}
}
Message::SubmitTask => handle_submit(app),
Message::EditTaskStart(index) => {
let data = app
.get_task_at_index(index)
.map(|t| (t.uid.clone(), t.to_smart_string(), t.description.clone()));
if let Some((task_uid, task_summary, task_description)) = data {
app.input_value = text_editor::Content::with_text(&task_summary);
app.input_value
.perform(text_editor::Action::Move(text_editor::Motion::DocumentEnd));
app.description_value = text_editor::Content::with_text(&task_description);
app.editing_uid = Some(task_uid.clone());
app.selected_uid = Some(task_uid);
app.active_focus = Focus::AddTaskInput;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::AddTaskInput;
}
return iced::widget::operation::focus(iced::widget::Id::new("main_input"));
}
Task::none()
}
Message::CancelEdit => {
app.input_value = text_editor::Content::new();
app.description_value = text_editor::Content::new();
app.editing_uid = None;
app.creating_child_of = None;
app.child_lock_active = false;
app.creating_with_desc = false;
common::scroll_to_selected(app, true)
}
Message::ToggleTaskShift(uid) => {
dispatch_and_select_next_row(app, AppIntent::ToggleTaskShift { uid: uid.clone() }, uid);
Task::none()
}
Message::ToggleTaskShiftSelected => {
if let Some(uid) = app.selected_uid.clone() {
dispatch_and_select_next_row(
app,
AppIntent::ToggleTaskShift { uid: uid.clone() },
uid,
);
}
Task::none()
}
Message::ToggleTask(index, _) => {
let data = app.get_task_at_index(index).map(|t| {
(
t.uid.clone(),
t.etag == "pending_refresh",
t.status.is_done(),
)
});
if let Some((uid, is_pending, is_done)) = data {
if is_pending {
return Task::none();
}
if is_done {
dispatch_and_maintain_selection(
app,
AppIntent::ToggleTask { uid: uid.clone() },
&uid,
);
} else {
dispatch_and_select_next_row(
app,
AppIntent::ToggleTask { uid: uid.clone() },
uid,
);
}
}
Task::none()
}
Message::ToggleDoneGroup(key) => {
common::dispatch_intent(app, crate::model::AppIntent::ToggleDoneGroup { key });
common::save_config(app);
Task::none()
}
Message::ToggleTreeCollapse(uid) => {
common::dispatch_intent(app, AppIntent::ToggleTreeCollapse { uid });
Task::none()
}
Message::ToggleHelpSection(title) => {
if app.help_expanded_sections.contains(&title) {
app.help_expanded_sections.remove(&title);
} else {
app.help_expanded_sections.insert(title);
}
Task::none()
}
Message::DeleteTask(index) => {
if let Some(uid) = app.get_task_at_index(index).map(|t| t.uid.clone()) {
app.selected_uid = Some(uid.clone());
dispatch_and_maintain_selection(
app,
AppIntent::DeleteTask { uid: uid.clone() },
&uid,
);
}
Task::none()
}
Message::EditSelectedDescription => {
if let Some(uid) = &app.selected_uid
&& let Some(idx) = app.find_task_index_by_uid(uid)
{
return Task::batch(vec![
handle(app, Message::EditTaskStart(idx)),
iced::widget::operation::focus(iced::widget::Id::new("description_input")),
]);
}
Task::none()
}
Message::PromoteSelected => {
if let Some(uid) = app.selected_uid.clone() {
common::dispatch_intent(app, AppIntent::RemoveParent { uid });
}
Task::none()
}
Message::DemoteSelected => {
if let Some(uid) = app.selected_uid.clone()
&& let Some(idx) = app.find_task_index_by_uid(&uid)
&& idx > 0
{
let parent_candidate_uid = app.get_task_at_index(idx - 1).unwrap().uid.clone();
if parent_candidate_uid != uid {
app.yanked_uid = Some(parent_candidate_uid);
return handle(app, Message::MakeChild(uid));
}
}
Task::none()
}
Message::YankSelected => {
if let Some(uid) = app.selected_uid.clone() {
app.yanked_uid = Some(uid.clone());
let mut tasks = vec![common::scroll_to_selected(app, false)];
if let Some(idx) = app.find_task_index_by_uid(&uid)
&& let Some(t) = app.get_task_at_index(idx)
{
let text = if t.description.is_empty() {
t.to_smart_string()
} else {
format!("{}\n\n{}", t.to_smart_string(), t.description)
};
tasks.push(iced::clipboard::write(text));
}
return Task::batch(tasks);
}
Task::none()
}
Message::KeyboardLinkChild => {
if let Some(parent_uid) = app.yanked_uid.clone()
&& let Some(selected_uid) = app.selected_uid.clone()
&& parent_uid != selected_uid
{
dispatch_and_maintain_selection(
app,
AppIntent::MakeChild {
uid: selected_uid.clone(),
parent_uid,
},
&selected_uid,
);
}
Task::none()
}
Message::KeyboardCreateChild => {
if let Some(selected_uid) = app.selected_uid.clone() {
return handle(app, Message::StartCreateChild(selected_uid));
}
Task::none()
}
Message::KeyboardAddDependency => {
if let Some(blocker_uid) = app.yanked_uid.clone()
&& let Some(uid) = app.selected_uid.clone()
{
common::dispatch_intent(app, AppIntent::AddDependency { uid, blocker_uid });
}
Task::none()
}
Message::KeyboardAddRelation => {
if let Some(related_uid) = app.yanked_uid.clone()
&& let Some(uid) = app.selected_uid.clone()
{
common::dispatch_intent(app, AppIntent::AddRelatedTo { uid, related_uid });
}
Task::none()
}
Message::KeyboardOpenContextMenu => {
if let Some(uid) = app.selected_uid.clone() {
return crate::gui::update::view::handle(app, Message::OpenContextMenu(uid, true));
}
Task::none()
}
Message::KeyboardToggleDetails => {
if let Some(uid) = app.selected_uid.clone() {
return crate::gui::update::view::handle(app, Message::ToggleDetails(uid));
}
Task::none()
}
Message::KeyboardDuplicateTask => {
if let Some(selected) = app.selected_uid.clone() {
return handle(app, Message::DuplicateTask(selected));
}
Task::none()
}
Message::KeyboardToggleTreeCollapse => {
if app.active_focus == crate::gui::state::Focus::Sidebar {
match app.sidebar_mode {
SidebarMode::Calendars => {}
SidebarMode::Categories => {
let cats = &app.cached_categories;
if let Some(cat) = cats.get(app.sidebar_selection_idx)
&& cat.has_children
{
return crate::gui::update::view::handle(
app,
Message::ToggleTagCollapse(cat.full_key.clone()),
);
}
}
SidebarMode::Locations => {
let locs = &app.cached_locations;
if let Some(loc) = locs.get(app.sidebar_selection_idx)
&& loc.has_children
{
return crate::gui::update::view::handle(
app,
Message::ToggleLocationCollapse(loc.full_key.clone()),
);
}
}
}
return Task::none();
}
if let Some(uid) = app.selected_uid.clone() {
return handle(app, Message::ToggleTreeCollapse(uid));
}
Task::none()
}
Message::DuplicateTask(uid) => {
app.yanked_uid = None;
app.yank_lock_active = false;
common::dispatch_intent(app, AppIntent::DuplicateTaskTree { uid });
Task::none()
}
Message::KeyboardOpenLocations => {
if let Some(uid) = app.selected_uid.clone() {
let count = app.store.count_tree_locations(&uid);
if count > 1 {
return crate::gui::update::view::handle(app, Message::OpenLocations(uid));
} else if count == 1 {
return crate::gui::update::view::handle(app, Message::OpenCoordinates(uid));
}
}
Task::none()
}
Message::KeyboardOpenUrl => {
if let Some(uid) = app.selected_uid.clone()
&& let Some(task) = app.store.get_task_ref(&uid)
&& let Some(url) = &task.url
{
return crate::gui::update::view::handle(app, Message::OpenUrl(url.clone()));
}
Task::none()
}
Message::KeyboardDeleteTaskTree => {
if let Some(uid) = app.selected_uid.clone() {
return handle(app, Message::DeleteTaskTree(uid));
}
Task::none()
}
Message::DeleteTaskTree(uid) => {
app.yanked_uid = None;
app.yank_lock_active = false;
dispatch_and_maintain_selection(
app,
AppIntent::DeleteTaskTree { uid: uid.clone() },
&uid,
);
Task::none()
}
Message::ToggleActiveSelected => {
if let Some(uid) = app.selected_uid.clone()
&& let Some(idx) = app.find_task_index_by_uid(&uid)
&& let Some(t) = app.get_task_at_index(idx)
{
if t.status == crate::model::TaskStatus::InProcess {
common::dispatch_intent(app, AppIntent::PauseTask { uid });
} else {
common::dispatch_intent(app, AppIntent::StartTask { uid });
}
}
Task::none()
}
Message::StopSelected => {
if let Some(uid) = app.selected_uid.clone() {
common::dispatch_intent(app, AppIntent::StopTask { uid });
}
Task::none()
}
Message::CancelSelected => {
if let Some(uid) = app.selected_uid.clone() {
dispatch_and_select_next_row(app, AppIntent::CancelTask { uid: uid.clone() }, uid);
}
Task::none()
}
Message::ChangePrioritySelected(delta) => {
if let Some(uid) = app.selected_uid.clone() {
common::dispatch_intent(app, AppIntent::ChangePriority { uid, delta });
}
Task::none()
}
Message::ChangePriority(index, delta) => {
if let Some(uid) = app.get_task_at_index(index).map(|t| t.uid.clone()) {
app.selected_uid = Some(uid.clone());
common::dispatch_intent(app, AppIntent::ChangePriority { uid, delta });
}
Task::none()
}
Message::SetTaskStatus(index, new_status) => {
if let Some(uid) = app.get_task_at_index(index).map(|t| t.uid.clone()) {
app.selected_uid = Some(uid.clone());
if new_status == crate::model::TaskStatus::Cancelled {
dispatch_and_select_next_row(
app,
AppIntent::CancelTask { uid: uid.clone() },
uid,
);
} else if new_status.is_done() {
dispatch_and_select_next_row(
app,
AppIntent::ToggleTask { uid: uid.clone() },
uid,
);
}
}
Task::none()
}
Message::MoveTask(uid, target_href) => {
app.selected_uid = Some(uid.clone());
app.moving_task_uid = None;
dispatch_and_maintain_selection(
app,
AppIntent::MoveTask {
uid: uid.clone(),
target_href,
},
&uid,
);
Task::none()
}
Message::StartTask(uid) => {
common::dispatch_intent(app, AppIntent::StartTask { uid });
Task::none()
}
Message::PauseTask(uid) => {
common::dispatch_intent(app, AppIntent::PauseTask { uid });
Task::none()
}
Message::StopTask(uid) => {
common::dispatch_intent(app, AppIntent::StopTask { uid });
Task::none()
}
Message::YankTask(uid) => {
app.yanked_uid = Some(uid.clone());
app.selected_uid = Some(uid.clone());
let mut tasks = vec![common::scroll_to_selected(app, false)];
if let Some(idx) = app.find_task_index_by_uid(&uid)
&& let Some(t) = app.get_task_at_index(idx)
{
let text = if t.description.is_empty() {
t.to_smart_string()
} else {
format!("{}\n\n{}", t.to_smart_string(), t.description)
};
tasks.push(iced::clipboard::write(text));
}
Task::batch(tasks)
}
Message::TogglePin(uid) => {
common::dispatch_intent(app, AppIntent::TogglePin { uid });
Task::none()
}
Message::ExtractSubtasks(uid) => {
if let Some((parent, _)) = app.store.get_task_mut(&uid) {
let desc_text = parent.description.clone();
let (clean_desc, extracted) =
crate::model::extractor::extract_markdown_tasks(&desc_text);
if !extracted.is_empty() {
parent.description = clean_desc;
parent.sequence += 1;
let parent_copy = parent.clone();
let target_href = parent_copy.calendar_href.clone();
let config = &app.core_config;
let def_time =
chrono::NaiveTime::parse_from_str(&config.default_reminder_time, "%H:%M")
.ok();
let mut actions = vec![crate::journal::Action::Update(parent_copy)];
for ext in extracted {
let mut sub =
crate::model::Task::new(&ext.raw_text, &app.tag_aliases, def_time);
sub.uid = ext.uid;
if !ext.description.is_empty() {
if sub.description.is_empty() {
sub.description = ext.description;
} else {
sub.description
.push_str(&format!("\n\n{}", ext.description));
}
}
if ext.is_completed {
sub.status = crate::model::TaskStatus::Completed;
sub.set_completion_date(Some(chrono::Utc::now()));
}
sub.parent_uid = Some(ext.parent_uid.unwrap_or(uid.clone()));
sub.dependencies = ext.dependencies;
sub.calendar_href = target_href.clone();
app.store.add_task(sub.clone());
actions.push(crate::journal::Action::Create(sub));
}
common::refresh_filtered_tasks(app);
if let Some(tx) = &app.bg_tx {
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(actions));
}
}
}
Task::none()
}
Message::ClearYank => {
app.yanked_uid = None;
app.yank_lock_active = false;
Task::none()
}
Message::EscCaptured => {
app.active_focus = Focus::MainList;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::MainList;
}
if app.editing_uid.is_some()
|| app.creating_child_of.is_some()
|| app.creating_with_desc
{
app.input_value = text_editor::Content::new();
app.description_value = text_editor::Content::new();
app.editing_uid = None;
app.creating_child_of = None;
app.creating_with_desc = false;
}
common::scroll_to_selected_delayed(app, false)
}
Message::EscapePressed => {
app.active_focus = Focus::MainList;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::MainList;
}
let mut needs_refresh = false;
let mut captured_action = false;
if app.moving_task_uid.is_some() {
app.moving_task_uid = None;
captured_action = true;
} else if app.ics_import_dialog_open {
app.ics_import_dialog_open = false;
app.ics_import_file_path = None;
app.ics_import_content = None;
app.ics_import_selected_calendar = None;
app.ics_import_task_count = None;
captured_action = true;
} else if app.editing_uid.is_some()
|| app.creating_child_of.is_some()
|| app.creating_with_desc
{
app.input_value = text_editor::Content::new();
app.description_value = text_editor::Content::new();
app.editing_uid = None;
app.creating_child_of = None;
app.child_lock_active = false;
app.creating_with_desc = false;
captured_action = true;
} else if app.yanked_uid.is_some() {
app.yanked_uid = None;
app.yank_lock_active = false;
captured_action = true;
} else if !app.input_value.text().is_empty() {
app.input_value = text_editor::Content::new();
captured_action = true;
} else if !app.search_value.text().is_empty() {
app.search_value = text_editor::Content::new();
needs_refresh = true;
captured_action = true;
} else if !app.session.selected_categories.is_empty() {
app.session.selected_categories.clear();
needs_refresh = true;
captured_action = true;
} else if !app.session.selected_locations.is_empty() {
app.session.selected_locations.clear();
needs_refresh = true;
captured_action = true;
}
if needs_refresh {
common::refresh_filtered_tasks(app);
}
if captured_action || app.editing_uid.is_none() {
return common::scroll_to_selected_delayed(app, true);
}
Task::none()
}
Message::MakeChild(target_uid) => {
if let Some(parent_uid) = app.yanked_uid.clone()
&& let Some(_orig) = app.store.get_task_ref(&target_uid)
{
if !app.yank_lock_active {
app.yanked_uid = None;
}
dispatch_and_maintain_selection(
app,
AppIntent::MakeChild {
uid: target_uid.clone(),
parent_uid,
},
&target_uid,
);
}
Task::none()
}
Message::RemoveParent(child_uid) => {
dispatch_and_maintain_selection(
app,
AppIntent::RemoveParent {
uid: child_uid.clone(),
},
&child_uid,
);
Task::none()
}
Message::RemoveDependency(uid, blocker_uid) => {
dispatch_and_maintain_selection(
app,
AppIntent::RemoveDependency {
uid: uid.clone(),
blocker_uid,
},
&uid,
);
Task::none()
}
Message::RemoveRelatedTo(uid, related_uid) => {
dispatch_and_maintain_selection(
app,
AppIntent::RemoveRelatedTo {
uid: uid.clone(),
related_uid,
},
&uid,
);
Task::none()
}
Message::AddDependency(target_uid) => {
if let Some(blocker_uid) = app.yanked_uid.clone() {
if !app.yank_lock_active {
app.yanked_uid = None;
}
dispatch_and_maintain_selection(
app,
AppIntent::AddDependency {
uid: target_uid.clone(),
blocker_uid,
},
&target_uid,
);
}
Task::none()
}
Message::AddRelatedTo(target_uid) => {
if let Some(related_uid) = app.yanked_uid.clone() {
if !app.yank_lock_active {
app.yanked_uid = None;
}
dispatch_and_maintain_selection(
app,
AppIntent::AddRelatedTo {
uid: target_uid.clone(),
related_uid,
},
&target_uid,
);
}
Task::none()
}
Message::StartMoveTask(uid) => {
app.moving_task_uid = Some(uid);
app.move_target_idx = 0;
app.active_context_menu = None; Task::none()
}
Message::CancelMoveTask => {
app.moving_task_uid = None;
Task::none()
}
Message::MigrateLocalTo(source_href, target_href) => {
if let Some(local_map) = app.store.calendars.get(&source_href) {
let tasks_to_move: Vec<_> = local_map.values().cloned().collect();
if tasks_to_move.is_empty() {
return Task::none();
}
app.loading = true;
if let Some(client) = &app.client {
return Task::perform(
crate::gui::async_ops::async_migrate_wrapper(
client.clone(),
tasks_to_move,
target_href,
),
|res| Message::MigrationComplete(res.map_err(|e| e.to_string())),
);
} else {
app.error_msg = Some(rust_i18n::t!("error_cannot_export_offline").to_string());
app.loading = false;
}
}
Task::none()
}
Message::SnoozeCustomInput(val) => {
app.snooze_custom_input = val;
Task::none()
}
Message::SnoozeCustomSubmit(t_uid, a_uid) => {
app.ringing_tasks
.retain(|(t, a)| !(t.uid == t_uid && a.uid == a_uid));
let mins = if let Ok(n) = app.snooze_custom_input.parse::<u32>() {
n
} else {
crate::model::parser::parse_duration(&app.snooze_custom_input).unwrap_or(10)
};
app.snooze_custom_input.clear();
handle(app, Message::SnoozeAlarm(t_uid, a_uid, mins))
}
Message::CompleteTaskFromAlarm(t_uid, a_uid) => {
app.ringing_tasks
.retain(|(t, a)| !(t.uid == t_uid && a.uid == a_uid));
if let Some(idx) = app.find_task_index_by_uid(&t_uid)
&& let Some(task) = app.get_task_at_index(idx)
&& !task.status.is_done()
{
if task.etag == "pending_refresh" {
return Task::none();
}
dispatch_and_select_next_row(
app,
AppIntent::ToggleTask { uid: t_uid.clone() },
t_uid.clone(),
);
}
Task::none()
}
Message::CancelTaskFromAlarm(t_uid, a_uid) => {
app.ringing_tasks
.retain(|(t, a)| !(t.uid == t_uid && a.uid == a_uid));
if app.find_task_index_by_uid(&t_uid).is_some() {
dispatch_and_select_next_row(
app,
AppIntent::CancelTask { uid: t_uid.clone() },
t_uid.clone(),
);
}
Task::none()
}
Message::SnoozeAlarm(t_uid, a_uid, mins) => {
if let Some((task, _)) = app.store.get_task_mut(&t_uid)
&& task.handle_snooze(&a_uid, mins)
{
task.sequence += 1;
let cloned = task.clone();
common::refresh_filtered_tasks(app);
if let Some(tx) = &app.bg_tx {
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(vec![
crate::journal::Action::Update(cloned),
]));
}
}
Task::none()
}
Message::DismissAlarm(t_uid, a_uid) => {
if let Some((task, _)) = app.store.get_task_mut(&t_uid)
&& task.handle_dismiss(&a_uid)
{
task.sequence += 1;
let cloned = task.clone();
common::refresh_filtered_tasks(app);
if let Some(tx) = &app.bg_tx {
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(vec![
crate::journal::Action::Update(cloned),
]));
}
}
Task::none()
}
Message::StartAddSession(uid) => {
app.adding_session_uid = Some(uid.clone());
app.session_input = iced::widget::text_editor::Content::new();
app.expanded_tasks.insert(uid.clone());
app.active_focus = Focus::AddTaskInput;
if let Ok(mut focus) = ACTIVE_FOCUS.write() {
*focus = Focus::AddTaskInput;
}
iced::widget::operation::focus(iced::widget::Id::from(format!("session_input_{}", uid)))
}
Message::SessionInputChanged(action) => {
if let iced::widget::text_editor::Action::Edit(iced::widget::text_editor::Edit::Enter) =
action
{
return handle(app, Message::SubmitSession);
}
if let iced::widget::text_editor::Action::Edit(
iced::widget::text_editor::Edit::Insert('\t'),
) = action
{
return Task::none();
}
app.session_input.perform(action);
Task::none()
}
Message::CancelAddSession => {
app.adding_session_uid = None;
app.session_input = iced::widget::text_editor::Content::new();
Task::none()
}
Message::SubmitSession => {
if let Some(uid) = app.adding_session_uid.clone() {
let input_text = app.session_input.text();
if let Some(session) = crate::model::parser::parse_session_input(&input_text)
&& let Some((t_mut, _)) = app.store.get_task_mut(&uid)
{
t_mut.add_session(session);
t_mut.sequence += 1;
let cloned = t_mut.clone();
app.adding_session_uid = None;
app.session_input = iced::widget::text_editor::Content::new();
common::refresh_filtered_tasks(app);
if let Some(tx) = &app.bg_tx {
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(vec![
crate::journal::Action::Update(cloned),
]));
}
}
}
Task::none()
}
Message::DeleteSession(uid, idx) => {
if let Some((t_mut, _)) = app.store.get_task_mut(&uid) {
t_mut.remove_session(idx);
t_mut.sequence += 1;
let cloned = t_mut.clone();
common::refresh_filtered_tasks(app);
if let Some(tx) = &app.bg_tx {
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(vec![
crate::journal::Action::Update(cloned),
]));
}
}
Task::none()
}
Message::ToggleShowAllSessions(uid) => {
app.expanded_tasks.insert(uid.clone());
if app.show_all_sessions.contains(&uid) {
app.show_all_sessions.remove(&uid);
} else {
app.show_all_sessions.insert(uid);
}
Task::none()
}
Message::KeyboardAddSession => {
if let Some(selected_uid) = app.selected_uid.clone() {
return handle(app, Message::StartAddSession(selected_uid));
}
Task::none()
}
Message::KeyboardToggleSessions => {
if let Some(selected_uid) = app.selected_uid.clone() {
return handle(app, Message::ToggleDetails(selected_uid));
}
Task::none()
}
_ => Task::none(),
}
}
fn handle_submit(app: &mut GuiApp) -> Task<Message> {
use crate::gui::update::common::{
apply_alias_retroactively, refresh_filtered_tasks, save_config,
};
use crate::model::{Task as TodoTask, extract_inline_aliases};
let raw_text = app.input_value.text();
let text_to_submit = raw_text.trim().to_string();
if text_to_submit.is_empty() {
app.input_value = text_editor::Content::new();
return Task::none();
}
let (clean_input, new_aliases) = extract_inline_aliases(&text_to_submit);
let mut retroactive_sync_batch = Vec::new();
if !new_aliases.is_empty() {
for (key, tags) in new_aliases {
app.tag_aliases.insert(key.clone(), tags.clone());
retroactive_sync_batch.extend(apply_alias_retroactively(app, &key, &tags));
}
save_config(app);
}
if clean_input.starts_with('#')
&& !clean_input.trim().contains(' ')
&& app.editing_uid.is_none()
{
let tag = clean_input.trim().trim_start_matches('#').to_string();
if !tag.is_empty() && !text_to_submit.contains(":=") {
app.sidebar_mode = SidebarMode::Categories;
app.session.selected_categories.clear();
app.session.selected_categories.push(tag);
app.input_value = text_editor::Content::new();
refresh_filtered_tasks(app);
if !retroactive_sync_batch.is_empty() {
let mut actions = Vec::new();
for t in retroactive_sync_batch {
actions.push(crate::journal::Action::Update(t));
}
if !actions.is_empty()
&& let Some(tx) = &app.bg_tx
{
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(actions));
}
}
return Task::none();
}
}
let is_loc_jump = clean_input.starts_with("@@") || clean_input.starts_with("loc:");
if is_loc_jump && !clean_input.trim().contains(' ') && app.editing_uid.is_none() {
let loc = crate::model::parser::strip_quotes(
clean_input
.trim_start_matches("@@")
.trim_start_matches("loc:"),
);
if !loc.is_empty() {
app.sidebar_mode = SidebarMode::Locations;
app.session.selected_locations.clear();
app.session.selected_locations.push(loc);
app.input_value = text_editor::Content::new();
refresh_filtered_tasks(app);
if !retroactive_sync_batch.is_empty() {
let mut actions = Vec::new();
for t in retroactive_sync_batch {
actions.push(crate::journal::Action::Update(t));
}
if !actions.is_empty()
&& let Some(tx) = &app.bg_tx
{
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(actions));
}
}
return Task::none();
}
}
let config_time = NaiveTime::parse_from_str(&app.default_reminder_time, "%H:%M").ok();
let desc_text = app.description_value.text();
let (cleaned_desc, extracted_subtasks) =
crate::model::extractor::extract_markdown_tasks(&desc_text);
if let Some(edit_uid) = &app.editing_uid {
if let Some((task, _)) = app.store.get_task_mut(edit_uid) {
task.description = desc_text;
task.apply_smart_input(&clean_input, &app.tag_aliases, config_time);
task.sequence += 1;
let task_copy = task.clone();
app.input_value = text_editor::Content::new();
app.description_value = text_editor::Content::new();
app.editing_uid = None;
app.selected_uid = Some(task_copy.uid.clone());
refresh_filtered_tasks(app);
let mut actions = Vec::new();
actions.push(crate::journal::Action::Update(task_copy));
for t in retroactive_sync_batch {
actions.push(crate::journal::Action::Update(t));
}
if !actions.is_empty()
&& let Some(tx) = &app.bg_tx
{
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(actions));
}
return Task::none();
}
} else if !clean_input.is_empty() {
let mut new_task = TodoTask::new(&clean_input, &app.tag_aliases, config_time);
if !cleaned_desc.is_empty() {
if new_task.description.is_empty() {
new_task.description = cleaned_desc;
} else {
new_task
.description
.push_str(&format!("\n\n{}", cleaned_desc));
}
}
if let Some(parent) = &app.creating_child_of {
new_task.parent_uid = Some(parent.clone());
if !app.child_lock_active {
app.creating_child_of = None;
}
}
let target_href = app
.active_cal_href
.clone()
.or_else(|| app.calendars.first().map(|c| c.href.clone()))
.unwrap_or_default();
if !target_href.is_empty() {
new_task.calendar_href = target_href.clone();
let parent_uid = new_task.uid.clone();
app.store.add_task(new_task.clone());
app.task_ids
.entry(new_task.uid.clone())
.or_insert_with(iced::widget::Id::unique);
let mut tasks_to_create = vec![new_task];
for ext in extracted_subtasks {
let mut sub = TodoTask::new(&ext.raw_text, &app.tag_aliases, config_time);
sub.uid = ext.uid;
if !ext.description.is_empty() {
if sub.description.is_empty() {
sub.description = ext.description;
} else {
sub.description
.push_str(&format!("\n\n{}", ext.description));
}
}
if ext.is_completed {
sub.status = crate::model::TaskStatus::Completed;
sub.set_completion_date(Some(chrono::Utc::now()));
}
let actual_parent = ext.parent_uid.unwrap_or(parent_uid.clone());
sub.parent_uid = Some(actual_parent);
sub.dependencies = ext.dependencies;
sub.calendar_href = target_href.clone();
app.store.add_task(sub.clone());
tasks_to_create.push(sub);
}
app.selected_uid = Some(parent_uid.clone());
refresh_filtered_tasks(app);
app.input_value = text_editor::Content::new();
app.description_value = text_editor::Content::new();
app.creating_with_desc = false;
let scroll_cmd = common::scroll_to_selected_delayed(app, false);
let focus_cmd = iced::widget::operation::focus(iced::widget::Id::new("main_input"));
let mut actions = Vec::new();
for t in tasks_to_create {
actions.push(crate::journal::Action::Create(t));
}
if !retroactive_sync_batch.is_empty() {
for t in retroactive_sync_batch {
actions.push(crate::journal::Action::Update(t));
}
}
if !actions.is_empty()
&& let Some(tx) = &app.bg_tx
{
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(actions));
}
return Task::batch(vec![scroll_cmd, focus_cmd]);
}
}
if !retroactive_sync_batch.is_empty() {
let mut actions = Vec::new();
for t in retroactive_sync_batch {
actions.push(crate::journal::Action::Update(t));
}
if !actions.is_empty()
&& let Some(tx) = &app.bg_tx
{
let _ = tx.try_send(crate::gui::async_ops::WorkerCommand::Batch(actions));
}
}
Task::none()
}