Notification base
This commit is contained in:
1 parent
dfaff30efa
commit
e1bfd9494d
4 files changed
+177
-9
No files matched your search
@@ -16,6 +16,7 @@ pub mod styling;
|
|||||||
mod sync;
|
mod sync;
|
||||||
mod word;
|
mod word;
|
||||||
mod writing;
|
mod writing;
|
||||||
|
pub mod notification;
|
||||||
|
|
||||||
use crate::RootMessage::Keyboard;
|
use crate::RootMessage::Keyboard;
|
||||||
use crate::data_provider::card_sets::load_sets;
|
use crate::data_provider::card_sets::load_sets;
|
||||||
|
|||||||
+23
-9
@@ -1,14 +1,15 @@
|
|||||||
use crate::data_provider::history::{get_history_of_set, history_dir};
|
use crate::data_provider::history::{get_history_of_set, history_dir};
|
||||||
use crate::data_provider::sqlite::{create_db, default_connection};
|
use crate::data_provider::sqlite::{create_db, default_connection};
|
||||||
use crate::data_provider::web_api::{get_local_version, get_web_version, load_data, set_local_version};
|
use crate::data_provider::web_api::{get_local_version, get_web_version, load_data, set_local_version};
|
||||||
use crate::dictionary::{DictionaryMessage, DictionaryState, app_data_dir};
|
use crate::dictionary::{app_data_dir, DictionaryMessage, DictionaryState};
|
||||||
use crate::dictionary_quiz::{DictionaryQuizMessage, DictionaryQuizState};
|
use crate::dictionary_quiz::{DictionaryQuizMessage, DictionaryQuizState};
|
||||||
use crate::history::{HistoryMessage, HistoryState};
|
use crate::history::{HistoryMessage, HistoryState};
|
||||||
use crate::import::{ImportMessage, ImportState};
|
use crate::import::{ImportMessage, ImportState};
|
||||||
use crate::lang::Id;
|
use crate::lang::Id;
|
||||||
use crate::message_navigation;
|
use crate::message_navigation;
|
||||||
use crate::navigation::Page::*;
|
use crate::navigation::Page::*;
|
||||||
use crate::navigation::RootMessage::{DataLoaded, Keyboard, UpdateData};
|
use crate::navigation::RootMessage::{DataLoaded, Keyboard, PushNotification, UpdateData, Notification};
|
||||||
|
use crate::notification::{NotificationRequest, NotificationManager, NotificationMessage};
|
||||||
use crate::quiz::{QuizMessage, QuizState};
|
use crate::quiz::{QuizMessage, QuizState};
|
||||||
use crate::randomizer::{RandomizerMessage, RandomizerState};
|
use crate::randomizer::{RandomizerMessage, RandomizerState};
|
||||||
use crate::repetition::{RepetitionMessage, RepetitionState};
|
use crate::repetition::{RepetitionMessage, RepetitionState};
|
||||||
@@ -21,16 +22,18 @@ use crate::sync::{SyncMessage, SyncState};
|
|||||||
use crate::view_navigation;
|
use crate::view_navigation;
|
||||||
use crate::word::{WordMessage, WordState};
|
use crate::word::{WordMessage, WordState};
|
||||||
use crate::writing::{WritingMessage, WritingState};
|
use crate::writing::{WritingMessage, WritingState};
|
||||||
use crate::{AppState, fill_state};
|
use crate::{fill_state, AppState};
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use iced::keyboard::Event;
|
use iced::keyboard::Event;
|
||||||
|
use iced::widget::stack;
|
||||||
use iced::{Element, Task};
|
use iced::{Element, Task};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use reqwest::Error;
|
use reqwest::Error;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
use iced_core::Length::Fill;
|
||||||
|
|
||||||
impl Default for ScreenState {
|
impl Default for ScreenState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@@ -40,6 +43,7 @@ impl Default for ScreenState {
|
|||||||
ScreenState {
|
ScreenState {
|
||||||
stack: vec![Selector(SelectorState::new(state.clone()))],
|
stack: vec![Selector(SelectorState::new(state.clone()))],
|
||||||
app_state: state.clone(),
|
app_state: state.clone(),
|
||||||
|
notification_manager: NotificationManager::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +65,8 @@ pub enum RootMessage {
|
|||||||
Import(ImportMessage),
|
Import(ImportMessage),
|
||||||
Keyboard(Event),
|
Keyboard(Event),
|
||||||
DataLoaded(HashMap<Id, Vec<(NaiveDate, u32)>>),
|
DataLoaded(HashMap<Id, Vec<(NaiveDate, u32)>>),
|
||||||
|
PushNotification(NotificationRequest),
|
||||||
|
Notification(NotificationMessage),
|
||||||
None,
|
None,
|
||||||
UpdateData,
|
UpdateData,
|
||||||
}
|
}
|
||||||
@@ -85,6 +91,7 @@ pub enum Page {
|
|||||||
pub struct ScreenState {
|
pub struct ScreenState {
|
||||||
stack: Vec<Page>,
|
stack: Vec<Page>,
|
||||||
app_state: Arc<Mutex<AppState>>,
|
app_state: Arc<Mutex<AppState>>,
|
||||||
|
notification_manager: NotificationManager
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScreenState {
|
impl ScreenState {
|
||||||
@@ -187,6 +194,15 @@ impl ScreenState {
|
|||||||
fill_state(&mut state);
|
fill_state(&mut state);
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Notification(message) = message {
|
||||||
|
return self.notification_manager.update(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let PushNotification(notification) = &message {
|
||||||
|
return self.notification_manager.push(notification.clone())
|
||||||
|
}
|
||||||
|
|
||||||
let task: (Task<RootMessage>, bool) = self.navigate_or_update(message);
|
let task: (Task<RootMessage>, bool) = self.navigate_or_update(message);
|
||||||
|
|
||||||
if task.1 {
|
if task.1 {
|
||||||
@@ -233,6 +249,7 @@ impl ScreenState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn view(&self) -> Element<'_, RootMessage> {
|
pub fn view(&self) -> Element<'_, RootMessage> {
|
||||||
|
|
||||||
println!("View update");
|
println!("View update");
|
||||||
let time = Instant::now();
|
let time = Instant::now();
|
||||||
let view = view_navigation!(
|
let view = view_navigation!(
|
||||||
@@ -252,8 +269,10 @@ impl ScreenState {
|
|||||||
Import
|
Import
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let notifications = self.notification_manager.draw().map(RootMessage::Notification);
|
||||||
|
|
||||||
println!("View took {:?}", time.elapsed());
|
println!("View took {:?}", time.elapsed());
|
||||||
view
|
stack![view, notifications].width(Fill).height(Fill).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_keyboard(&mut self, message: Event) -> Task<RootMessage> {
|
fn handle_keyboard(&mut self, message: Event) -> Task<RootMessage> {
|
||||||
@@ -281,7 +300,6 @@ macro_rules! view_navigation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! state_update {
|
macro_rules! state_update {
|
||||||
($message:expr, $stack:expr, $($e:ident), *) => {
|
($message:expr, $stack:expr, $($e:ident), *) => {
|
||||||
@@ -300,7 +318,6 @@ macro_rules! state_update {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! message_navigation {
|
macro_rules! message_navigation {
|
||||||
($msg:expr, $stack:expr, $state:expr) => {
|
($msg:expr, $stack:expr, $state:expr) => {
|
||||||
@@ -321,7 +338,6 @@ macro_rules! message_navigation {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! state_navigate_handle {
|
macro_rules! state_navigate_handle {
|
||||||
($stack:expr, $($e:ident), *) => {
|
($stack:expr, $($e:ident), *) => {
|
||||||
@@ -335,14 +351,12 @@ macro_rules! state_navigate_handle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NavigatedPage<T> {
|
pub trait NavigatedPage<T> {
|
||||||
fn navigate(&mut self, message: &T) -> Option<Page>;
|
fn navigate(&mut self, message: &T) -> Option<Page>;
|
||||||
fn navigated(&mut self);
|
fn navigated(&mut self);
|
||||||
fn update(&mut self, message: T) -> Task<RootMessage>;
|
fn update(&mut self, message: T) -> Task<RootMessage>;
|
||||||
fn view(&self) -> Element<'_, T>;
|
fn view(&self) -> Element<'_, T>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait KeyPressedPage {
|
pub trait KeyPressedPage {
|
||||||
fn press(&mut self, message: &Event) -> Task<RootMessage>;
|
fn press(&mut self, message: &Event) -> Task<RootMessage>;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
use crate::navigation::RootMessage;
|
||||||
|
use crate::styling::{BUTTON_RADIUS, HALF_SPACING};
|
||||||
|
use chrono::Utc;
|
||||||
|
use iced::widget::container::Style;
|
||||||
|
use iced::widget::{column, container, scrollable, text, Column};
|
||||||
|
use iced::{Element, Task};
|
||||||
|
use iced_core::alignment::Horizontal::Right;
|
||||||
|
use iced_core::alignment::Vertical;
|
||||||
|
use iced_core::Background::Color;
|
||||||
|
use iced_core::Border;
|
||||||
|
use iced_core::Length::Fill;
|
||||||
|
|
||||||
|
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<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)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let container = container(column![
|
||||||
|
iced::widget::button("✕")
|
||||||
|
.on_press(NotificationMessage::Remove(*id))
|
||||||
|
.style(iced::widget::button::text),
|
||||||
|
content
|
||||||
|
].align_x(Right).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::DataLoaded => {
|
||||||
|
text!("Данные успешно скачаны")
|
||||||
|
}
|
||||||
|
NotificationData::ImportError => {
|
||||||
|
text!("Ошибка при импорте данных из Anki")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum NotificationRequest {
|
||||||
|
Info(NotificationData),
|
||||||
|
Warning(NotificationData),
|
||||||
|
Error(NotificationData),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum NotificationData {
|
||||||
|
DataLoaded,
|
||||||
|
ImportError,
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ use iced::{Center, Element, Fill, Font, Length, Task};
|
|||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use crate::notification::{NotificationRequest, NotificationData};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum SyncMessage {
|
pub enum SyncMessage {
|
||||||
@@ -128,6 +129,7 @@ impl NavigatedPage<SyncMessage> for SyncState {
|
|||||||
state.decks = updated_state.decks;
|
state.decks = updated_state.decks;
|
||||||
state.dictionary = updated_state.dictionary;
|
state.dictionary = updated_state.dictionary;
|
||||||
state.word_groups = updated_state.word_groups;
|
state.word_groups = updated_state.word_groups;
|
||||||
|
return Task::done(RootMessage::PushNotification(NotificationRequest::Info(NotificationData::DataLoaded)))
|
||||||
}
|
}
|
||||||
Disable => {}
|
Disable => {}
|
||||||
DisableSync => {
|
DisableSync => {
|
||||||
|
|||||||
Reference in new issue
Block a user