use crate::config::{AppTheme, LogLevel};
use crate::gui::icon;
use crate::gui::message::Message;
use crate::gui::state::{AppState, GuiApp};
use crate::storage::LOCAL_CALENDAR_HREF;
use iced::widget::{
MouseArea, Space, button, checkbox, column, container, row, scrollable, text, text_input,
tooltip,
};
use iced::{Color, Element, Length, Theme};
#[cfg(feature = "gui")]
use iced_aw::color_picker;
use strum::IntoEnumIterator;
#[derive(Debug, Clone, PartialEq, Eq)]
struct LangOption {
code: String,
label: String,
}
impl std::fmt::Display for LangOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.label)
}
}
fn get_native_language_name(code: &str) -> String {
let base_code = code.split(&['-', '_'][..]).next().unwrap_or(code);
if let Some(lang) = isolang::Language::from_639_1(base_code) {
let raw_name = lang.to_autonym().unwrap_or_else(|| lang.to_name());
let mut chars = raw_name.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
} else {
code.to_string() }
}
pub fn view_settings(app: &GuiApp) -> Element<'_, Message> {
let is_settings = matches!(app.state, AppState::Settings);
let title_text = text(if is_settings {
rust_i18n::t!("settings")
} else {
rust_i18n::t!("welcome_title")
})
.size(40);
let title_row = if is_settings {
row![
button(icon::icon(icon::ARROW_LEFT).size(24))
.style(button::text)
.on_press(Message::CancelSettings),
title_text,
Space::new().width(Length::Fill)
]
.spacing(20)
.align_y(iced::Alignment::Center)
} else {
row![title_text, Space::new().width(Length::Fill)]
};
let title_drag_area: Element<_> =
MouseArea::new(container(title_row).width(Length::Fill).padding(20))
.on_press(Message::WindowDragged)
.into();
let error = if let Some(e) = &app.error_msg {
text(e).color(Color::from_rgb(1.0, 0.0, 0.0))
} else if let Some(Some(warn)) = crate::system::KEYRING_WARNING.get() {
text(warn).color(Color::from_rgb(1.0, 0.6, 0.0))
} else {
text("")
};
if app.config_was_corrupted {
let error_text = app.error_msg.clone().unwrap_or_default();
return container(
column![
icon::icon(icon::TRASH)
.size(40)
.color(Color::from_rgb(0.8, 0.2, 0.2)),
text(rust_i18n::t!("config_error_title")).size(24),
text(rust_i18n::t!("config_error_corrupted")).size(16),
container(
text(error_text)
.size(14)
.font(iced::Font::MONOSPACE)
.color(Color::from_rgb(0.8, 0.1, 0.1))
)
.padding(10)
.style(container::rounded_box),
text(rust_i18n::t!("config_error_fix_remove")),
button(text(rust_i18n::t!("quit_application")))
.style(button::danger)
.on_press(Message::CloseWindow)
]
.spacing(20)
.align_x(iced::Alignment::Center),
)
.width(Length::Fill)
.height(Length::Fill)
.center_x(Length::Fill)
.center_y(Length::Fill)
.into();
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct CalOption {
name: String,
href: String,
}
impl std::fmt::Display for CalOption {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
}
let cal_options: Vec<CalOption> = app
.calendars
.iter()
.filter(|c| c.href != crate::storage::LOCAL_TRASH_HREF && c.href != "local://recovery")
.map(|c| CalOption {
name: c.name.clone(),
href: c.href.clone(),
})
.collect();
let current_cal_opt = cal_options
.iter()
.find(|o| {
Some(&o.href) == app.ob_default_cal.as_ref()
|| Some(&o.name) == app.ob_default_cal.as_ref()
})
.cloned();
let picker: Element<_> = if !cal_options.is_empty() && is_settings {
column![
text(rust_i18n::t!("default_collection")),
iced::widget::pick_list(cal_options, current_cal_opt, |opt| {
Message::ObDefaultCalChanged(opt.href)
})
.width(Length::Fill)
.padding(10)
]
.spacing(5)
.into()
} else {
Space::new().width(0).into()
};
let lang_picker: Element<_> = if is_settings {
let mut lang_options = vec![LangOption {
code: "auto".to_string(),
label: rust_i18n::t!("language_system").to_string(),
}];
let mut available = rust_i18n::available_locales!().to_vec();
available.sort();
for loc in available {
lang_options.push(LangOption {
code: loc.to_string(),
label: get_native_language_name(&loc),
});
}
let current_lang_code = app.language.clone().unwrap_or_else(|| "auto".to_string());
let current_lang_opt = lang_options
.iter()
.find(|o| o.code == current_lang_code)
.cloned()
.unwrap_or_else(|| lang_options[0].clone());
let lang_picker_row = row![
text(rust_i18n::t!("language_select")),
iced::widget::pick_list(lang_options, Some(current_lang_opt), |opt| {
Message::SetLanguage(opt.code)
})
]
.spacing(10)
.align_y(iced::Alignment::Center);
column![
lang_picker_row,
button(
text(rust_i18n::t!(
"translation_help",
url = "translate.codeberg.org"
))
.size(12)
.style(|theme: &Theme| text::Style {
color: Some(theme.extended_palette().primary.base.color)
})
)
.style(button::text)
.padding(0)
.on_press(Message::OpenUrl(
"https://translate.codeberg.org/projects/cfait/".to_string()
))
]
.spacing(5)
.into()
} else {
Space::new().width(0).into()
};
let theme_picker: Element<_> = if is_settings {
container(
column![
row![
text(rust_i18n::t!("app_theme")),
iced::widget::pick_list(
AppTheme::iter().collect::<Vec<_>>(),
Some(app.current_theme),
Message::ThemeChanged
)
]
.spacing(10)
.align_y(iced::Alignment::Center),
]
.spacing(10),
)
.into()
} else {
Space::new().width(0).into()
};
let notifications_ui: Element<_> = if is_settings {
column![
text(rust_i18n::t!("notifications_and_reminders")).size(20),
checkbox::<Message, iced::Theme, iced::Renderer>(app.auto_reminders)
.label(rust_i18n::t!("auto_remind_on_due_start_label"))
.on_toggle(Message::SetAutoReminders),
row![
text(rust_i18n::t!("default_time_label")).width(Length::Fixed(200.0)),
text_input("09:00", &app.default_reminder_time)
.on_input(Message::SetDefaultReminderTime)
.width(Length::Fixed(80.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
text(rust_i18n::t!("snooze_presets")).size(14),
row![
text(rust_i18n::t!("short_label")),
text_input("1h", &app.ob_snooze_short_input)
.on_input(Message::SetSnoozeShort)
.width(Length::Fixed(60.0))
.padding(5),
text(rust_i18n::t!("long_label")),
text_input("1d", &app.ob_snooze_long_input)
.on_input(Message::SetSnoozeLong)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
row![
text(rust_i18n::t!("sync_interval_label")).width(Length::Fixed(200.0)),
text_input("30m", &app.ob_auto_refresh_input)
.on_input(Message::SetAutoRefreshInterval)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
text("").size(5),
text(rust_i18n::t!("calendar_integration")).size(20),
{
let cb =
checkbox::<Message, iced::Theme, iced::Renderer>(app.create_events_for_tasks)
.label(rust_i18n::t!("create_calendar_events_for_tasks_with_dates"));
if !app.deleting_events {
cb.on_toggle(Message::SetCreateEventsForTasks)
} else {
cb
}
},
text(rust_i18n::t!("create_calendar_events_note"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
text("").size(5),
{
let cb = checkbox::<Message, iced::Theme, iced::Renderer>(
app.delete_events_on_completion,
)
.label(rust_i18n::t!("delete_calendar_events_on_completion_label"));
if !app.deleting_events {
cb.on_toggle(Message::SetDeleteEventsOnCompletion)
} else {
cb
}
},
text(rust_i18n::t!("events_deleted_on_task_delete"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
{
let btn = button(text(rust_i18n::t!("delete_all_calendar_events")));
if !app.deleting_events {
btn.on_press(Message::DeleteAllCalendarEvents)
} else {
btn
}
},
if app.deleting_events {
text(rust_i18n::t!("export_debug_status_exporting"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6))
} else {
text(rust_i18n::t!("calendar_events_reversible_note"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6))
},
]
.spacing(10)
.into()
} else {
Space::new().width(0).into()
};
let advanced_ui: Element<_> = if is_settings {
let content = if app.show_advanced_settings {
let hide_fully_ui: Element<_> = if !app.hide_completed {
checkbox::<Message, iced::Theme, iced::Renderer>(app.hide_fully_completed_tags)
.label(rust_i18n::t!("hide_fully_completed_tags"))
.on_toggle(Message::ToggleHideFullyCompletedTags)
.into()
} else {
Space::new().width(0).into()
};
column![
text(rust_i18n::t!("sorting_and_visibility")).size(20),
checkbox::<Message, iced::Theme, iced::Renderer>(app.hide_completed)
.label(rust_i18n::t!("hide_completed_and_canceled_tasks"))
.on_toggle(Message::ToggleHideCompleted),
hide_fully_ui,
tooltip(
checkbox::<Message, iced::Theme, iced::Renderer>(app.hide_aliases_in_sidebar)
.label(rust_i18n::t!("hide_aliases_in_sidebar"))
.on_toggle(Message::ToggleHideAliasesInSidebar),
text(rust_i18n::t!("hide_aliases_in_sidebar_tooltip")).size(12),
tooltip::Position::Top
)
.style(crate::gui::view::tooltip_style)
.delay(std::time::Duration::from_millis(700)),
checkbox::<Message, iced::Theme, iced::Renderer>(app.show_priority_numbers)
.label(rust_i18n::t!("show_priority_numbers"))
.on_toggle(Message::SetShowPriorityNumbers),
checkbox::<Message, iced::Theme, iced::Renderer>(app.strikethrough_completed)
.label(rust_i18n::t!("strikethrough_completed"))
.on_toggle(Message::SetStrikethroughCompleted),
checkbox::<Message, iced::Theme, iced::Renderer>(app.sort_standard_by_priority)
.label(rust_i18n::t!("sort_standard_by_priority_label"))
.on_toggle(Message::ToggleSortStandardByPriority),
Space::new().height(10),
row![
text(rust_i18n::t!("sorting_preset_label")).width(Length::Fixed(200.0)),
iced::widget::pick_list(
crate::config::SortPreset::iter().collect::<Vec<_>>(),
Some(app.sort_preset),
Message::SetSortPreset
)
.width(Length::Fill)
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
Space::new().height(10),
text(rust_i18n::t!("priority_rules")).size(16),
row![
text(rust_i18n::t!("due_within_days")).width(Length::Fixed(150.0)),
text_input("1", &app.ob_urgent_days_input)
.on_input(Message::ObUrgentDaysChanged)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
row![
text(rust_i18n::t!("priority_le")).width(Length::Fixed(150.0)),
text_input("1", &app.ob_urgent_prio_input)
.on_input(Message::ObUrgentPrioChanged)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
row![
text(rust_i18n::t!("default_priority_label")).width(Length::Fixed(150.0)),
text_input("5", &app.ob_default_priority_input)
.on_input(Message::ObDefaultPriorityChanged)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
Space::new().height(10),
text(rust_i18n::t!("sorting_timeframes")).size(16),
row![
text(rust_i18n::t!("start_grace_days")).width(Length::Fixed(150.0)),
text_input("1", &app.ob_start_grace_input)
.on_input(Message::ObStartGraceChanged)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
row![
text(rust_i18n::t!("priority_cutoff_months")).width(Length::Fixed(150.0)),
text_input("6", &app.ob_sort_months_input)
.on_input(Message::ObSortMonthsChanged)
.width(Length::Fixed(100.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
Space::new().height(10),
text(rust_i18n::t!("display_limits")).size(16),
row![
text(rust_i18n::t!("max_completed_tasks_root")).width(Length::Fixed(200.0)),
text_input("20", &app.ob_max_done_roots_input)
.on_input(Message::SetMaxDoneRoots)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
text(rust_i18n::t!("max_completed_tasks_root_explain"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
Space::new().height(10),
row![
text(rust_i18n::t!("max_completed_subtasks")).width(Length::Fixed(200.0)),
text_input("5", &app.ob_max_done_subtasks_input)
.on_input(Message::SetMaxDoneSubtasks)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
text(rust_i18n::t!("max_completed_subtasks_explain"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
Space::new().height(10),
text(rust_i18n::t!("pinned_actions_label")).size(16),
{
let mut action_col = column![].spacing(2);
for action in crate::config::TaskAction::ALL {
let is_pinned = app.pinned_actions.contains(action);
let action_val = *action;
let icon_char = match action_val {
crate::config::TaskAction::CompleteAndShift => icon::REPEAT,
crate::config::TaskAction::ToggleDetails => icon::INFO,
crate::config::TaskAction::ToggleTimer => icon::PLAY,
crate::config::TaskAction::StopTimer => icon::DEBUG_STOP,
crate::config::TaskAction::AddSession => icon::TIMER_PLUS,
crate::config::TaskAction::IncreasePriority => icon::PLUS,
crate::config::TaskAction::DecreasePriority => icon::MINUS,
crate::config::TaskAction::Cancel => icon::CROSS,
crate::config::TaskAction::Edit => icon::EDIT,
crate::config::TaskAction::Delete
| crate::config::TaskAction::DeleteTree => icon::TRASH,
crate::config::TaskAction::Yank => icon::LINK,
crate::config::TaskAction::ExtractSubtasks => {
icon::get_extract_subtasks_icon()
}
crate::config::TaskAction::TogglePin => icon::THUMB_TACK,
crate::config::TaskAction::CreateSubtask => icon::CREATE_CHILD,
crate::config::TaskAction::DuplicateTree => icon::CLONE,
crate::config::TaskAction::Promote => icon::ELEVATOR_UP,
crate::config::TaskAction::Move => icon::MOVE,
crate::config::TaskAction::OpenCoordinates => icon::MAP_LOCATION_DOT,
crate::config::TaskAction::OpenLocations => icon::MAP_MARKER_MULTIPLE,
crate::config::TaskAction::OpenUrl => icon::URL_CHECK,
};
let check_icon = if is_pinned {
icon::CHECK_SQUARE
} else {
icon::SQUARE
};
let toggle_row = row![
icon::icon(check_icon).size(16),
icon::icon(icon_char).size(14).width(Length::Fixed(20.0)),
text(action.label()).size(14)
]
.spacing(8)
.align_y(iced::Alignment::Center);
let row_btn = button(toggle_row)
.style(iced::widget::button::text)
.padding(4)
.width(Length::Fill)
.on_press(Message::TogglePinnedAction(action_val, !is_pinned));
action_col = action_col.push(row_btn);
}
action_col
},
Space::new().height(10),
text(rust_i18n::t!("data_management")).size(16),
row![
text(rust_i18n::t!("trash_retention_days_label")).width(Length::Fixed(200.0)),
text_input("14", &app.ob_trash_retention_input)
.on_input(Message::SetTrashRetention)
.width(Length::Fixed(60.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
text(rust_i18n::t!("trash_retention_explain"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
Space::new().height(10),
text(rust_i18n::t!("logging_label")).size(16),
row![
text(rust_i18n::t!("log_level_label")).width(Length::Fixed(200.0)),
iced::widget::pick_list(
LogLevel::ALL.to_vec(),
Some(app.log_level),
Message::SetLogLevel
)
.width(Length::Fixed(120.0))
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
text(rust_i18n::t!("log_level_explain"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
Space::new().height(10),
text(rust_i18n::t!("quick_filter_title")).size(16),
checkbox::<Message, iced::Theme, iced::Renderer>(app.show_quick_filter)
.label(rust_i18n::t!("quick_filter_show_button"))
.on_toggle(Message::SetShowQuickFilter),
row![
text(rust_i18n::t!("quick_filter_search_term")).width(Length::Fixed(150.0)),
text_input("is:ready", &app.ob_quick_filter_term_input)
.on_input(Message::SetQuickFilterTerm)
.width(Length::Fill)
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
row![
text(rust_i18n::t!("quick_filter_icon")).width(Length::Fixed(150.0)),
text_input("f0fa9", &app.ob_quick_filter_icon_input)
.on_input(Message::SetQuickFilterIcon)
.width(Length::Fill)
.padding(5)
]
.spacing(10)
.align_y(iced::Alignment::Center),
]
.spacing(5)
.padding(10)
} else {
column![]
};
column![
button(
row![
text(rust_i18n::t!("advanced_settings_button")).size(16),
Space::new().width(Length::Fill),
icon::icon(if app.show_advanced_settings {
icon::ARROW_EXPAND_UP
} else {
icon::ARROW_EXPAND_DOWN
})
.size(14)
]
.align_y(iced::Alignment::Center)
)
.width(Length::Fill)
.style(iced::widget::button::text)
.on_press(Message::ToggleAdvancedSettings(!app.show_advanced_settings)),
content
]
.spacing(5)
.into()
} else {
Space::new().width(0).into()
};
let aliases_ui: Element<_> = if is_settings {
let input_row = row![
text_input(&rust_i18n::t!("alias_key_label"), &app.alias_input_key)
.on_input(Message::AliasKeyInput)
.padding(5)
.width(Length::FillPortion(1)),
text_input(&rust_i18n::t!("alias_value_label"), &app.alias_input_values)
.on_input(Message::AliasValueInput)
.on_submit(Message::AddAlias)
.padding(5)
.width(Length::FillPortion(2)),
if app.editing_alias_key.is_some() {
row![
button(icon::icon(icon::CHECK).size(14))
.style(button::success)
.padding(6)
.on_press(Message::AddAlias),
button(icon::icon(icon::CROSS).size(14))
.style(button::danger)
.padding(6)
.on_press(Message::CancelEditAlias)
]
.spacing(5)
} else {
row![
button(text(rust_i18n::t!("add")))
.padding(5)
.on_press(Message::AddAlias)
]
}
]
.spacing(10)
.align_y(iced::Alignment::Center);
let mut list_col = column![
text(rust_i18n::t!("tag_aliases")).size(20),
input_row,
iced::widget::rule::horizontal(1)
]
.spacing(10);
let mut sorted_aliases: Vec<_> = app.tag_aliases.iter().collect();
sorted_aliases.sort_by_key(|(k, _)| k.to_string());
for (key, vals) in sorted_aliases {
let val_str = vals.join(", ");
let is_editing_this = app.editing_alias_key.as_ref() == Some(key);
let key_text = text(if key.starts_with("@@") {
key.to_string()
} else {
format!("#{}", key)
})
.width(Length::FillPortion(1))
.wrapping(iced::widget::text::Wrapping::Glyph)
.style(if is_editing_this {
|theme: &Theme| text::Style {
color: Some(theme.extended_palette().primary.base.color),
}
} else {
|_: &Theme| text::Style::default()
});
let val_text = text(val_str.clone())
.width(Length::FillPortion(2))
.wrapping(iced::widget::text::Wrapping::Glyph)
.style(if is_editing_this {
|theme: &Theme| text::Style {
color: Some(theme.extended_palette().primary.base.color),
}
} else {
|_: &Theme| text::Style::default()
});
let row_item = row![
key_text,
text("->").width(Length::Fixed(20.0)),
val_text,
button(icon::icon(icon::EDIT).size(12))
.style(button::secondary)
.padding(5)
.on_press(Message::EditAlias(key.clone(), val_str)),
button(icon::icon(icon::CROSS).size(12))
.style(button::danger)
.padding(5)
.on_press(Message::RemoveAlias(key.clone()))
]
.spacing(10)
.align_y(iced::Alignment::Center);
list_col = list_col.push(row_item);
}
let area = container(list_col).padding(10).style(|_| container::Style {
border: iced::Border {
radius: 4.0.into(),
width: 1.0,
color: Color::from_rgb(0.3, 0.3, 0.3),
},
..Default::default()
});
area.into()
} else {
Space::new().width(0).into()
};
let cal_mgmt_ui: Element<_> =
if is_settings && (!app.calendars.is_empty() || !app.remote_cals_editing.is_empty()) {
let mut col = column![text(rust_i18n::t!("remote_collections")).size(20)].spacing(10);
for cal in &app.remote_cals_editing {
let cal_href = cal.href.clone();
let is_enabled = !app.disabled_calendars.contains(&cal_href);
let checkbox_elem = {
let href = cal_href.clone();
checkbox::<Message, iced::Theme, iced::Renderer>(is_enabled)
.label("")
.on_toggle(move |v| Message::ToggleCalendarDisabled(href.clone(), !v))
};
let name_input = {
let href = cal_href.clone();
text_input(&rust_i18n::t!("name_label"), &cal.name)
.on_input(move |s| Message::RemoteCalendarNameChanged(href.clone(), s))
.padding(5)
.width(Length::Fill)
};
let original_cal = app.calendars.iter().find(|c| c.href == cal_href);
let has_changes = original_cal
.is_none_or(|orig| orig.name != cal.name || orig.color != cal.color);
let current_color = cal
.color
.as_ref()
.and_then(|h| crate::color_utils::parse_hex_to_floats(h))
.map(|(r, g, b)| Color::from_rgb(r, g, b))
.unwrap_or(Color::from_rgb(0.5, 0.5, 0.5));
let color_btn = {
let href = cal_href.clone();
button(
text(icon::PALETTE_COLOR.to_string())
.font(icon::FONT)
.size(16)
.color(current_color),
)
.padding(5)
.style(button::text)
.on_press(Message::OpenColorPicker(href.clone(), current_color))
};
let color_widget: Element<_> =
if app.color_picker_active_href.as_ref() == Some(&cal_href) {
color_picker::ColorPicker::new(
true,
current_color,
color_btn,
Message::CancelColorPicker,
Message::SubmitColorPicker,
)
.into()
} else {
color_btn.into()
};
let save_btn: Element<_> = if has_changes {
let h = cal.href.clone();
button(
icon::icon(icon::CHECK)
.size(16)
.color(app.theme().extended_palette().primary.base.color),
)
.style(button::text)
.padding(5)
.on_press(Message::SubmitRemoteCalendar(h))
.into()
} else {
Space::new().width(Length::Fixed(26.0)).into()
};
col = col.push(
row![checkbox_elem, name_input, color_widget, save_btn]
.spacing(5)
.align_y(iced::Alignment::Center),
);
}
col = col.push(
button(text(rust_i18n::t!("create_new_remote_calendar")))
.style(button::secondary)
.on_press(Message::AddRemoteCalendar),
);
container(col)
.padding(10)
.style(|_| container::Style {
border: iced::Border {
radius: 4.0.into(),
width: 1.0,
color: Color::from_rgb(0.3, 0.3, 0.3),
},
..Default::default()
})
.into()
} else {
Space::new().width(0).into()
};
let local_cal_ui: Element<_> = if is_settings {
let mut local_cal_col = column![
text(rust_i18n::t!("local_collections")).size(20),
text(rust_i18n::t!("local_collections_explain"))
.size(12)
.color(Color::from_rgb(0.6, 0.6, 0.6)),
]
.spacing(10);
for cal in &app.local_cals_editing {
let href = cal.href.clone();
let is_default = href == LOCAL_CALENDAR_HREF;
let name_input = text_input(&rust_i18n::t!("name_label"), &cal.name)
.on_input(move |s| Message::LocalCalendarNameChanged(href.clone(), s))
.padding(5)
.width(Length::FillPortion(3));
let export_href = cal.href.clone();
let export_btn = button(
row![
icon::icon(icon::EXPORT).size(14),
text(rust_i18n::t!("export")).size(10)
]
.spacing(3)
.align_y(iced::Alignment::Center),
)
.padding(5)
.style(button::secondary)
.on_press(Message::ExportLocalIcs(export_href));
let import_href = cal.href.clone();
let import_btn = button(
row![
icon::icon(icon::IMPORT).size(14),
text(rust_i18n::t!("import_action")).size(10)
]
.spacing(3)
.align_y(iced::Alignment::Center),
)
.padding(5)
.style(button::secondary)
.on_press(Message::ImportLocalIcs(import_href));
let current_color = cal
.color
.as_ref()
.and_then(|h| crate::color_utils::parse_hex_to_floats(h))
.map(|(r, g, b)| Color::from_rgb(r, g, b))
.unwrap_or(Color::from_rgb(0.5, 0.5, 0.5));
let color_btn = button(
text(icon::PALETTE_COLOR.to_string())
.font(icon::FONT)
.size(16)
.color(current_color),
)
.padding(5)
.style(button::text)
.on_press(Message::OpenColorPicker(cal.href.clone(), current_color));
let color_widget: Element<_> =
if app.color_picker_active_href.as_ref() == Some(&cal.href) {
color_picker::ColorPicker::new(
true,
current_color,
color_btn,
Message::CancelColorPicker,
Message::SubmitColorPicker,
)
.into()
} else {
color_btn.into()
};
let delete_btn: Element<_> = if !is_default {
let h = cal.href.clone();
button(icon::icon(icon::TRASH).size(14))
.style(button::danger)
.padding(5)
.on_press(Message::DeleteLocalCalendar(h))
.into()
} else {
Space::new().width(Length::Fixed(22.0)).into()
};
local_cal_col = local_cal_col.push(
row![name_input, export_btn, import_btn, color_widget, delete_btn]
.spacing(5)
.align_y(iced::Alignment::Center),
);
}
local_cal_col = local_cal_col.push(
button(text(rust_i18n::t!("create_new_local_calendar")))
.style(button::secondary)
.on_press(Message::AddLocalCalendar),
);
container(local_cal_col)
.padding(10)
.style(|_| container::Style {
border: iced::Border {
radius: 6.0.into(),
width: 1.0,
color: Color::from_rgba(0.5, 0.5, 0.5, 0.2),
},
..Default::default()
})
.into()
} else {
Space::new().width(0).into()
};
let save_connect_btn = button(text(if is_settings {
rust_i18n::t!("save_and_connect")
} else {
rust_i18n::t!("connect")
}))
.padding(10)
.width(Length::Fill)
.on_press(Message::ObSubmit);
let insecure_check = checkbox::<Message, iced::Theme, iced::Renderer>(app.ob_insecure)
.label(rust_i18n::t!("allow_insecure_ssl"))
.on_toggle(Message::ObInsecureToggled)
.size(16)
.text_size(14);
let offline_button_or_space: Element<_> = if !is_settings {
button(text(rust_i18n::t!("local_label")))
.padding(10)
.style(button::secondary)
.on_press(Message::ObSubmitOffline)
.into()
} else {
Space::new().height(0).into()
};
let form = column![
container(
column![
text(rust_i18n::t!("server_connection")).size(20),
text(rust_i18n::t!("caldav_url")),
text_input("https://...", &app.ob_url)
.on_input(Message::ObUrlChanged)
.padding(10),
text(rust_i18n::t!("username")),
text_input(&rust_i18n::t!("username"), &app.ob_user)
.on_input(Message::ObUserChanged)
.padding(10),
text(rust_i18n::t!("password")),
row![
text_input(&rust_i18n::t!("password"), &app.ob_pass)
.on_input(Message::ObPassChanged)
.secure(!app.ob_password_visible)
.padding(10)
.width(Length::Fill),
button(
icon::icon(if app.ob_password_visible {
icon::EYE_CLOSED
} else {
icon::EYE
})
.size(20)
)
.style(button::text)
.padding(10)
.on_press(Message::ToggleObPasswordVisibility)
]
.spacing(8)
.align_y(iced::Alignment::Center),
insecure_check,
checkbox::<Message, iced::Theme, iced::Renderer>(app.sync_settings)
.label(rust_i18n::t!("sync_settings"))
.on_toggle(Message::SetSyncSettings),
save_connect_btn
]
.spacing(15)
)
.padding(10)
.style(|_| container::Style {
border: iced::Border {
radius: 6.0.into(),
width: 1.0,
color: Color::from_rgba(0.5, 0.5, 0.5, 0.2)
},
..Default::default()
}),
lang_picker, theme_picker, picker,
cal_mgmt_ui,
local_cal_ui,
notifications_ui,
aliases_ui,
advanced_ui,
offline_button_or_space,
]
.spacing(20)
.max_width(500);
let scrollable_content = column![error, form]
.spacing(20)
.align_x(iced::Alignment::Center);
let main_col = column![
title_drag_area,
scrollable(
container(scrollable_content)
.width(Length::Fill)
.padding(20)
.center_x(Length::Fill),
)
]
.width(Length::Fill)
.height(Length::Fill);
container(main_col)
.width(Length::Fill)
.height(Length::Fill)
.into()
}