2026-08-23 21:00:48 +03:00
|
|
|
use crate::navigation::RootMessage;
|
|
|
|
|
use crate::styling::{BUTTON_RADIUS, HALF_SPACING};
|
|
|
|
|
use chrono::Utc;
|
|
|
|
|
use iced::widget::container::Style;
|
2026-08-23 22:48:46 +03:00
|
|
|
use iced::widget::{Column, container, row, scrollable, text};
|
2026-08-23 21:00:48 +03:00
|
|
|
use iced::{Element, Task};
|
2026-08-23 22:05:08 +03:00
|
|
|
use iced_core::Alignment::Center;
|
2026-08-23 21:00:48 +03:00
|
|
|
use iced_core::Background::Color;
|
|
|
|
|
use iced_core::Border;
|
|
|
|
|
use iced_core::Length::Fill;
|
2026-08-23 22:05:08 +03:00
|
|
|
use iced_core::alignment::Horizontal::Right;
|
|
|
|
|
use iced_core::alignment::Vertical;
|
2026-08-23 21:00:48 +03:00
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-23 22:05:08 +03:00
|
|
|
container(scrollable(column))
|
|
|
|
|
.width(Fill)
|
|
|
|
|
.height(Fill)
|
2026-08-23 21:00:48 +03:00
|
|
|
.align_x(Right)
|
2026-08-23 22:05:08 +03:00
|
|
|
.align_y(Vertical::Top)
|
|
|
|
|
.padding(HALF_SPACING)
|
2026-08-23 21:00:48 +03:00
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn push(&mut self, notification: NotificationRequest) -> Task<RootMessage> {
|
|
|
|
|
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<RootMessage> {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-08-23 22:05:08 +03:00
|
|
|
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),
|
|
|
|
|
);
|
2026-08-23 21:00:48 +03:00
|
|
|
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))),
|
2026-08-23 22:05:08 +03:00
|
|
|
border: Border::default()
|
|
|
|
|
.width(1)
|
|
|
|
|
.color(color)
|
|
|
|
|
.rounded(BUTTON_RADIUS),
|
2026-08-23 21:00:48 +03:00
|
|
|
shadow: Default::default(),
|
|
|
|
|
snap: false,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn draw_data<'a>(data: &NotificationData) -> Element<'a, NotificationMessage> {
|
|
|
|
|
match data {
|
|
|
|
|
NotificationData::DataLoaded => {
|
|
|
|
|
text!("Данные успешно скачаны")
|
|
|
|
|
}
|
|
|
|
|
NotificationData::ImportError => {
|
|
|
|
|
text!("Ошибка при импорте данных из Anki")
|
|
|
|
|
}
|
2026-08-23 21:49:01 +03:00
|
|
|
NotificationData::IncorrectAnkiFile => {
|
|
|
|
|
text!("Неподдерживаемый формат файла .apkg")
|
|
|
|
|
}
|
|
|
|
|
NotificationData::OpenFileError => {
|
|
|
|
|
text!("Ошибка открытия файла пакета")
|
|
|
|
|
}
|
|
|
|
|
NotificationData::VoiceVoxUnavailable => {
|
|
|
|
|
text!("Не получается подключиться к VoiceVox")
|
|
|
|
|
}
|
|
|
|
|
NotificationData::PermissionDenied => {
|
|
|
|
|
text!("У приложение недостаточно прав")
|
|
|
|
|
}
|
2026-08-23 22:16:02 +03:00
|
|
|
NotificationData::WebApiUnavailable => {
|
|
|
|
|
text!("Не получается подключиться к Api")
|
|
|
|
|
}
|
2026-08-23 21:00:48 +03:00
|
|
|
}
|
2026-08-23 22:05:08 +03:00
|
|
|
.width(Fill)
|
2026-08-23 21:00:48 +03:00
|
|
|
.into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub enum NotificationRequest {
|
|
|
|
|
Info(NotificationData),
|
|
|
|
|
Warning(NotificationData),
|
|
|
|
|
Error(NotificationData),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
|
|
|
|
pub enum NotificationData {
|
2026-08-23 21:49:01 +03:00
|
|
|
PermissionDenied,
|
2026-08-23 21:00:48 +03:00
|
|
|
DataLoaded,
|
|
|
|
|
ImportError,
|
2026-08-23 21:49:01 +03:00
|
|
|
IncorrectAnkiFile,
|
|
|
|
|
OpenFileError,
|
2026-08-23 22:05:08 +03:00
|
|
|
VoiceVoxUnavailable,
|
2026-08-23 22:16:02 +03:00
|
|
|
WebApiUnavailable,
|
2026-08-23 21:00:48 +03:00
|
|
|
}
|