rust_i18n::i18n!("../locales", fallback = "en");
use crate::client::RustyClient;
use crate::config::{AppTheme, Config, LogLevel};
use crate::context::AppContext;
use crate::gui::icon;
use crate::model::{Alarm, CalendarListEntry, Task as TodoTask};
use crate::store::TaskStore;
use crate::system::SystemEvent;
use iced::widget::text_editor;
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use strum::IntoEnumIterator;
use tokio::sync::mpsc;
#[derive(PartialEq, Clone, Copy, Debug, Default)]
pub enum AppState {
#[default]
Loading,
Onboarding,
Active,
Settings,
Help(crate::help::HelpTab, u8),
}
#[derive(Default, PartialEq, Clone, Copy, Debug)]
pub enum SidebarMode {
#[default]
Calendars,
Categories,
Locations, }
#[derive(Debug, Clone, Copy)]
pub enum ResizeDirection {
North,
South,
East,
West,
NorthEast,
NorthWest,
SouthEast,
SouthWest,
}
pub struct GuiApp {
pub core_config: Config,
pub state: AppState,
pub ctx: Arc<dyn AppContext>,
pub store: TaskStore,
pub controller: crate::controller::TaskController,
pub session: crate::model::SessionState,
pub tasks: Vec<crate::store::TaskListItem>,
pub calendars: Vec<CalendarListEntry>,
pub client: Option<RustyClient>,
pub tag_aliases: HashMap<String, Vec<String>>,
pub bg_tx: Option<tokio::sync::mpsc::Sender<crate::gui::async_ops::WorkerCommand>>,
pub cached_categories: Vec<(String, usize)>,
pub cached_locations: Vec<(String, usize)>,
pub task_ids: HashMap<String, iced::widget::Id>,
pub sidebar_mode: SidebarMode,
pub active_cal_href: Option<String>,
pub hidden_calendars: HashSet<String>,
pub disabled_calendars: HashSet<String>,
pub yanked_uid: Option<String>,
pub yank_lock_active: bool,
pub hovered_tag_uid: Option<String>,
pub selected_uid: Option<String>,
pub hide_completed: bool,
pub strikethrough_completed: bool,
pub hide_fully_completed_tags: bool,
pub sort_cutoff_months: Option<u32>,
pub current_theme: AppTheme,
pub resolved_random_theme: AppTheme,
pub filter_min_duration: Option<u32>,
pub filter_max_duration: Option<u32>,
pub filter_include_unset_duration: bool,
pub quick_filter_term: String,
pub quick_filter_icon: String,
pub show_quick_filter: bool,
pub sidebar_is_hidden: bool,
pub ob_quick_filter_term_input: String,
pub ob_quick_filter_icon_input: String,
pub input_value: text_editor::Content,
pub description_value: text_editor::Content,
pub search_value: text_editor::Content,
pub search_debounce_version: usize,
pub editing_uid: Option<String>,
pub creating_child_of: Option<String>,
pub moving_task_uid: Option<String>,
pub child_lock_active: bool,
pub creating_with_desc: bool,
pub new_task_title: String,
pub expanded_tasks: HashSet<String>,
pub help_expanded_sections: HashSet<String>,
pub unsynced_changes: bool,
pub unsynced_tooltip: String,
pub last_sync_failed: bool,
pub adding_session_uid: Option<String>,
pub session_input: iced::widget::text_editor::Content,
pub show_all_sessions: HashSet<String>,
pub current_placeholder: String,
pub search_placeholder: String,
pub notes_placeholder: String,
pub location_tab_icon: char,
pub random_icon: char,
pub alias_input_key: String,
pub alias_input_values: String,
pub ob_trash_retention_input: String,
pub trash_retention_days: u32,
pub loading: bool,
pub error_msg: Option<String>,
pub ob_url: String,
pub ob_user: String,
pub ob_pass: String,
pub ob_default_cal: Option<String>,
pub ob_sort_months_input: String,
pub ob_insecure: bool,
pub config_was_corrupted: bool,
pub local_cals_editing: Vec<CalendarListEntry>,
pub remote_cals_editing: Vec<CalendarListEntry>,
pub color_picker_active_href: Option<String>,
pub temp_color: iced::Color,
pub scrollable_id: iced::widget::Id,
pub sidebar_scrollable_id: iced::widget::Id,
pub resize_direction: Option<ResizeDirection>,
pub current_window_size: iced::Size,
pub cursor_position: iced::Point,
pub ob_urgent_days_input: String,
pub ob_urgent_prio_input: String,
pub ob_default_priority_input: String,
pub ob_start_grace_input: String,
pub urgent_days: u32,
pub urgent_prio: u8,
pub default_priority: u8,
pub start_grace_period_days: u32,
pub alarm_tx: Option<mpsc::Sender<SystemEvent>>, pub ringing_tasks: Vec<(TodoTask, Alarm)>,
pub snooze_custom_input: String,
pub ics_import_dialog_open: bool,
pub ics_import_file_path: Option<String>,
pub ics_import_content: Option<String>,
pub ics_import_selected_calendar: Option<String>,
pub ics_import_task_count: Option<usize>,
pub last_click: Option<(std::time::Instant, String)>,
pub pinned_actions: Vec<crate::config::TaskAction>,
pub active_context_menu: Option<(String, bool, iced::Point)>,
pub language: Option<String>,
pub auto_reminders: bool,
pub default_reminder_time: String,
pub snooze_short_mins: u32,
pub snooze_long_mins: u32,
pub create_events_for_tasks: bool,
pub delete_events_on_completion: bool,
pub deleting_events: bool,
pub ob_snooze_short_input: String,
pub ob_snooze_long_input: String,
pub ob_auto_refresh_input: String,
pub show_advanced_settings: bool,
pub ob_max_done_roots_input: String,
pub ob_max_done_subtasks_input: String,
pub show_priority_numbers: bool,
pub log_level: LogLevel,
pub force_ssd: bool,
pub auto_refresh_interval_mins: u32,
pub ui_scale: f32,
}
impl GuiApp {
pub fn sort_calendars(&mut self) {
self.calendars.sort_by_key(|c| {
if c.href == "local://recovery" {
1
} else if c.href == crate::storage::LOCAL_TRASH_HREF {
2
} else {
0
}
});
}
pub fn get_filtered_calendars(&self) -> Vec<&CalendarListEntry> {
self.calendars
.iter()
.filter(|c| !self.disabled_calendars.contains(&c.href))
.filter(|c| {
if c.href == crate::storage::LOCAL_TRASH_HREF || c.href == "local://recovery" {
self.store
.calendars
.get(&c.href)
.is_some_and(|map| !map.is_empty())
} else {
true
}
})
.collect()
}
pub fn get_task_at_index(&self, idx: usize) -> Option<&TodoTask> {
match self.tasks.get(idx) {
Some(crate::store::TaskListItem::Task(t)) => Some(t),
_ => None,
}
}
pub fn find_task_index_by_uid(&self, uid: &str) -> Option<usize> {
self.tasks.iter().position(|item| {
if let crate::store::TaskListItem::Task(t) = item {
t.uid == uid
} else {
false
}
})
}
}
impl Default for GuiApp {
fn default() -> Self {
let loc_icons = [
icon::LOCATION,
icon::EARTH_ASIA,
icon::EARTH_AMERICAS,
icon::EARTH_AFRICA,
icon::EARTH_GENERIC,
icon::PLANET,
icon::GALAXY,
icon::ISLAND,
icon::COMPASS,
icon::MOUNTAINS,
icon::GLOBE,
icon::GLOBEMODEL,
icon::MOON,
];
let mut rng = fastrand::Rng::new();
let location_tab_icon = loc_icons[rng.usize(..loc_icons.len())];
let random_icon =
crate::gui::icon::RANDOM_ICONS[rng.usize(..crate::gui::icon::RANDOM_ICONS.len())];
let themes: Vec<AppTheme> = AppTheme::iter()
.filter(|&t| t != AppTheme::Random)
.collect();
let resolved_random_theme = if !themes.is_empty() {
themes[rng.usize(..themes.len())]
} else {
AppTheme::RustyDark
};
let ctx = Arc::new(crate::context::StandardContext::new(None));
let store = TaskStore::new(ctx.clone());
let client = Arc::new(tokio::sync::Mutex::new(None));
let controller = crate::controller::TaskController::new(
Arc::new(tokio::sync::Mutex::new(store.clone())),
client,
ctx.clone(),
);
Self {
core_config: Config::default(),
ctx: ctx.clone(),
state: AppState::Loading,
store,
controller,
session: crate::model::SessionState::default(),
tasks: vec![],
calendars: vec![],
client: None,
tag_aliases: HashMap::new(),
bg_tx: None,
cached_categories: Vec::new(),
cached_locations: Vec::new(),
task_ids: HashMap::new(),
sidebar_mode: SidebarMode::Calendars,
active_cal_href: None,
hidden_calendars: HashSet::new(),
disabled_calendars: HashSet::new(),
yanked_uid: None,
yank_lock_active: false,
selected_uid: None,
hovered_tag_uid: None,
hide_completed: false,
hide_fully_completed_tags: true,
sort_cutoff_months: Some(2),
ob_sort_months_input: "2".to_string(),
current_theme: AppTheme::default(),
resolved_random_theme,
filter_min_duration: None,
filter_max_duration: None,
filter_include_unset_duration: true,
quick_filter_term: "is:ready".to_string(),
quick_filter_icon: "f0fa9".to_string(),
show_quick_filter: true,
sidebar_is_hidden: false,
ob_quick_filter_term_input: "is:ready".to_string(),
ob_quick_filter_icon_input: "f0fa9".to_string(),
input_value: text_editor::Content::new(),
description_value: text_editor::Content::new(),
search_value: text_editor::Content::new(),
search_debounce_version: 0,
editing_uid: None,
creating_child_of: None,
moving_task_uid: None,
child_lock_active: false,
creating_with_desc: false,
new_task_title: String::new(),
expanded_tasks: HashSet::new(),
help_expanded_sections: HashSet::new(),
unsynced_changes: false,
unsynced_tooltip: String::new(),
last_sync_failed: false,
adding_session_uid: None,
session_input: iced::widget::text_editor::Content::new(),
show_all_sessions: HashSet::new(),
current_placeholder: rust_i18n::t!("new_task_prompt").to_string(),
search_placeholder: rust_i18n::t!("search_placeholder").to_string(),
notes_placeholder: rust_i18n::t!("notes_placeholder").to_string(),
location_tab_icon,
random_icon,
alias_input_key: String::new(),
alias_input_values: String::new(),
ob_trash_retention_input: "14".to_string(),
trash_retention_days: 14,
loading: true,
error_msg: None,
ob_url: String::new(),
ob_user: String::new(),
ob_pass: String::new(),
ob_default_cal: None,
ob_insecure: false,
config_was_corrupted: false,
local_cals_editing: vec![],
remote_cals_editing: vec![],
color_picker_active_href: None,
temp_color: iced::Color::WHITE,
scrollable_id: iced::widget::Id::unique(),
sidebar_scrollable_id: iced::widget::Id::unique(),
resize_direction: None,
current_window_size: iced::Size::new(800.0, 600.0),
cursor_position: iced::Point::default(),
ob_urgent_days_input: "1".to_string(),
ob_urgent_prio_input: "1".to_string(),
ob_default_priority_input: "5".to_string(),
ob_start_grace_input: "1".to_string(),
urgent_days: 1,
urgent_prio: 1,
default_priority: 5,
start_grace_period_days: 1,
alarm_tx: None,
ringing_tasks: Vec::new(),
snooze_custom_input: String::new(),
language: None,
auto_reminders: true,
default_reminder_time: "09:00".to_string(),
snooze_short_mins: 60,
snooze_long_mins: 1440,
create_events_for_tasks: false,
delete_events_on_completion: false,
strikethrough_completed: false,
deleting_events: false,
ob_snooze_short_input: "1h".to_string(),
ob_snooze_long_input: "1d".to_string(),
ob_auto_refresh_input: "30m".to_string(),
show_advanced_settings: false,
ob_max_done_roots_input: "20".to_string(),
ob_max_done_subtasks_input: "5".to_string(),
show_priority_numbers: true,
log_level: LogLevel::Info,
force_ssd: {
#[cfg(target_os = "windows")]
{
let info = os_info::get();
matches!(info.os_type(), os_info::Type::Windows)
&& info.version().to_string().starts_with("10")
}
#[cfg(not(target_os = "windows"))]
{
false
}
},
auto_refresh_interval_mins: 30,
ui_scale: 1.0,
pinned_actions: crate::config::Config::default().pinned_actions,
active_context_menu: None,
last_click: None,
ics_import_dialog_open: false,
ics_import_file_path: None,
ics_import_content: None,
ics_import_selected_calendar: None,
ics_import_task_count: None,
}
}
}