use crate::navigation::RootMessage; use crate::styling::{BUTTON_RADIUS, HALF_SPACING}; use chrono::Utc; use iced::widget::container::Style; use iced::widget::{Column, container, row, scrollable, text}; use iced::{Element, Task}; use iced_core::Alignment::Center; use iced_core::Background::Color; use iced_core::Border; use iced_core::Length::Fill; use iced_core::alignment::Horizontal::Right; use iced_core::alignment::Vertical; pub struct NotificationManager { list: Vec<(u32, NotificationRequest, u32)>, notification_lifetime: u32, next_id: u32, } #[derive(Clone)] pub enum NotificationMessage { Remove(u32), } impl Default for NotificationManager { fn default() -> Self { Self::new() } } impl NotificationManager { pub fn new() -> Self { Self { list: Vec::new(), notification_lifetime: 10, next_id: 1, } } pub fn draw(&self) -> Element<'_, NotificationMessage> { let mut column = Column::new().spacing(HALF_SPACING); for (id, notif, time) in &self.list { column = column.push(Self::draw_notif(id, notif, time)); } container(scrollable(column)) .width(Fill) .height(Fill) .align_x(Right) .align_y(Vertical::Top) .padding(HALF_SPACING) .into() } pub fn push(&mut self, notification: NotificationRequest) -> Task { let id = self.next_id; self.list.push(( id, notification, Utc::now().timestamp() as u32 + self.notification_lifetime, )); self.next_id += 1; let message = NotificationMessage::Remove(id); let time = self.notification_lifetime; Task::perform( async move { tokio::time::sleep(core::time::Duration::from_secs(time as u64)).await; }, |_| RootMessage::Notification(message), ) } pub fn update(&mut self, message: NotificationMessage) -> Task { match message { NotificationMessage::Remove(id) => { if let Some(index) = self.list.iter().position(|item| item.0 == id) { self.list.remove(index); } } } Task::none() } fn draw_notif<'a>( id: &u32, notif: &NotificationRequest, _time: &u32, ) -> Element<'a, NotificationMessage> { let color_id; let content = match notif { NotificationRequest::Info(data) => { color_id = 1; Self::draw_data(data) } NotificationRequest::Warning(data) => { color_id = 2; Self::draw_data(data) } NotificationRequest::Error(data) => { color_id = 3; Self::draw_data(data) } }; let container = container( row![ content, iced::widget::button("✕") .on_press(NotificationMessage::Remove(*id)) .style(iced::widget::button::text) ] .align_y(Center) .width(300) .padding(HALF_SPACING), ); container .style(move |theme| { let color = match color_id { 1 => theme.palette().success, 2 => theme.palette().warning, 3 => theme.palette().danger, _ => theme.palette().warning, }; Style { text_color: None, background: Some(Color(color.scale_alpha(0.3))), border: Border::default() .width(1) .color(color) .rounded(BUTTON_RADIUS), shadow: Default::default(), snap: false, } }) .into() } fn draw_data<'a>(data: &NotificationData) -> Element<'a, NotificationMessage> { match data { NotificationData::DataSynced => { text!("Данные успешно синхронизированы") } NotificationData::ImportError => { text!("Ошибка при импорте данных из Anki") } NotificationData::IncorrectAnkiFile => { text!("Неподдерживаемый формат файла .apkg") } NotificationData::OpenFileError => { text!("Ошибка открытия файла") } NotificationData::VoiceVoxUnavailable => { text!("Не получается подключиться к VoiceVox") } NotificationData::PermissionDenied => { text!("У приложение недостаточно прав для доступа в файлам") } NotificationData::WebApiUnavailable => { text!("Не получается подключиться к Api") } NotificationData::DbSaveError => { text!("Не получается сохранить данные") } NotificationData::DbLoadError => { text!("Не получается загрузить данные") } NotificationData::CompressError => { text!("Ошибка сжатия или распаковки данных") } NotificationData::LocalVersionChanged => { text!("Локальная версия будет заменена") } } .width(Fill) .into() } } #[derive(Clone)] pub enum NotificationRequest { Info(NotificationData), Warning(NotificationData), Error(NotificationData), } #[derive(Clone, Debug)] pub enum NotificationData { PermissionDenied, DataSynced, ImportError, IncorrectAnkiFile, OpenFileError, VoiceVoxUnavailable, WebApiUnavailable, DbSaveError, DbLoadError, CompressError, LocalVersionChanged, } pub type MaybeError = Result<(), NotificationData>;