feat: paket: account page
This commit is contained in:
parent
b9fb7fcea4
commit
31698154e0
7 changed files with 283 additions and 94 deletions
|
@ -1,3 +1,3 @@
|
|||
app_id = "de.j4ne.Paket"
|
||||
|
||||
icons = ["plus", "minus", "package-x-generic", "mail", "loupe-large"]
|
||||
icons = ["plus", "minus", "package-x-generic", "mail", "loupe-large", "person", "copy"]
|
214
paket/src/account.rs
Normal file
214
paket/src/account.rs
Normal file
|
@ -0,0 +1,214 @@
|
|||
use adw::prelude::*;
|
||||
use gtk::gdk;
|
||||
use libpaket::{stammdaten::CustomerDataFull, LibraryError, LibraryResult};
|
||||
use relm4::{Component, ComponentParts};
|
||||
|
||||
use crate::LoginSharedState;
|
||||
|
||||
#[tracker::track]
|
||||
pub struct AccountView {
|
||||
logged_in: bool,
|
||||
|
||||
#[do_not_track]
|
||||
login: LoginSharedState,
|
||||
#[no_eq]
|
||||
customer_data_full: Option<CustomerDataFull>,
|
||||
}
|
||||
|
||||
// We have to handle both events, as we want to reset everything, if we log out.
|
||||
#[derive(Debug)]
|
||||
pub enum AccountCmd {
|
||||
LoggedIn,
|
||||
LoggedOut,
|
||||
GotCustomerDataFull(LibraryResult<CustomerDataFull>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum CopyTargets {
|
||||
PostNumber,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AccountInput {
|
||||
Copy(CopyTargets),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AccountServices {
|
||||
Advices,
|
||||
SendungVerfolgung,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AccountOutput {
|
||||
LoggedOut,
|
||||
LoggedIn,
|
||||
HaveService(AccountServices),
|
||||
}
|
||||
|
||||
macro_rules! get_str_from_customer_data {
|
||||
($model: ident, $id: ident) => {{
|
||||
|| -> Option<String> {
|
||||
let data = $model.customer_data_full.as_ref()?;
|
||||
Some(data.common.$id.clone())
|
||||
}()
|
||||
}};
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl Component for AccountView {
|
||||
type Input = AccountInput;
|
||||
type Output = AccountOutput;
|
||||
type Init = LoginSharedState;
|
||||
type CommandOutput = AccountCmd;
|
||||
|
||||
view! {
|
||||
#[root]
|
||||
adw::Bin {
|
||||
#[wrap(Some)]
|
||||
set_child = >k::ListBox {
|
||||
// General infos
|
||||
append = &adw::PreferencesGroup {
|
||||
#[track(model.changed_customer_data_full() && model.get_customer_data_full().is_some())]
|
||||
set_title?: get_str_from_customer_data!(model, display_name).as_ref(),
|
||||
|
||||
// Postnumber
|
||||
add = &adw::ActionRow {
|
||||
#[track(model.changed_customer_data_full() && model.get_customer_data_full().is_some())]
|
||||
set_title?: get_str_from_customer_data!(model, post_number).as_ref(),
|
||||
|
||||
set_subtitle: "Postnummer",
|
||||
|
||||
add_suffix = >k::Button {
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ButtonContent {
|
||||
set_icon_name: relm4_icons::icon_names::COPY,
|
||||
set_label: "Copy",
|
||||
},
|
||||
|
||||
connect_clicked => AccountInput::Copy(CopyTargets::PostNumber),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn init(
|
||||
init: Self::Init,
|
||||
root: Self::Root,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
) -> relm4::ComponentParts<Self> {
|
||||
let model = AccountView {
|
||||
logged_in: false,
|
||||
login: init,
|
||||
tracker: 0,
|
||||
customer_data_full: None,
|
||||
};
|
||||
|
||||
{
|
||||
let login = model.login.clone();
|
||||
sender.command(move |out, shutdown| {
|
||||
shutdown
|
||||
.register(async move {
|
||||
let login = { login.clone().as_ref().lock().await.clone() };
|
||||
let (sender, receiver) = relm4::channel::<AccountCmd>();
|
||||
login.subscribe(&sender, |model| match model {
|
||||
Some(_) => AccountCmd::LoggedIn,
|
||||
None => AccountCmd::LoggedOut,
|
||||
});
|
||||
loop {
|
||||
out.send(receiver.recv().await.unwrap()).unwrap();
|
||||
}
|
||||
})
|
||||
.drop_on_shutdown()
|
||||
});
|
||||
}
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
message: Self::Input,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
self.reset();
|
||||
|
||||
match message {
|
||||
AccountInput::Copy(target) => {
|
||||
let value = match target {
|
||||
CopyTargets::PostNumber => {
|
||||
get_str_from_customer_data!(self, post_number)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(value) = value {
|
||||
let display = root.display();
|
||||
let clipboard = display.clipboard();
|
||||
clipboard.set_text(value.as_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn update_cmd(
|
||||
&mut self,
|
||||
message: Self::CommandOutput,
|
||||
sender: relm4::ComponentSender<Self>,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
self.reset();
|
||||
|
||||
match message {
|
||||
AccountCmd::LoggedIn => {
|
||||
self.set_logged_in(true);
|
||||
}
|
||||
AccountCmd::LoggedOut => {
|
||||
self.set_logged_in(false);
|
||||
}
|
||||
AccountCmd::GotCustomerDataFull(data) => match data {
|
||||
Ok(data) => {
|
||||
for service in &data.common.services {
|
||||
match service {
|
||||
libpaket::stammdaten::CustomerDataService::Packstation => (),
|
||||
libpaket::stammdaten::CustomerDataService::Paketankuendigung => sender
|
||||
.output(AccountOutput::HaveService(
|
||||
AccountServices::SendungVerfolgung,
|
||||
))
|
||||
.unwrap(),
|
||||
libpaket::stammdaten::CustomerDataService::Briefankuendigung => sender
|
||||
.output(AccountOutput::HaveService(AccountServices::Advices))
|
||||
.unwrap(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.set_customer_data_full(Some(data));
|
||||
}
|
||||
Err(err) => todo!(),
|
||||
},
|
||||
}
|
||||
|
||||
if self.changed_logged_in() {
|
||||
if self.logged_in {
|
||||
sender.output(AccountOutput::LoggedIn).unwrap();
|
||||
let token = self.login.clone();
|
||||
sender.oneshot_command(async move {
|
||||
let token = crate::login::get_id_token(&token).await.unwrap();
|
||||
let client = libpaket::StammdatenClient::new();
|
||||
let mut res: LibraryResult<CustomerDataFull> = Err(LibraryError::NetworkFetch);
|
||||
while res.is_err() && *res.as_ref().err().unwrap() == LibraryError::NetworkFetch
|
||||
{
|
||||
res = client.customer_data_full(&token).await;
|
||||
}
|
||||
AccountCmd::GotCustomerDataFull(res)
|
||||
});
|
||||
} else {
|
||||
sender.output(AccountOutput::LoggedOut).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -101,7 +101,7 @@ impl AsyncComponent for App {
|
|||
.forward(sender.input_sender(), convert_ready_response);
|
||||
|
||||
let login = Login::builder()
|
||||
.launch(login_shared_state.clone())
|
||||
.launch_with_broker(login_shared_state.clone(), &paket::LOGIN_BROKER)
|
||||
.forward(sender.input_sender(), convert_login_response);
|
||||
|
||||
let model = App {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
pub mod account;
|
||||
pub mod advice;
|
||||
pub mod advices;
|
||||
pub mod constants;
|
||||
|
@ -6,3 +7,8 @@ pub mod ready;
|
|||
pub mod tracking;
|
||||
|
||||
pub use login::LoginSharedState;
|
||||
|
||||
pub static LOGIN_BROKER: relm4::MessageBroker<login::LoginInput> = relm4::MessageBroker::new();
|
||||
pub fn send_log_out() {
|
||||
LOGIN_BROKER.send(login::LoginInput::LogOut);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ static KEYRING: OnceLock<oo7::Keyring> = OnceLock::new();
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum LoginInput {
|
||||
LogOut,
|
||||
NeedsLogin,
|
||||
NeedsRefresh,
|
||||
ReceivedAuthCode(String),
|
||||
|
@ -194,6 +195,18 @@ impl AsyncComponent for Login {
|
|||
self.reset();
|
||||
|
||||
match message {
|
||||
LoginInput::LogOut => {
|
||||
self.refresh_token = None;
|
||||
sender.output(LoginOutput::RequiresLogin).unwrap();
|
||||
{
|
||||
let token = self.shared_id_token.lock().await;
|
||||
*token.write() = None;
|
||||
}
|
||||
let keyring = KEYRING.get().unwrap();
|
||||
let _ = keyring
|
||||
.delete(&HashMap::from([("app", crate::constants::APP_ID)]))
|
||||
.await;
|
||||
}
|
||||
LoginInput::NeedsRefresh => {
|
||||
let refresh_token = self.refresh_token.as_ref().unwrap().clone();
|
||||
sender.oneshot_command(async {
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
use adw::prelude::*;
|
||||
use libpaket::{stammdaten::CustomerDataFull, LibraryError, LibraryResult};
|
||||
use relm4::prelude::*;
|
||||
|
||||
use crate::{
|
||||
account::{AccountOutput, AccountServices, AccountView},
|
||||
advices::{AdvicesView, AdvicesViewInput},
|
||||
tracking::{TrackingInput, TrackingOutput, TrackingView},
|
||||
};
|
||||
|
||||
#[tracker::track]
|
||||
pub struct Ready {
|
||||
#[do_not_track]
|
||||
login: crate::LoginSharedState,
|
||||
activate: bool,
|
||||
logged_in: bool,
|
||||
have_service_advices: bool,
|
||||
have_service_tracking: bool,
|
||||
|
||||
#[do_not_track]
|
||||
account_component: Controller<AccountView>,
|
||||
#[do_not_track]
|
||||
advices_component: AsyncController<AdvicesView>,
|
||||
#[do_not_track]
|
||||
|
@ -33,21 +33,14 @@ pub enum ReadyOutput {
|
|||
pub enum ReadyCmds {
|
||||
LoggedIn,
|
||||
LoggedOut,
|
||||
GotCustomerDataFull(LibraryResult<libpaket::stammdaten::CustomerDataFull>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Services {
|
||||
Advices,
|
||||
SendungVerfolgung,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ReadyInput {
|
||||
Activate,
|
||||
Deactivate,
|
||||
HaveService(Services),
|
||||
ServiceBorked(Services),
|
||||
LoggedIn,
|
||||
LoggedOut,
|
||||
HaveService(AccountServices),
|
||||
ServiceBorked(AccountServices),
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
|
@ -106,6 +99,13 @@ impl Component for Ready {
|
|||
#[track(model.changed_have_service_tracking())]
|
||||
set_visible: model.have_service_tracking,
|
||||
},
|
||||
|
||||
add = &model.account_component.widget().clone() -> adw::Bin {
|
||||
} -> page_account: adw::ViewStackPage {
|
||||
set_title: Some("Account"),
|
||||
set_name: Some("page_account"),
|
||||
set_icon_name: Some(relm4_icons::icon_names::PERSON)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -132,41 +132,28 @@ impl Component for Ready {
|
|||
.launch(init.clone())
|
||||
.forward(&sender.input_sender(), convert_tracking_output);
|
||||
|
||||
let account_component = AccountView::builder()
|
||||
.launch(init.clone())
|
||||
.forward(&sender.input_sender(), convert_account_output);
|
||||
|
||||
let toast_overlay = adw::ToastOverlay::new();
|
||||
|
||||
let model = Ready {
|
||||
have_service_advices: false,
|
||||
have_service_tracking: true,
|
||||
login: init.clone(),
|
||||
activate: false,
|
||||
logged_in: false,
|
||||
|
||||
account_component,
|
||||
advices_component,
|
||||
tracking_component,
|
||||
toast_overlay,
|
||||
|
||||
tracker: 0,
|
||||
};
|
||||
|
||||
{
|
||||
let login = model.login.clone();
|
||||
sender.command(move |out, shutdown| {
|
||||
shutdown
|
||||
.register(async move {
|
||||
let login = { login.clone().as_ref().lock().await.clone() };
|
||||
let (sender, receiver) = relm4::channel::<ReadyCmds>();
|
||||
login.subscribe(&sender, |model| match model {
|
||||
Some(_) => ReadyCmds::LoggedIn,
|
||||
None => ReadyCmds::LoggedOut,
|
||||
});
|
||||
loop {
|
||||
out.send(receiver.recv().await.unwrap()).unwrap();
|
||||
}
|
||||
})
|
||||
.drop_on_shutdown()
|
||||
});
|
||||
}
|
||||
|
||||
let breakpoint = adw::Breakpoint::new(adw::BreakpointCondition::new_length(
|
||||
adw::BreakpointConditionLengthType::MaxWidth,
|
||||
450.0,
|
||||
550.0,
|
||||
adw::LengthUnit::Sp,
|
||||
));
|
||||
|
||||
|
@ -193,39 +180,24 @@ impl Component for Ready {
|
|||
self.reset();
|
||||
|
||||
match message {
|
||||
ReadyInput::Activate => {
|
||||
self.set_activate(true);
|
||||
if self.changed_activate() {
|
||||
let token = self.login.clone();
|
||||
sender.oneshot_command(async move {
|
||||
let token = crate::login::get_id_token(&token).await.unwrap();
|
||||
let client = libpaket::StammdatenClient::new();
|
||||
let mut res: LibraryResult<CustomerDataFull> =
|
||||
Err(LibraryError::NetworkFetch);
|
||||
while res.is_err()
|
||||
&& *res.as_ref().err().unwrap() == LibraryError::NetworkFetch
|
||||
{
|
||||
res = client.customer_data_full(&token).await;
|
||||
ReadyInput::LoggedIn => {
|
||||
self.set_logged_in(true);
|
||||
}
|
||||
ReadyCmds::GotCustomerDataFull(res)
|
||||
});
|
||||
}
|
||||
}
|
||||
ReadyInput::Deactivate => {
|
||||
self.set_activate(false);
|
||||
ReadyInput::LoggedOut => {
|
||||
self.set_logged_in(false);
|
||||
}
|
||||
ReadyInput::HaveService(service) => match service {
|
||||
Services::Advices => {
|
||||
AccountServices::Advices => {
|
||||
self.set_have_service_advices(true);
|
||||
self.advices_component.emit(AdvicesViewInput::Fetch);
|
||||
}
|
||||
Services::SendungVerfolgung => {
|
||||
AccountServices::SendungVerfolgung => {
|
||||
self.set_have_service_tracking(true);
|
||||
self.tracking_component.emit(TrackingInput::Search(None))
|
||||
}
|
||||
},
|
||||
ReadyInput::ServiceBorked(service) => match service {
|
||||
Services::Advices => {
|
||||
AccountServices::Advices => {
|
||||
self.toast_overlay.add_toast(
|
||||
adw::Toast::builder()
|
||||
.title("Service borked: Mail notifications")
|
||||
|
@ -234,7 +206,7 @@ impl Component for Ready {
|
|||
);
|
||||
self.set_have_service_advices(false);
|
||||
}
|
||||
Services::SendungVerfolgung => {
|
||||
AccountServices::SendungVerfolgung => {
|
||||
self.toast_overlay.add_toast(
|
||||
adw::Toast::builder()
|
||||
.title("Service borked: Shipment tracking")
|
||||
|
@ -244,43 +216,28 @@ impl Component for Ready {
|
|||
self.set_have_service_tracking(false);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn update_cmd(
|
||||
&mut self,
|
||||
message: Self::CommandOutput,
|
||||
sender: ComponentSender<Self>,
|
||||
_: &Self::Root,
|
||||
) {
|
||||
match message {
|
||||
ReadyCmds::LoggedIn => sender.input(ReadyInput::Activate),
|
||||
ReadyCmds::LoggedOut => sender.input(ReadyInput::Deactivate),
|
||||
ReadyCmds::GotCustomerDataFull(res) => match res {
|
||||
Ok(res) => {
|
||||
for service in &res.common.services {
|
||||
match service {
|
||||
libpaket::stammdaten::CustomerDataService::Packstation => (),
|
||||
libpaket::stammdaten::CustomerDataService::Paketankuendigung => {
|
||||
sender.input(ReadyInput::HaveService(Services::SendungVerfolgung))
|
||||
if self.changed_logged_in() {
|
||||
if self.logged_in {
|
||||
sender.output(ReadyOutput::Ready).unwrap();
|
||||
} else {
|
||||
todo!();
|
||||
}
|
||||
libpaket::stammdaten::CustomerDataService::Briefankuendigung => {
|
||||
sender.input(ReadyInput::HaveService(Services::Advices))
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
sender.output(ReadyOutput::Ready).unwrap()
|
||||
}
|
||||
Err(err) => todo!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_tracking_output(value: TrackingOutput) -> ReadyInput {
|
||||
match value {
|
||||
TrackingOutput::Borked => ReadyInput::ServiceBorked(Services::SendungVerfolgung),
|
||||
TrackingOutput::Borked => ReadyInput::ServiceBorked(AccountServices::SendungVerfolgung),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_account_output(value: AccountOutput) -> ReadyInput {
|
||||
match value {
|
||||
AccountOutput::LoggedOut => ReadyInput::LoggedOut,
|
||||
AccountOutput::LoggedIn => ReadyInput::LoggedIn,
|
||||
AccountOutput::HaveService(service) => ReadyInput::HaveService(service),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ use libpaket::tracking::{Shipment, TrackingParams};
|
|||
use libpaket::{LibraryError, LibraryResult};
|
||||
use relm4::factory::{FactoryComponent, FactoryHashMap};
|
||||
use relm4::prelude::*;
|
||||
use relm4::{adw, gtk};
|
||||
|
||||
use crate::login::get_id_token;
|
||||
use crate::LoginSharedState;
|
||||
|
|
Loading…
Reference in a new issue