Compare commits
2 commits
0b2ae19085
...
ea41aa43d8
Author | SHA1 | Date | |
---|---|---|---|
|
ea41aa43d8 | ||
|
dc1987a77b |
8 changed files with 407 additions and 231 deletions
|
@ -184,7 +184,7 @@ macro_rules! parse_json_response {
|
||||||
let mut unused = std::collections::BTreeSet::new();
|
let mut unused = std::collections::BTreeSet::new();
|
||||||
|
|
||||||
println!("res({}): {}", stringify!($type), res);
|
println!("res({}): {}", stringify!($type), res);
|
||||||
let res: Result<$type,_> = serde_ignored::deserialize(jd, |path| {
|
let res: Result<$type, _> = serde_ignored::deserialize(jd, |path| {
|
||||||
unused.insert(path.to_string());
|
unused.insert(path.to_string());
|
||||||
});
|
});
|
||||||
println!("res({}): {:?}", stringify!($type), unused);
|
println!("res({}): {:?}", stringify!($type), unused);
|
||||||
|
@ -205,7 +205,7 @@ macro_rules! parse_json_response_from_apiresult {
|
||||||
let mut unused = std::collections::BTreeSet::new();
|
let mut unused = std::collections::BTreeSet::new();
|
||||||
|
|
||||||
println!("res({}): {}", stringify!($type), res);
|
println!("res({}): {}", stringify!($type), res);
|
||||||
let res: Result<APIResult<$type>,_> = serde_ignored::deserialize(jd, |path| {
|
let res: Result<APIResult<$type>, _> = serde_ignored::deserialize(jd, |path| {
|
||||||
unused.insert(path.to_string());
|
unused.insert(path.to_string());
|
||||||
});
|
});
|
||||||
println!("res({}): {:?}", stringify!($type), unused);
|
println!("res({}): {:?}", stringify!($type), unused);
|
||||||
|
|
|
@ -17,3 +17,4 @@ libpaket = { path = "../libpaket" }
|
||||||
glycin = { version = "2.0.0-beta", features = ["gdk4"] }
|
glycin = { version = "2.0.0-beta", features = ["gdk4"] }
|
||||||
oo7 = { version = "0.3" }
|
oo7 = { version = "0.3" }
|
||||||
relm4-icons = { version = "0.9" }
|
relm4-icons = { version = "0.9" }
|
||||||
|
gtk = { package = "gtk4", version = "0.9", features = ["v4_16"]}
|
119
paket/src/advice.rs
Normal file
119
paket/src/advice.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use libpaket::LibraryError;
|
||||||
|
use relm4::{adw, gtk, gtk::gdk, gtk::gio, gtk::glib};
|
||||||
|
|
||||||
|
use adw::prelude::*;
|
||||||
|
use relm4::prelude::*;
|
||||||
|
|
||||||
|
use crate::advices::AdviceClient;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Advice {
|
||||||
|
Prod(AdviceProd),
|
||||||
|
UITest(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AdviceProd {
|
||||||
|
pub client: AdviceClient,
|
||||||
|
pub model: libpaket::advices::Advice,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracker::track]
|
||||||
|
pub struct AdviceCard {
|
||||||
|
#[do_not_track]
|
||||||
|
metadata: Advice,
|
||||||
|
texture: Option<gdk::Texture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AdviceCardCmds {
|
||||||
|
GotTexture(gdk::Texture),
|
||||||
|
Error(LibraryError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::factory(pub)]
|
||||||
|
impl FactoryComponent for AdviceCard {
|
||||||
|
type Init = Advice;
|
||||||
|
type Input = ();
|
||||||
|
type Output = ();
|
||||||
|
type CommandOutput = AdviceCardCmds;
|
||||||
|
type ParentWidget = gtk::FlowBox;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
#[root]
|
||||||
|
gtk::FlowBoxChild {
|
||||||
|
set_halign: gtk::Align::Start,
|
||||||
|
set_valign: gtk::Align::Start,
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = >k::Overlay {
|
||||||
|
set_margin_all: 8,
|
||||||
|
|
||||||
|
add_overlay = >k::Spinner {
|
||||||
|
start: (),
|
||||||
|
set_align: gtk::Align::Center,
|
||||||
|
|
||||||
|
#[track(self.changed_texture())]
|
||||||
|
set_visible: self.texture.is_none(),
|
||||||
|
},
|
||||||
|
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = >k::Picture {
|
||||||
|
#[track(self.changed_texture())]
|
||||||
|
set_paintable: self.texture.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_model(value: Self::Init, _index: &DynamicIndex, sender: FactorySender<Self>) -> Self {
|
||||||
|
let _self = Self {
|
||||||
|
metadata: value.clone(),
|
||||||
|
texture: None,
|
||||||
|
tracker: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
sender.oneshot_command(async move {
|
||||||
|
let res = match value {
|
||||||
|
Advice::Prod(value) => {
|
||||||
|
let res = value.client.get_image(&value.model).await;
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => return AdviceCardCmds::Error(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Advice::UITest(value) => value,
|
||||||
|
};
|
||||||
|
|
||||||
|
let file = {
|
||||||
|
let (file, io_stream) = gio::File::new_tmp(None::<&std::path::Path>).unwrap();
|
||||||
|
let output_stream = io_stream.output_stream();
|
||||||
|
output_stream
|
||||||
|
.write(res.as_slice(), None::<&gio::Cancellable>)
|
||||||
|
.unwrap();
|
||||||
|
file
|
||||||
|
};
|
||||||
|
|
||||||
|
let image = glycin::Loader::new(file)
|
||||||
|
.load()
|
||||||
|
.await
|
||||||
|
.expect("Image decoding failed");
|
||||||
|
let frame = image
|
||||||
|
.next_frame()
|
||||||
|
.await
|
||||||
|
.expect("Image frame decoding failed");
|
||||||
|
|
||||||
|
AdviceCardCmds::GotTexture(frame.texture())
|
||||||
|
});
|
||||||
|
|
||||||
|
_self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_cmd(&mut self, message: Self::CommandOutput, sender: FactorySender<Self>) {
|
||||||
|
match message {
|
||||||
|
AdviceCardCmds::GotTexture(texture) => self.set_texture(Some(texture)),
|
||||||
|
AdviceCardCmds::Error(err) => todo!(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,123 +1,293 @@
|
||||||
use adw::{gio, glib};
|
use std::collections::HashMap;
|
||||||
use gtk::gdk;
|
use std::sync::Arc;
|
||||||
use libpaket::advices::UatToken;
|
|
||||||
use libpaket::LibraryError;
|
use futures::lock::Mutex;
|
||||||
use relm4::gtk;
|
use libpaket::{AdviceClient as LibraryAdviceClient, LibraryResult};
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use gio::prelude::*;
|
use gio::prelude::*;
|
||||||
use glib::prelude::*;
|
use glib::prelude::*;
|
||||||
|
use gtk::{gio, glib};
|
||||||
|
use relm4::prelude::*;
|
||||||
use relm4::prelude::*;
|
use relm4::prelude::*;
|
||||||
|
|
||||||
|
use relm4::factory::FactoryVecDeque;
|
||||||
|
|
||||||
|
use crate::advice::{Advice, AdviceCard, AdviceProd};
|
||||||
|
|
||||||
|
struct AdviceClientImpl {
|
||||||
|
uat_token: libpaket::advices::UatToken,
|
||||||
|
client: libpaket::AdviceClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AdviceClient(Arc<Mutex<AdviceClientImpl>>);
|
||||||
|
|
||||||
|
impl AdviceClient {
|
||||||
|
pub async fn get_image(&self, advice: &libpaket::advices::Advice) -> LibraryResult<Vec<u8>> {
|
||||||
|
let lock = self.0.lock().await;
|
||||||
|
lock.client
|
||||||
|
.fetch_advice_image(advice, &lock.uat_token)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(uat_token: libpaket::advices::UatToken) -> Self {
|
||||||
|
Self(Arc::new(Mutex::new(AdviceClientImpl {
|
||||||
|
uat_token,
|
||||||
|
client: LibraryAdviceClient::new(),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AdvicesDayView {
|
||||||
|
date: String,
|
||||||
|
factory: FactoryVecDeque<AdviceCard>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AppAdviceMetadata {
|
pub struct AdvicesForDay {
|
||||||
|
pub advices: Vec<Advice>,
|
||||||
pub date: String,
|
pub date: String,
|
||||||
pub advice: libpaket::advices::Advice,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracker::track]
|
|
||||||
pub struct AppAdvice {
|
|
||||||
#[do_not_track]
|
|
||||||
metadata: AppAdviceMetadata,
|
|
||||||
texture: Option<gdk::Texture>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum AppAdviceCmds {
|
|
||||||
GotTexture(gdk::Texture),
|
|
||||||
Error(LibraryError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[relm4::factory(pub)]
|
#[relm4::factory(pub)]
|
||||||
impl FactoryComponent for AppAdvice {
|
impl FactoryComponent for AdvicesDayView {
|
||||||
type Init = (AppAdviceMetadata, UatToken);
|
type Init = AdvicesForDay;
|
||||||
type Input = ();
|
type Input = ();
|
||||||
type Output = ();
|
type Output = ();
|
||||||
type CommandOutput = AppAdviceCmds;
|
type CommandOutput = ();
|
||||||
type ParentWidget = adw::Carousel;
|
type ParentWidget = gtk::Box;
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
#[root]
|
gtk::Box {
|
||||||
gtk::Overlay {
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
add_overlay = >k::Spinner {
|
set_margin_all: 16,
|
||||||
start: (),
|
|
||||||
set_align: gtk::Align::Center,
|
|
||||||
|
|
||||||
#[track(self.changed_texture())]
|
gtk::Label {
|
||||||
set_visible: self.texture.is_none(),
|
add_css_class: relm4::css::TITLE_4,
|
||||||
|
set_halign: gtk::Align::Start,
|
||||||
|
set_margin_bottom: 4,
|
||||||
|
set_margin_start: 8,
|
||||||
|
|
||||||
|
set_label: self.date.as_str(),
|
||||||
},
|
},
|
||||||
|
|
||||||
add_overlay = >k::Box {
|
self.factory.widget() -> >k::FlowBox {
|
||||||
add_css_class: relm4::css::OSD,
|
|
||||||
add_css_class: relm4::css::TOOLBAR,
|
|
||||||
add_css_class: relm4::css::NUMERIC,
|
|
||||||
|
|
||||||
set_valign: gtk::Align::End,
|
},
|
||||||
set_halign: gtk::Align::End,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
set_margin_all: 8,
|
fn init_model(value: Self::Init, _: &DynamicIndex, sender: FactorySender<Self>) -> Self {
|
||||||
|
let mut factory = FactoryVecDeque::builder().launch_default().detach();
|
||||||
|
|
||||||
gtk::Label {
|
{
|
||||||
set_label: self.metadata.date.as_str(),
|
let mut guard = factory.guard();
|
||||||
|
for item in value.advices {
|
||||||
|
guard.push_back(item);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let _self = Self {
|
||||||
|
date: value.date,
|
||||||
|
factory,
|
||||||
|
};
|
||||||
|
|
||||||
|
_self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum AdvicesViewState {
|
||||||
|
Loading,
|
||||||
|
HaveNone,
|
||||||
|
HaveSome,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracker::track]
|
||||||
|
pub struct AdvicesView {
|
||||||
|
#[do_not_track]
|
||||||
|
factory: FactoryVecDeque<AdvicesDayView>,
|
||||||
|
state: AdvicesViewState,
|
||||||
|
|
||||||
|
#[do_not_track]
|
||||||
|
login: crate::LoginSharedState,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AdvicesViewInput {
|
||||||
|
Fetch,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AdvicesViewCommands {
|
||||||
|
GotAdvices(LibraryResult<Vec<AdvicesForDay>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[relm4::component(async, pub)]
|
||||||
|
impl AsyncComponent for AdvicesView {
|
||||||
|
type Init = crate::LoginSharedState;
|
||||||
|
type Input = AdvicesViewInput;
|
||||||
|
type Output = ();
|
||||||
|
type CommandOutput = AdvicesViewCommands;
|
||||||
|
|
||||||
|
view! {
|
||||||
|
gtk::ScrolledWindow {
|
||||||
|
adw::Clamp {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = &adw::ViewStack {
|
||||||
|
#[name = "advices_page_loading"]
|
||||||
|
add = &adw::StatusPage {
|
||||||
|
set_title: "Loading mail notifications...",
|
||||||
|
},
|
||||||
|
|
||||||
|
#[name = "advices_page_no_available"]
|
||||||
|
add = &adw::StatusPage {
|
||||||
|
set_title: "No mail notifications available."
|
||||||
|
},
|
||||||
|
|
||||||
|
#[name = "advices_page_have_some"]
|
||||||
|
add = &adw::Clamp {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = >k::ScrolledWindow {
|
||||||
|
#[wrap(Some)]
|
||||||
|
set_child = model.factory.widget() -> >k::Box {
|
||||||
|
set_orientation: gtk::Orientation::Vertical,
|
||||||
|
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
#[track(model.changed_state())]
|
||||||
|
set_visible_child: {
|
||||||
|
let page: >k::Widget = match model.state {
|
||||||
|
AdvicesViewState::Loading => advices_page_loading.upcast_ref::<gtk::Widget>(),
|
||||||
|
AdvicesViewState::HaveNone => advices_page_no_available.upcast_ref::<gtk::Widget>(),
|
||||||
|
AdvicesViewState::HaveSome => advices_page_have_some.upcast_ref::<gtk::Widget>(),
|
||||||
|
};
|
||||||
|
page
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
|
|
||||||
#[wrap(Some)]
|
|
||||||
set_child = >k::Picture {
|
|
||||||
#[track(self.changed_texture())]
|
|
||||||
set_paintable: self.texture.as_ref()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_model(value: Self::Init, _index: &DynamicIndex, sender: FactorySender<Self>) -> Self {
|
async fn init(
|
||||||
let _self = Self {
|
init: Self::Init,
|
||||||
metadata: value.0,
|
root: Self::Root,
|
||||||
texture: None,
|
sender: AsyncComponentSender<Self>,
|
||||||
|
) -> AsyncComponentParts<Self> {
|
||||||
|
let factory = FactoryVecDeque::builder().launch_default().detach();
|
||||||
|
|
||||||
|
let model = AdvicesView {
|
||||||
|
factory,
|
||||||
|
state: AdvicesViewState::Loading,
|
||||||
tracker: 0,
|
tracker: 0,
|
||||||
|
login: init,
|
||||||
};
|
};
|
||||||
|
|
||||||
let advice = _self.metadata.advice.clone();
|
let widgets = view_output!();
|
||||||
let uat = value.1;
|
|
||||||
|
|
||||||
sender.oneshot_command(async move {
|
AsyncComponentParts { model, widgets }
|
||||||
let res = libpaket::advices::AdviceClient::new()
|
|
||||||
.fetch_advice_image(&advice, &uat)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let res = match res {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(err) => return AppAdviceCmds::Error(err),
|
|
||||||
};
|
|
||||||
|
|
||||||
let file = {
|
|
||||||
let (file, io_stream) = gio::File::new_tmp(None::<&std::path::Path>).unwrap();
|
|
||||||
let output_stream = io_stream.output_stream();
|
|
||||||
output_stream
|
|
||||||
.write(res.as_slice(), None::<&gio::Cancellable>)
|
|
||||||
.unwrap();
|
|
||||||
file
|
|
||||||
};
|
|
||||||
|
|
||||||
let image = glycin::Loader::new(file)
|
|
||||||
.load()
|
|
||||||
.await
|
|
||||||
.expect("Image decoding failed");
|
|
||||||
let frame = image
|
|
||||||
.next_frame()
|
|
||||||
.await
|
|
||||||
.expect("Image frame decoding failed");
|
|
||||||
|
|
||||||
AppAdviceCmds::GotTexture(frame.texture())
|
|
||||||
});
|
|
||||||
|
|
||||||
_self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_cmd(&mut self, message: Self::CommandOutput, sender: FactorySender<Self>) {
|
async fn update(
|
||||||
|
&mut self,
|
||||||
|
message: Self::Input,
|
||||||
|
sender: AsyncComponentSender<Self>,
|
||||||
|
root: &Self::Root,
|
||||||
|
) {
|
||||||
match message {
|
match message {
|
||||||
AppAdviceCmds::GotTexture(texture) => self.set_texture(Some(texture)),
|
AdvicesViewInput::Fetch => {
|
||||||
AppAdviceCmds::Error(err) => todo!(),
|
self.set_state(AdvicesViewState::Loading);
|
||||||
|
|
||||||
|
let token = self.login.clone();
|
||||||
|
sender.oneshot_command(async move {
|
||||||
|
// fetching advices
|
||||||
|
let dhli_token = crate::login::get_id_token(&token).await.unwrap();
|
||||||
|
let advices = libpaket::WebClient::new().advices(&dhli_token).await;
|
||||||
|
let advices = match advices {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => return AdvicesViewCommands::GotAdvices(Err(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = libpaket::advices::AdviceClient::new();
|
||||||
|
if !advices.has_any_advices() {
|
||||||
|
return AdvicesViewCommands::GotAdvices(Ok(Vec::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let uat_token = match client.access_token(&advices).await {
|
||||||
|
Ok(oki) => oki,
|
||||||
|
Err(err) => return AdvicesViewCommands::GotAdvices(Err(err)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut arr = Vec::new();
|
||||||
|
|
||||||
|
let client = AdviceClient::new(uat_token);
|
||||||
|
|
||||||
|
if let Some(current) = advices.get_current_advice() {
|
||||||
|
let mut advices_arr = Vec::new();
|
||||||
|
for item in ¤t.list {
|
||||||
|
advices_arr.push(Advice::Prod(AdviceProd {
|
||||||
|
client: client.clone(),
|
||||||
|
model: item.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.push(AdvicesForDay {
|
||||||
|
date: current.date.clone(),
|
||||||
|
advices: advices_arr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for old_n in advices.get_old_advices() {
|
||||||
|
let mut advices_arr = Vec::new();
|
||||||
|
for item in &old_n.list {
|
||||||
|
advices_arr.push(Advice::Prod(AdviceProd {
|
||||||
|
client: client.clone(),
|
||||||
|
model: item.clone(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.push(AdvicesForDay {
|
||||||
|
date: old_n.date.clone(),
|
||||||
|
advices: advices_arr,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvicesViewCommands::GotAdvices(Ok(arr))
|
||||||
|
})
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_cmd(
|
||||||
|
&mut self,
|
||||||
|
message: Self::CommandOutput,
|
||||||
|
sender: AsyncComponentSender<Self>,
|
||||||
|
root: &Self::Root,
|
||||||
|
) {
|
||||||
|
match message {
|
||||||
|
AdvicesViewCommands::GotAdvices(res) => match res {
|
||||||
|
Ok(arr) => {
|
||||||
|
{
|
||||||
|
let mut guard = self.factory.guard();
|
||||||
|
guard.clear();
|
||||||
|
}
|
||||||
|
if arr.len() == 0 {
|
||||||
|
self.set_state(AdvicesViewState::HaveNone);
|
||||||
|
} else {
|
||||||
|
self.set_state(AdvicesViewState::HaveSome);
|
||||||
|
let mut guard = self.factory.guard();
|
||||||
|
for item in arr {
|
||||||
|
guard.push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
println!("{:?}", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use login::{Login, LoginOutput, LoginSharedState};
|
use paket::login::{new_login_shared_state, Login, LoginOutput};
|
||||||
use ready::{Ready, ReadyOutput};
|
use paket::ready::{Ready, ReadyOutput};
|
||||||
use relm4::{
|
use relm4::{
|
||||||
RELM_THREADS,
|
RELM_THREADS,
|
||||||
adw, gtk, main_adw_application, prelude::*, tokio::sync::Mutex,
|
adw, gtk, main_adw_application, prelude::*, tokio::sync::Mutex,
|
||||||
|
@ -10,11 +10,6 @@ use relm4::{
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use adw::{glib, prelude::*};
|
use adw::{glib, prelude::*};
|
||||||
|
|
||||||
mod advices;
|
|
||||||
mod constants;
|
|
||||||
mod login;
|
|
||||||
mod ready;
|
|
||||||
mod tracking;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
enum AppState {
|
enum AppState {
|
||||||
|
@ -172,7 +167,7 @@ impl AsyncComponent for App {
|
||||||
root: Self::Root,
|
root: Self::Root,
|
||||||
sender: AsyncComponentSender<Self>,
|
sender: AsyncComponentSender<Self>,
|
||||||
) -> AsyncComponentParts<Self> {
|
) -> AsyncComponentParts<Self> {
|
||||||
let login_shared_state = Arc::new(Mutex::new(Arc::new(SharedState::new())));
|
let login_shared_state = new_login_shared_state();
|
||||||
|
|
||||||
let ready = Ready::builder()
|
let ready = Ready::builder()
|
||||||
.launch(login_shared_state.clone())
|
.launch(login_shared_state.clone())
|
||||||
|
@ -297,6 +292,6 @@ fn main() {
|
||||||
theme.add_resource_path("/de/j4ne/Paket/icons/");
|
theme.add_resource_path("/de/j4ne/Paket/icons/");
|
||||||
theme.add_resource_path("/de/j4ne/Paket/scalable/actions/");
|
theme.add_resource_path("/de/j4ne/Paket/scalable/actions/");
|
||||||
relm4_icons::initialize_icons();
|
relm4_icons::initialize_icons();
|
||||||
let app = RelmApp::new(constants::APP_ID);
|
let app = RelmApp::new(paket::constants::APP_ID);
|
||||||
app.run_async::<App>(());
|
app.run_async::<App>(());
|
||||||
}
|
}
|
8
paket/src/lib.rs
Normal file
8
paket/src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
pub mod advice;
|
||||||
|
pub mod advices;
|
||||||
|
pub mod constants;
|
||||||
|
pub mod login;
|
||||||
|
pub mod ready;
|
||||||
|
pub mod tracking;
|
||||||
|
|
||||||
|
pub use login::LoginSharedState;
|
|
@ -42,6 +42,10 @@ pub struct LoginFlowModel {
|
||||||
|
|
||||||
pub type LoginSharedState = Arc<Mutex<Arc<SharedState<Option<DHLIdToken>>>>>;
|
pub type LoginSharedState = Arc<Mutex<Arc<SharedState<Option<DHLIdToken>>>>>;
|
||||||
|
|
||||||
|
pub fn new_login_shared_state() -> LoginSharedState {
|
||||||
|
Arc::new(Mutex::new(Arc::new(SharedState::new())))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_id_token(value: &LoginSharedState) -> Option<DHLIdToken> {
|
pub async fn get_id_token(value: &LoginSharedState) -> Option<DHLIdToken> {
|
||||||
let mutex_guard = value.lock().await;
|
let mutex_guard = value.lock().await;
|
||||||
let shared_state_guard = mutex_guard.read();
|
let shared_state_guard = mutex_guard.read();
|
||||||
|
|
|
@ -17,14 +17,9 @@ use relm4::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::advices::AppAdviceMetadata;
|
use crate::advices::{AdvicesViewInput, AdvicesView};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum ReadyAdvicesState {
|
|
||||||
Loading,
|
|
||||||
HaveNone,
|
|
||||||
HaveSome,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracker::track]
|
#[tracker::track]
|
||||||
pub struct Ready {
|
pub struct Ready {
|
||||||
|
@ -34,9 +29,7 @@ pub struct Ready {
|
||||||
have_service_advices: bool,
|
have_service_advices: bool,
|
||||||
|
|
||||||
#[do_not_track]
|
#[do_not_track]
|
||||||
advices_factory: FactoryVecDeque<crate::advices::AppAdvice>,
|
advices_component: AsyncController<AdvicesView>,
|
||||||
advices_state: ReadyAdvicesState,
|
|
||||||
|
|
||||||
#[do_not_track]
|
#[do_not_track]
|
||||||
tracking_factory: FactoryHashMap<String, crate::tracking::ShipmentView>,
|
tracking_factory: FactoryHashMap<String, crate::tracking::ShipmentView>,
|
||||||
}
|
}
|
||||||
|
@ -55,8 +48,6 @@ pub enum ReadyCmds {
|
||||||
LoggedIn,
|
LoggedIn,
|
||||||
LoggedOut,
|
LoggedOut,
|
||||||
GotCustomerDataFull(LibraryResult<libpaket::stammdaten::CustomerDataFull>),
|
GotCustomerDataFull(LibraryResult<libpaket::stammdaten::CustomerDataFull>),
|
||||||
RetryAdvices,
|
|
||||||
GotAdvices((LibraryResult<Vec<AppAdviceMetadata>>, Option<UatToken>)),
|
|
||||||
GotTracking(LibraryResult<Vec<Shipment>>),
|
GotTracking(LibraryResult<Vec<Shipment>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,48 +70,10 @@ impl Component for Ready {
|
||||||
view! {
|
view! {
|
||||||
#[root]
|
#[root]
|
||||||
adw::ViewStack {
|
adw::ViewStack {
|
||||||
add = &adw::Bin {
|
add = model.advices_component.widget() -> >k::ScrolledWindow {
|
||||||
#[wrap(Some)]
|
/*#[track(model.changed_have_service_advices())]
|
||||||
set_child = &adw::ViewStack {
|
set_visible: model.have_service_advices,*/
|
||||||
#[name = "advices_page_loading"]
|
|
||||||
add = &adw::StatusPage {
|
|
||||||
set_title: "Loading mail notifications...",
|
|
||||||
|
|
||||||
},
|
|
||||||
#[name = "advices_page_no_available"]
|
|
||||||
add = &adw::StatusPage {
|
|
||||||
set_title: "No mail notifications available."
|
|
||||||
},
|
|
||||||
#[name = "advices_page_have_some"]
|
|
||||||
add = &adw::Clamp {
|
|
||||||
#[wrap(Some)]
|
|
||||||
set_child = >k::Box {
|
|
||||||
set_orientation: gtk::Orientation::Horizontal,
|
|
||||||
|
|
||||||
#[local_ref]
|
|
||||||
advices_carousel -> adw::Carousel {
|
|
||||||
set_orientation: gtk::Orientation::Vertical,
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
adw::CarouselIndicatorDots {
|
|
||||||
#[watch]
|
|
||||||
set_carousel: Some(advices_carousel),
|
|
||||||
set_orientation: gtk::Orientation::Vertical,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
#[track(model.changed_advices_state())]
|
|
||||||
set_visible_child: {
|
|
||||||
let page: >k::Widget = match model.advices_state {
|
|
||||||
ReadyAdvicesState::Loading => advices_page_loading.upcast_ref::<gtk::Widget>(),
|
|
||||||
ReadyAdvicesState::HaveNone => advices_page_no_available.upcast_ref::<gtk::Widget>(),
|
|
||||||
ReadyAdvicesState::HaveSome => advices_page_have_some.upcast_ref::<gtk::Widget>(),
|
|
||||||
};
|
|
||||||
page
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} -> /*page_advices: adw::ViewStackPage*/ {
|
} -> /*page_advices: adw::ViewStackPage*/ {
|
||||||
set_title: Some("Mail notification"),
|
set_title: Some("Mail notification"),
|
||||||
set_name: Some("page_advices"),
|
set_name: Some("page_advices"),
|
||||||
|
@ -157,7 +110,7 @@ impl Component for Ready {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} -> /*page_tracking: adw::ViewStackPage*/ {
|
} -> {
|
||||||
set_title: Some("Shipment tracking"),
|
set_title: Some("Shipment tracking"),
|
||||||
set_name: Some("page_tracking"),
|
set_name: Some("page_tracking"),
|
||||||
},
|
},
|
||||||
|
@ -169,21 +122,18 @@ impl Component for Ready {
|
||||||
root: Self::Root,
|
root: Self::Root,
|
||||||
sender: ComponentSender<Self>,
|
sender: ComponentSender<Self>,
|
||||||
) -> ComponentParts<Self> {
|
) -> ComponentParts<Self> {
|
||||||
let advices_factory = FactoryVecDeque::builder().launch_default().detach();
|
|
||||||
|
|
||||||
let tracking_factory = FactoryHashMap::builder().launch_default().detach();
|
let tracking_factory = FactoryHashMap::builder().launch_default().detach();
|
||||||
|
let advices_component = AdvicesView::builder().launch(init.clone()).detach();
|
||||||
|
|
||||||
let model = Ready {
|
let model = Ready {
|
||||||
have_service_advices: false,
|
have_service_advices: false,
|
||||||
advices_factory,
|
|
||||||
advices_state: ReadyAdvicesState::Loading,
|
|
||||||
login: init.clone(),
|
login: init.clone(),
|
||||||
activate: false,
|
activate: false,
|
||||||
tracking_factory,
|
tracking_factory,
|
||||||
|
advices_component,
|
||||||
tracker: 0,
|
tracker: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let advices_carousel = model.advices_factory.widget();
|
|
||||||
let tracking_box = model.tracking_factory.widget();
|
let tracking_box = model.tracking_factory.widget();
|
||||||
|
|
||||||
let widgets = view_output!();
|
let widgets = view_output!();
|
||||||
|
@ -280,38 +230,8 @@ impl Component for Ready {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ReadyInput::HaveAdvicesService => {
|
ReadyInput::HaveAdvicesService => {
|
||||||
let token = self.login.clone();
|
self.have_service_advices = true;
|
||||||
sender.oneshot_command(async move {
|
self.advices_component.emit(AdvicesViewInput::Fetch);
|
||||||
// fetching advices
|
|
||||||
let dhli_token = crate::login::get_id_token(&token).await.unwrap();
|
|
||||||
let advices = libpaket::WebClient::new().advices(&dhli_token).await;
|
|
||||||
let advices = match advices {
|
|
||||||
Ok(res) => res,
|
|
||||||
Err(err) => return ReadyCmds::GotAdvices((Err(err), None)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut advices_vec = Vec::new();
|
|
||||||
|
|
||||||
if let Some(current) = advices.get_current_advice() {
|
|
||||||
push_advice_from_libpaket_advice(&mut advices_vec, current);
|
|
||||||
}
|
|
||||||
|
|
||||||
for old_n in advices.get_old_advices() {
|
|
||||||
push_advice_from_libpaket_advice(&mut advices_vec, old_n);
|
|
||||||
}
|
|
||||||
|
|
||||||
if advices_vec.len() == 0 {
|
|
||||||
return ReadyCmds::GotAdvices((Ok(advices_vec), None));
|
|
||||||
}
|
|
||||||
|
|
||||||
match libpaket::advices::AdviceClient::new()
|
|
||||||
.access_token(&advices)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(uat_token) => ReadyCmds::GotAdvices((Ok(advices_vec), Some(uat_token))),
|
|
||||||
Err(err) => ReadyCmds::GotAdvices((Err(err), None)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -403,48 +323,7 @@ impl Component for Ready {
|
||||||
sender.output(ReadyOutput::Error(err)).unwrap();
|
sender.output(ReadyOutput::Error(err)).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ReadyCmds::GotAdvices(res) => match res.0 {
|
|
||||||
Ok(advices_vec) => {
|
|
||||||
{
|
|
||||||
let mut guard = self.advices_factory.guard();
|
|
||||||
guard.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
if advices_vec.len() == 0 {
|
|
||||||
self.set_advices_state(ReadyAdvicesState::HaveNone);
|
|
||||||
} else {
|
|
||||||
self.set_advices_state(ReadyAdvicesState::HaveSome);
|
|
||||||
let uat_token = res.1.unwrap();
|
|
||||||
let mut guard = self.advices_factory.guard();
|
|
||||||
guard.clear();
|
|
||||||
for i in advices_vec {
|
|
||||||
guard.push_back((i, uat_token.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
sender.output(ReadyOutput::Error(err)).unwrap();
|
|
||||||
sender.oneshot_command(async {
|
|
||||||
relm4::tokio::time::sleep(Duration::from_secs(30)).await;
|
|
||||||
ReadyCmds::RetryAdvices
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReadyCmds::RetryAdvices => {
|
|
||||||
sender.input(ReadyInput::HaveAdvicesService);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_advice_from_libpaket_advice(
|
|
||||||
vec: &mut Vec<AppAdviceMetadata>,
|
|
||||||
libpaket_advice: &AdvicesList,
|
|
||||||
) {
|
|
||||||
for i in &libpaket_advice.list {
|
|
||||||
vec.push(AppAdviceMetadata {
|
|
||||||
date: libpaket_advice.date.clone(),
|
|
||||||
advice: i.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue