neveko/neveko-gui/src/apps/market.rs

1231 lines
55 KiB
Rust

use neveko_core::*;
use std::sync::mpsc::{
Receiver,
Sender,
};
pub struct MultisigManagement {
pub exchange_multisig_keys: String,
pub export_info: String,
pub has_mediator: bool,
pub make_info: String,
pub mediator: String,
pub prepare_info: String,
pub query_mediator: bool,
pub signed_txset: String,
pub vendor: String,
}
impl Default for MultisigManagement {
fn default() -> Self {
MultisigManagement {
exchange_multisig_keys: utils::empty_string(),
export_info: utils::empty_string(),
has_mediator: false,
make_info: utils::empty_string(),
mediator: utils::empty_string(),
prepare_info: utils::empty_string(),
query_mediator: false,
signed_txset: utils::empty_string(),
vendor: utils::empty_string(),
}
}
}
pub struct MarketApp {
contact_info_tx: Sender<models::Contact>,
contact_info_rx: Receiver<models::Contact>,
contact_timeout_tx: Sender<bool>,
contact_timeout_rx: Receiver<bool>,
customer_orders: Vec<models::Order>,
find_vendor: String,
get_vendor_products_tx: Sender<Vec<models::Product>>,
get_vendor_products_rx: Receiver<Vec<models::Product>>,
get_vendor_product_tx: Sender<models::Product>,
get_vendor_product_rx: Receiver<models::Product>,
is_loading: bool,
is_ordering: bool,
is_pinging: bool,
is_customer_viewing_orders: bool,
is_managing_multisig: bool,
is_product_image_set: bool,
is_showing_products: bool,
is_showing_product_image: bool,
is_showing_product_update: bool,
is_showing_orders: bool,
is_showing_vendor_status: bool,
is_showing_vendors: bool,
is_timeout: bool,
is_vendor_enabled: bool,
is_window_shopping: bool,
msig: MultisigManagement,
/// order currently being acted on
m_order: models::Order,
orders: Vec<models::Order>,
product_from_vendor: models::Product,
product_image: egui_extras::RetainedImage,
products: Vec<models::Product>,
product_update_pid: String,
new_product_image: String,
new_product_name: String,
new_product_desc: String,
new_product_price: String,
new_product_qty: String,
new_order: models::Order,
new_order_price: u128,
new_order_quantity: String,
new_order_shipping_address: String,
_refresh_on_delete_product_tx: Sender<bool>,
_refresh_on_delete_product_rx: Receiver<bool>,
submit_order_tx: Sender<models::Order>,
submit_order_rx: Receiver<models::Order>,
s_contact: models::Contact,
s_order: models::Order,
vendor_status: utils::ContactStatus,
vendors: Vec<models::Contact>,
}
impl Default for MarketApp {
fn default() -> Self {
let (contact_timeout_tx, contact_timeout_rx) = std::sync::mpsc::channel();
let (_refresh_on_delete_product_tx, _refresh_on_delete_product_rx) =
std::sync::mpsc::channel();
let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
let s = db::Interface::open();
let r = db::Interface::read(&s.env, &s.handle, contact::NEVEKO_VENDOR_ENABLED);
let is_vendor_enabled = r == contact::NEVEKO_VENDOR_MODE_ON;
let (contact_info_tx, contact_info_rx) = std::sync::mpsc::channel();
let (get_vendor_products_tx, get_vendor_products_rx) = std::sync::mpsc::channel();
let (get_vendor_product_tx, get_vendor_product_rx) = std::sync::mpsc::channel();
let (submit_order_tx, submit_order_rx) = std::sync::mpsc::channel();
MarketApp {
contact_info_rx,
contact_info_tx,
contact_timeout_rx,
contact_timeout_tx,
customer_orders: Vec::new(),
find_vendor: utils::empty_string(),
get_vendor_products_rx,
get_vendor_products_tx,
get_vendor_product_rx,
get_vendor_product_tx,
is_customer_viewing_orders: false,
is_loading: false,
is_managing_multisig: false,
is_ordering: false,
is_pinging: false,
is_product_image_set: false,
is_showing_orders: false,
is_showing_products: false,
is_showing_product_image: false,
is_showing_product_update: false,
is_showing_vendor_status: false,
is_showing_vendors: false,
is_timeout: false,
is_vendor_enabled,
is_window_shopping: false,
msig: Default::default(),
m_order: Default::default(),
new_order: Default::default(),
new_order_price: 0,
new_order_shipping_address: utils::empty_string(),
new_order_quantity: utils::empty_string(),
orders: Vec::new(),
product_from_vendor: Default::default(),
product_image: egui_extras::RetainedImage::from_image_bytes(
"qr.png",
&read_product_image,
)
.unwrap(),
products: Vec::new(),
product_update_pid: utils::empty_string(),
new_product_image: utils::empty_string(),
new_product_name: utils::empty_string(),
new_product_desc: utils::empty_string(),
new_product_price: utils::empty_string(),
new_product_qty: utils::empty_string(),
_refresh_on_delete_product_tx,
_refresh_on_delete_product_rx,
s_contact: Default::default(),
s_order: Default::default(),
submit_order_rx,
submit_order_tx,
vendor_status: Default::default(),
vendors: Vec::new(),
}
}
}
impl eframe::App for MarketApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
// Hook into async channel threads
//-----------------------------------------------------------------------------------
if let Ok(submit_order) = self.submit_order_rx.try_recv() {
self.s_order = submit_order;
if self.s_order.orid != utils::empty_string() {
self.is_ordering = false;
self.is_loading = false;
}
}
if let Ok(contact_info) = self.contact_info_rx.try_recv() {
self.s_contact = contact_info;
if self.s_contact.xmr_address != utils::empty_string() {
self.is_pinging = false;
self.vendor_status.is_vendor = self.s_contact.is_vendor;
}
}
if let Ok(vendor_products) = self.get_vendor_products_rx.try_recv() {
self.is_loading = false;
self.products = vendor_products;
}
if let Ok(vendor_product) = self.get_vendor_product_rx.try_recv() {
self.is_loading = false;
if !vendor_product.image.is_empty() {
// only pull image from vendor when we want to view
let file_path = format!(
"/home/{}/.neveko/{}.jpeg",
std::env::var("USER").unwrap_or(String::from("user")),
vendor_product.pid
);
if self.is_window_shopping {
match std::fs::write(&file_path, &vendor_product.image) {
Ok(w) => w,
Err(_) => {
log::error!("failed to write product image")
}
};
self.is_loading = true;
let contents = std::fs::read(&file_path).unwrap_or(Vec::new());
// this image should uwrap if vendor image bytes are
// bad
let default_img = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
let default_r_img =
egui_extras::RetainedImage::from_image_bytes("qr.png", &default_img)
.unwrap();
self.product_image =
egui_extras::RetainedImage::from_image_bytes(file_path, &contents)
.unwrap_or(default_r_img);
}
}
self.product_from_vendor = vendor_product;
self.is_product_image_set = true;
self.is_showing_product_image = true;
self.is_loading = false;
}
if let Ok(timeout) = self.contact_timeout_rx.try_recv() {
self.is_timeout = true;
if timeout {
self.is_pinging = false;
}
}
// Vendor status window
//-----------------------------------------------------------------------------------
let mut is_showing_vendor_status = self.is_showing_vendor_status;
egui::Window::new("vendor status")
.title_bar(false)
.open(&mut is_showing_vendor_status)
.vscroll(true)
.id(egui::Id::new(self.vendor_status.i2p.clone()))
.show(&ctx, |ui| {
if self.is_pinging {
ui.add(egui::Spinner::new());
ui.label("pinging...");
}
let status = if self.s_contact.xmr_address != utils::empty_string() {
"online"
} else {
"offline"
};
let mode = if self.vendor_status.is_vendor {
"enabled "
} else {
"disabled"
};
ui.label(format!("status: {}", status));
ui.label(format!("vendor mode: {}", mode));
ui.label(format!("nick: {}", self.vendor_status.nick));
ui.label(format!("tx proof: {}", self.vendor_status.txp));
ui.label(format!("jwp: {}", self.vendor_status.jwp));
ui.label(format!("expiration: {}", self.vendor_status.h_exp));
ui.label(format!("signed key: {}", self.vendor_status.signed_key));
if ui.button("Exit").clicked() {
self.is_showing_vendor_status = false;
}
});
// Product image window
//-----------------------------------------------------------------------------------
let mut is_showing_product_image = self.is_showing_product_image;
egui::Window::new("product image")
.open(&mut is_showing_product_image)
.title_bar(false)
.vscroll(true)
.show(ctx, |ui| {
self.product_image.show(ui);
if ui.button("Exit").clicked() {
self.is_showing_product_image = false;
self.is_product_image_set = false;
let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
self.product_image =
egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image)
.unwrap();
}
});
// Multisig Management window
//-----------------------------------------------------------------------------------
let mut is_managing_multisig = self.is_managing_multisig;
egui::Window::new("msig")
.open(&mut is_managing_multisig)
.title_bar(false)
.vscroll(true)
.show(ctx, |ui| {
ui.heading("Multisig Management");
ui.horizontal(|ui| {
let mediator = ui.label("Mediator: ");
let prefix = String::from(crate::GUI_MSIG_MEDIATOR_DB_KEY);
if !self.msig.query_mediator {
let mediator_db = utils::search_gui_db(String::from(&prefix), self.m_order.orid.clone());
log::debug!("mediator db: {}", mediator_db);
self.msig.has_mediator = mediator_db != utils::empty_string();
self.msig.mediator = mediator_db;
self.msig.query_mediator = true;
} else if self.msig.query_mediator && !self.msig.has_mediator {
ui.text_edit_singleline(&mut self.msig.mediator)
.labelled_by(mediator.id);
ui.label("\t");
if ui.button("Set Mediator").clicked() {
utils::write_gui_db(prefix, self.m_order.orid.clone(), self.msig.mediator.clone());
self.msig.has_mediator = true;
}
} else {
ui.label(self.msig.mediator.clone());
ui.label("\t");
if ui.button("Clear Mediator").clicked() {
utils::clear_gui_db(prefix, self.m_order.orid.clone());
self.msig.mediator = utils::empty_string();
self.msig.has_mediator = false;
self.msig.query_mediator = false;
}
}
});
ui.horizontal(|ui| {
ui.label("Prepare: \t\t\t\t\t");
if ui.button("Prepare").clicked() {
}
});
ui.horizontal(|ui| {
ui.label("Make: \t\t\t\t\t\t");
if ui.button("Make").clicked() {
}
});
ui.horizontal(|ui| {
ui.label("Exchange Keys: \t\t");
if ui.button("Exchange").clicked() {
}
});
ui.horizontal(|ui| {
ui.label("Fund:\t\t\t\t\t\t\t");
if ui.button("Fund").clicked() {
}
});
ui.horizontal(|ui| {
ui.label("Export Info: \t\t\t\t");
if ui.button("Export").clicked() {
}
});
ui.horizontal(|ui| {
ui.label("Release Payment: \t");
if ui.button("Sign Txset").clicked() {
}
});
ui.label("\n");
if ui.button("Exit").clicked() {
self.is_managing_multisig = false;
self.is_loading = false;
}
});
// View orders - Customer Order Flow Management
//-----------------------------------------------------------------------------------
let mut is_customer_viewing_orders = self.is_customer_viewing_orders;
egui::Window::new("view orders")
.open(&mut is_customer_viewing_orders)
.title_bar(false)
.vscroll(true)
.show(&ctx, |ui| {
ui.heading("View Orders");
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("orid");
});
header.col(|ui| {
ui.strong("date");
});
header.col(|ui| {
ui.strong("status");
});
header.col(|ui| {
ui.strong("");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for o in &self.customer_orders {
let row_height = 20.0;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label(format!("{}", o.orid));
});
row.col(|ui| {
let h_date =
chrono::NaiveDateTime::from_timestamp_opt(o.date, 0)
.unwrap()
.to_string();
ui.label(format!("{}", h_date));
});
row.col(|ui| {
ui.label(format!("{}", o.status));
});
row.col(|ui| {
if ui.button("MSIG").clicked() {
// dynamically generate buttons for multisig wallet ops
self.is_managing_multisig = true;
self.m_order.orid = String::from(&o.orid);
}
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| {
if ui.button("Cancel").clicked() {
// TODO(c2m): Cancel order logic
}
});
});
});
}
});
if ui.button("Exit").clicked() {
self.is_customer_viewing_orders = false;
self.is_loading = false;
}
});
// Customer Order Form
//-----------------------------------------------------------------------------------
let mut is_ordering = self.is_ordering;
egui::Window::new("order form")
.open(&mut is_ordering)
.title_bar(false)
.vscroll(true)
.show(&ctx, |ui| {
ui.heading("Order Form");
if self.is_loading {
ui.add(egui::Spinner::new());
ui.label("loading...");
}
ui.label(format!("cid: {}", self.new_order.cid));
ui.label(format!("pid: {}", self.new_order.pid));
ui.horizontal(|ui| {
let shipping_name = ui.label("shipping address: ");
ui.text_edit_singleline(&mut self.new_order_shipping_address)
.labelled_by(shipping_name.id);
});
ui.horizontal(|ui| {
let qty_name = ui.label("quantity: \t\t\t\t");
ui.text_edit_singleline(&mut self.new_order_quantity)
.labelled_by(qty_name.id);
});
ui.label(format!("price: {}", self.new_order_price));
let qty = match self.new_order_quantity.parse::<u128>() {
Ok(q) => q,
Err(_) => 0,
};
let mut p_qty: u128 = 0;
for p in &self.products {
if p.pid == self.new_order.pid {
p_qty = p.qty;
break;
}
}
if qty <= p_qty && qty > 0 {
if ui.button("Submit Order").clicked() {
let address_bytes = self.new_order_shipping_address.clone().into_bytes();
let encrypted_shipping_address =
gpg::encrypt(self.vendor_status.i2p.clone(), &address_bytes);
let new_order = reqres::OrderRequest {
cid: String::from(&self.new_order.cid),
pid: String::from(&self.new_order.pid),
ship_address: encrypted_shipping_address.unwrap_or(Vec::new()),
quantity: qty,
..Default::default()
};
log::debug!("new order: {:?}", &new_order);
self.is_loading = true;
submit_order_req(
self.submit_order_tx.clone(),
self.vendor_status.i2p.clone(),
ctx.clone(),
self.vendor_status.jwp.clone(),
new_order,
);
self.new_order = Default::default();
self.new_order_price = 0;
self.new_order_quantity = utils::empty_string();
self.new_order_shipping_address = utils::empty_string();
self.is_showing_products = false;
}
}
ui.label("\n");
if ui.button("Exit").clicked() {
self.is_ordering = false;
self.is_loading = false;
}
});
// View vendors
//-----------------------------------------------------------------------------------
let mut is_showing_vendors = self.is_showing_vendors;
egui::Window::new("vendors")
.open(&mut is_showing_vendors)
.title_bar(false)
.vscroll(true)
.show(&ctx, |ui| {
ui.heading("Vendors");
// Vendor filter
//-----------------------------------------------------------------------------------
ui.heading("\nFind Vendor");
ui.label("\n");
ui.horizontal(|ui| {
let find_vendor_label = ui.label("filter vendors: ");
ui.text_edit_singleline(&mut self.find_vendor)
.labelled_by(find_vendor_label.id);
});
ui.label("\n");
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::remainder())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("Nickname");
});
header.col(|ui| {
ui.strong(".b32.i2p");
});
header.col(|ui| {
ui.strong("");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for v in &self.vendors {
if v.i2p_address.contains(&self.find_vendor) {
let row_height = 20.0;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label("vendor");
});
row.col(|ui| {
ui.label(format!("{}", v.i2p_address));
});
row.col(|ui| {
if ui.button("Check Status").clicked() {
let nick_db = utils::search_gui_db(
String::from(crate::GUI_NICK_DB_KEY),
String::from(&v.i2p_address),
);
let nick = if nick_db == utils::empty_string() {
String::from("anon")
} else {
nick_db
};
self.vendor_status.nick = nick;
self.vendor_status.i2p = String::from(&v.i2p_address);
// get the txp
self.vendor_status.txp = utils::search_gui_db(
String::from(crate::GUI_TX_PROOF_DB_KEY),
String::from(&v.i2p_address),
);
// get the jwp
self.vendor_status.jwp = utils::search_gui_db(
String::from(crate::GUI_JWP_DB_KEY),
String::from(&v.i2p_address),
);
let r_exp = utils::search_gui_db(
String::from(crate::GUI_EXP_DB_KEY),
String::from(&v.i2p_address),
);
self.vendor_status.exp = r_exp;
let expire = match self.vendor_status.exp.parse::<i64>()
{
Ok(n) => n,
Err(_e) => 0,
};
self.vendor_status.h_exp =
chrono::NaiveDateTime::from_timestamp_opt(
expire, 0,
)
.unwrap()
.to_string();
// MESSAGES WON'T BE SENT UNTIL KEY IS SIGNED AND
// TRUSTED!
self.vendor_status.signed_key =
check_signed_key(self.vendor_status.i2p.clone());
send_contact_info_req(
self.contact_info_tx.clone(),
ctx.clone(),
self.vendor_status.i2p.clone(),
contact::Prune::Pruned.value(),
);
vendor_status_timeout(
self.contact_timeout_tx.clone(),
ctx.clone(),
);
self.is_showing_vendor_status = true;
self.is_pinging = true;
}
});
row.col(|ui| {
let now = chrono::offset::Utc::now().timestamp();
let expire = match self.vendor_status.exp.parse::<i64>() {
Ok(n) => n,
Err(_e) => 0,
};
if now < expire
&& self.vendor_status.signed_key
&& self.vendor_status.jwp != utils::empty_string()
&& v.i2p_address == self.vendor_status.i2p
&& self.vendor_status.is_vendor
{
if ui.button("View Products").clicked() {
self.is_loading = true;
send_products_from_vendor_req(
self.get_vendor_products_tx.clone(),
ctx.clone(),
self.vendor_status.i2p.clone(),
self.vendor_status.jwp.clone(),
);
self.is_window_shopping = true;
self.is_showing_products = true;
self.is_showing_vendors = false;
}
}
});
});
}
}
});
if ui.button("Exit").clicked() {
self.is_showing_vendors = false;
}
});
// Vendor status window
//-----------------------------------------------------------------------------------
let mut is_showing_vendor_status = self.is_showing_vendor_status;
egui::Window::new(&self.vendor_status.i2p)
.title_bar(false)
.open(&mut is_showing_vendor_status)
.vscroll(true)
.id(egui::Id::new(self.vendor_status.i2p.clone()))
.show(&ctx, |ui| {
if self.is_pinging {
ui.add(egui::Spinner::new());
ui.label("pinging...");
}
let status = if self.s_contact.xmr_address != utils::empty_string() {
"online"
} else {
"offline"
};
let mode = if self.vendor_status.is_vendor {
"enabled "
} else {
"disabled"
};
ui.label(format!("status: {}", status));
ui.label(format!("vendor mode: {}", mode));
ui.label(format!("nick: {}", self.vendor_status.nick));
ui.label(format!("tx proof: {}", self.vendor_status.txp));
ui.label(format!("jwp: {}", self.vendor_status.jwp));
ui.label(format!("expiration: {}", self.vendor_status.h_exp));
ui.label(format!("signed key: {}", self.vendor_status.signed_key));
if ui.button("Exit").clicked() {
self.is_showing_vendor_status = false;
}
});
// Product image window
//-----------------------------------------------------------------------------------
let mut is_showing_product_image = self.is_showing_product_image;
egui::Window::new("product image")
.open(&mut is_showing_product_image)
.title_bar(false)
.vscroll(true)
.show(ctx, |ui| {
self.product_image.show(ui);
if ui.button("Exit").clicked() {
self.is_showing_product_image = false;
self.is_product_image_set = false;
let read_product_image = std::fs::read("./assets/qr.png").unwrap_or(Vec::new());
self.product_image =
egui_extras::RetainedImage::from_image_bytes("qr.png", &read_product_image)
.unwrap();
}
});
// Products Management window
//-----------------------------------------------------------------------------------
let mut is_showing_products = self.is_showing_products;
egui::Window::new("product management")
.open(&mut is_showing_products)
.title_bar(false)
.vscroll(true)
.show(&ctx, |ui| {
ui.heading("Products");
use egui_extras::{
Column,
TableBuilder,
};
if self.is_loading {
ui.add(egui::Spinner::new());
ui.label("loading...");
}
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::auto())
.column(Column::remainder())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("Name");
});
header.col(|ui| {
ui.strong("Description");
});
header.col(|ui| {
ui.strong("Price");
});
header.col(|ui| {
ui.strong("Quantity");
});
header.col(|ui| {
ui.strong("Image");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for p in &self.products {
let row_height = 20.0;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label(format!("{}", p.name));
});
row.col(|ui| {
ui.label(format!("{}", p.description));
});
row.col(|ui| {
ui.label(format!("{}", p.price));
});
row.col(|ui| {
ui.label(format!("{}", p.qty));
});
row.col(|ui| {
if ui.button("View").clicked() {
if !self.is_product_image_set {
let file_path = format!(
"/home/{}/.neveko/{}.jpeg",
std::env::var("USER")
.unwrap_or(String::from("user")),
p.pid
);
// For the sake of brevity product list doesn't have
// image bytes, get them
if self.is_window_shopping {
self.is_loading = true;
send_product_from_vendor_req(
self.get_vendor_product_tx.clone(),
ctx.clone(),
self.vendor_status.i2p.clone(),
self.vendor_status.jwp.clone(),
String::from(&p.pid),
);
} else {
let i_product = product::find(&p.pid);
match std::fs::write(&file_path, &i_product.image) {
Ok(w) => w,
Err(_) => {
log::error!("failed to write product image")
}
};
let contents =
std::fs::read(&file_path).unwrap_or(Vec::new());
if !i_product.image.is_empty() {
// this image should uwrap if vendor image bytes are
// bad
let default_img = std::fs::read("./assets/qr.png")
.unwrap_or(Vec::new());
let default_r_img =
egui_extras::RetainedImage::from_image_bytes(
"qr.png",
&default_img,
)
.unwrap();
self.product_image =
egui_extras::RetainedImage::from_image_bytes(
file_path, &contents,
)
.unwrap_or(default_r_img);
}
}
if !self.is_window_shopping {
self.is_product_image_set = true;
self.is_showing_product_image = true;
}
ctx.request_repaint();
}
}
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|ui| {
if !self.is_window_shopping {
if ui.button("Update").clicked() {
self.product_update_pid = p.pid.clone();
self.new_product_desc = p.description.clone();
self.new_product_name = p.name.clone();
self.new_product_price = format!("{}", p.price);
self.new_product_qty = format!("{}", p.qty);
self.is_showing_product_update = true;
}
} else {
if ui.button("Create Order").clicked() {
self.new_order.pid = p.pid.clone();
self.new_order.cid = i2p::get_destination(None);
self.new_order_price = p.price;
self.is_ordering = true;
}
}
});
});
});
}
});
if ui.button("Exit").clicked() {
self.is_showing_products = false;
}
});
// Vendor specific
// Update Product window
//-----------------------------------------------------------------------------------
let mut is_showing_product_update = self.is_showing_product_update;
egui::Window::new("update product")
.open(&mut is_showing_product_update)
.title_bar(false)
.vscroll(true)
.show(ctx, |ui| {
ui.heading(format!("Update Product - {}", self.new_product_name));
ui.label(
"____________________________________________________________________________\n",
);
// TODO(c2m): file picker for images
ui.horizontal(|ui| {
let product_name = ui.label("image: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_image)
.labelled_by(product_name.id);
ui.label("\t/path/to/image.png");
});
ui.horizontal(|ui| {
let product_name = ui.label("name: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_name)
.labelled_by(product_name.id);
});
ui.horizontal(|ui| {
let product_desc = ui.label("description: \t");
ui.text_edit_singleline(&mut self.new_product_desc)
.labelled_by(product_desc.id);
});
ui.horizontal(|ui| {
let product_price = ui.label("price: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_price)
.labelled_by(product_price.id);
ui.label("\t (piconeros)")
});
ui.horizontal(|ui| {
let product_qty = ui.label("quantity: \t\t");
ui.text_edit_singleline(&mut self.new_product_qty)
.labelled_by(product_qty.id);
});
ui.label("\n");
if ui.button("Update Product").clicked() {
let image: Vec<u8> = std::fs::read(self.new_product_image.clone()).unwrap_or_default();
let price = match self.new_product_price.parse::<u128>() {
Ok(p) => p,
Err(_) => 0,
};
let qty = match self.new_product_qty.parse::<u128>() {
Ok(q) => q,
Err(_) => 0,
};
let product: models::Product = models::Product {
pid: self.product_update_pid.clone(),
description: self.new_product_desc.clone(),
image,
in_stock: qty > 0,
name: self.new_product_name.clone(),
price,
qty,
};
let j_product = utils::product_to_json(&product);
product::modify(j_product);
self.new_product_desc = utils::empty_string();
self.new_product_name = utils::empty_string();
self.new_product_price = utils::empty_string();
self.new_product_qty = utils::empty_string();
self.new_product_image = utils::empty_string();
self.is_showing_product_update = false;
self.products = product::find_all();
}
if ui.button("Exit").clicked() {
self.new_product_desc = utils::empty_string();
self.new_product_name = utils::empty_string();
self.new_product_price = utils::empty_string();
self.new_product_qty = utils::empty_string();
self.new_product_image = utils::empty_string();
self.is_showing_product_update = false;
}
});
// Vendor Orders window
//-----------------------------------------------------------------------------------
let mut is_showing_orders = self.is_showing_orders;
egui::Window::new("manage orders")
.open(&mut is_showing_orders)
.title_bar(false)
.vscroll(true)
.show(&ctx, |ui| {
ui.heading("Manage Orders");
use egui_extras::{
Column,
TableBuilder,
};
let table = TableBuilder::new(ui)
.striped(true)
.resizable(true)
.cell_layout(egui::Layout::left_to_right(egui::Align::Center))
.column(Column::auto())
.column(Column::initial(100.0).at_least(40.0).clip(true))
.column(Column::initial(100.0).at_least(40.0).clip(true))
.column(Column::initial(100.0).at_least(40.0).clip(true))
.column(Column::remainder())
.min_scrolled_height(0.0);
table
.header(20.0, |mut header| {
header.col(|ui| {
ui.strong("");
});
header.col(|ui| {
ui.strong("");
});
header.col(|ui| {
ui.strong("");
});
header.col(|ui| {
ui.strong("");
});
header.col(|ui| {
ui.strong("");
});
})
.body(|mut body| {
for o in &self.orders {
let row_height = 20.0;
body.row(row_height, |mut row| {
row.col(|ui| {
ui.label(format!("{}", o.cid));
});
row.col(|ui| {
ui.label(format!("{}", o.status));
});
row.col(|ui| {
ui.label(format!("{}", o.date));
});
row.col(|ui| {
ui.label(format!("{}", o.subaddress));
});
row.col(|ui| {
ui.style_mut().wrap = Some(false);
ui.horizontal(|_ui| {
// update button
});
});
});
}
});
});
// End Vendor specific
// Market Dashboard Main window
//-----------------------------------------------------------------------------------
egui::CentralPanel::default().show(ctx, |ui| {
if ui.button("Refresh").clicked() {
self.products = product::find_all();
self.orders = order::find_all();
}
ui.horizontal(|ui| {
let vendor_mode: &str = if self.is_vendor_enabled {
"enabled"
} else {
"disabled"
};
ui.label(format!("vendor mode: {} \t", vendor_mode));
if ui.button("toggle").clicked() {
self.is_vendor_enabled = utils::toggle_vendor_enabled();
}
});
if ui.button("View Vendors").clicked() {
// assume all contacts are vendors until updated status check
self.vendors = contact::find_all();
self.is_showing_vendors = true;
}
ui.label("\n");
if ui.button("View Orders").clicked() {
self.customer_orders = order::find_all_backup();
self.is_customer_viewing_orders = true;
}
if self.is_vendor_enabled {
ui.label("\n");
ui.heading("Add Product");
ui.label(
"____________________________________________________________________________\n",
);
// TODO(c2m): file picker for images
ui.horizontal(|ui| {
let product_name = ui.label("image: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_image)
.labelled_by(product_name.id);
ui.label("\t/path/to/image.png");
});
ui.horizontal(|ui| {
let product_name = ui.label("name: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_name)
.labelled_by(product_name.id);
});
ui.horizontal(|ui| {
let product_desc = ui.label("description: \t");
ui.text_edit_singleline(&mut self.new_product_desc)
.labelled_by(product_desc.id);
});
ui.horizontal(|ui| {
let product_price = ui.label("price: \t\t\t");
ui.text_edit_singleline(&mut self.new_product_price)
.labelled_by(product_price.id);
ui.label("\t (piconeros)")
});
ui.horizontal(|ui| {
let product_qty = ui.label("quantity: \t\t");
ui.text_edit_singleline(&mut self.new_product_qty)
.labelled_by(product_qty.id);
});
if ui.button("Add Product").clicked() {
let image: Vec<u8> = std::fs::read(self.new_product_image.clone()).unwrap_or_default();
let price = match self.new_product_price.parse::<u128>() {
Ok(p) => p,
Err(_) => 0,
};
let qty = match self.new_product_qty.parse::<u128>() {
Ok(q) => q,
Err(_) => 0,
};
let product: models::Product = models::Product {
pid: utils::empty_string(),
description: self.new_product_desc.clone(),
image,
in_stock: qty > 0,
name: self.new_product_name.clone(),
price,
qty,
};
let j_product = utils::product_to_json(&product);
product::create(j_product);
self.new_product_desc = utils::empty_string();
self.new_product_name = utils::empty_string();
self.new_product_price = utils::empty_string();
self.new_product_qty = utils::empty_string();
self.new_product_image = utils::empty_string();
}
ui.label("\n");
if ui.button("View Products").clicked() {
self.products = product::find_all();
self.is_showing_products = true;
self.is_showing_vendors = false;
}
ui.label("\n");
if ui.button("Manage Orders").clicked() {
// TODO(c2m): vendor order management logic
}
}
});
}
}
fn _refresh_on_delete_product_req(_tx: Sender<bool>, _ctx: egui::Context) {
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
log::error!("refreshing products....");
todo!();
// let _ = tx.send(true);
// ctx.request_repaint();
});
}
fn send_contact_info_req(
tx: Sender<models::Contact>,
ctx: egui::Context,
contact: String,
prune: u32,
) {
log::debug!("async send_contact_info_req");
tokio::spawn(async move {
match contact::add_contact_request(contact, prune).await {
Ok(contact) => {
let _ = tx.send(contact);
ctx.request_repaint();
}
_ => log::debug!("failed to request invoice"),
}
});
}
fn check_signed_key(contact: String) -> bool {
let v = utils::search_gui_db(String::from(crate::GUI_SIGNED_GPG_DB_KEY), contact);
v != utils::empty_string()
}
fn send_products_from_vendor_req(
tx: Sender<Vec<models::Product>>,
ctx: egui::Context,
contact: String,
jwp: String,
) {
log::debug!("fetching products for vendor: {}", contact);
tokio::spawn(async move {
let result = product::get_vendor_products(contact, jwp).await;
if result.is_ok() {
let products: Vec<models::Product> = result.unwrap();
log::info!("retreived {:?} products", products.len());
let _ = tx.send(products);
ctx.request_repaint();
}
});
}
fn send_product_from_vendor_req(
tx: Sender<models::Product>,
ctx: egui::Context,
contact: String,
jwp: String,
pid: String,
) {
log::debug!("fetching product {} from vendor: {}", &pid, contact);
tokio::spawn(async move {
let result = product::get_vendor_product(contact, jwp, pid).await;
if result.is_ok() {
let product: models::Product = result.unwrap();
log::info!("retreived product {}", &product.pid);
let _ = tx.send(product);
ctx.request_repaint();
}
});
}
fn vendor_status_timeout(tx: Sender<bool>, ctx: egui::Context) {
tokio::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(
crate::ADD_CONTACT_TIMEOUT_SECS,
))
.await;
log::error!("vendor status timeout");
let _ = tx.send(true);
ctx.request_repaint();
});
}
fn submit_order_req(
tx: Sender<models::Order>,
contact: String,
ctx: egui::Context,
jwp: String,
request: reqres::OrderRequest,
) {
tokio::spawn(async move {
log::info!("submit order");
let r_contact = String::from(&contact);
let order = order::transmit_order_request(r_contact, jwp, request).await;
let u_order = order.unwrap_or_else(|_| Default::default());
// cache order request to db
order::backup(&u_order);
let _ = tx.send(u_order);
ctx.request_repaint();
});
}