Files
advotracker/advotracker_client/examples/combobox-test.rs
2021-11-12 15:48:17 +01:00

848 lines
32 KiB
Rust

/*
* advotracker - Hotline tackingtool for Advocats
*
* Copyright 2021 Ralf Zerres <ralf.zerres@networkx.de>
* SPDX-License-Identifier: (0BSD or MIT)
*/
// suppress creation of a new console window on window
#![windows_subsystem = "windows"]
use cfg_if::cfg_if;
use locales::t;
use orbtk::{
prelude::*,
theme_default::{THEME_DEFAULT, THEME_DEFAULT_COLORS_DARK, THEME_DEFAULT_FONTS},
theming::config::ThemeConfig,
};
use serde::Deserialize;
use std::env;
use std::time::SystemTime;
use tracing::{info, trace, Level};
use advotracker::{
data::{constants::*, structures::Email},
widgets::global_state::GlobalState,
widgets::main_view,
//services::exports::send_ticketdata::sendticketdata,
widgets::policycheck::*,
widgets::policycheck_state::*,
widgets::ticketdata::*,
};
#[cfg(target_os = "windows")]
use orbtk::theme_fluent::{THEME_FLUENT, THEME_FLUENT_COLORS_DARK, THEME_FLUENT_FONTS};
pub enum TicketdataAction {
ClearForm(),
SendForm(),
UpdatePolicyCode(String),
}
// German localization file.
static ADVOTRACKER_DE_DE: &str = include_str!("../assets/advotracker/advotracker_de_DE.ron");
/// Get the active language environment.
fn get_lang() -> String {
// get system environment
let mut lang = env::var("LANG").unwrap_or_else(|_| "C".to_string());
lang = lang.substring(0, 5).to_string(); // "de_DE.UTF-8" -> "de_DE"
info!("GUI-Language: preset to {:?}", lang);
// return the active language
lang
}
cfg_if! {
if #[cfg(windows)] {
/// Extend and register theme assets.
fn theme() -> Theme {
register_default_fonts(Theme::from_config(
ThemeConfig::from(DEFAULT_DARK_EXT)
.extend(ThemeConfig::from(THEME_DEFAULT))
.extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK))
.extend(ThemeConfig::from(THEME_DEFAULT_FONTS)),
))
}
fn theme_fluent() -> Theme {
register_fluent_fonts(Theme::from_config(
ThemeConfig::from(FLUENT_DARK_EXT)
.extend(ThemeConfig::from(THEME_FLUENT))
.extend(ThemeConfig::from(THEME_FLUENT_COLORS_DARK))
.extend(ThemeConfig::from(THEME_FLUENT_FONTS)),
))
// register_fluent_fonts(Theme::from_config(
// ThemeConfig::from(FLUENT_LIGHT_EXT)
// .extend(ThemeConfig::from(THEME_FLUENT))
// .extend(ThemeConfig::from(THEME_FLUENT_COLORS_DARK))
// .extend(ThemeConfig::from(THEME_FLUENT_FONTS)),
}
} else {
/// Extend and register theme assets.
fn theme() -> Theme {
register_default_fonts(Theme::from_config(
ThemeConfig::from(DEFAULT_DARK_EXT)
.extend(ThemeConfig::from(THEME_DEFAULT))
.extend(ThemeConfig::from(THEME_DEFAULT_COLORS_DARK))
.extend(ThemeConfig::from(THEME_DEFAULT_FONTS)),
))
}
}
}
// Main
fn main() -> Result<(), Box<dyn std::error::Error>> {
// initialize the tracing subsystem
// a drop in replacement for classical logging
// reference: https://tokio.rs/blog/2019-08-tracing/
let span = tracing::span!(Level::TRACE, "advotracker");
let _enter = span.enter();
let subscriber = fmt::Subscriber::builder()
.with_env_filter(trace)
//.with_max_level(tracing::Level::DEBUG)
.finish();
tracing::subscriber::with_default(subscriber, || {
// get system environment
let lang = get_lang();
let mut state = t!("state.started", lang);
let mut res = t!("parse.environment", lang);
trace!(target: "advotracker", process = ?res, state = ?state);
trace!(target: "advotracker", environment = "system", lang = ?lang);
trace!(target: "advotracker", machine = ?&machine_kind);
// how to handle unimplemented lang resources??
res = t!("parse.environment", lang);
trace!(target: "advotracker", environment = "envy", lang = ?lang);
state = t!("state.finished", lang);
trace!(target: "advotracker", process = ?res, state = ?state);
// main tasks
res = t!("main.started", lang);
state = t!("state.started", lang);
trace!(target: "advotracker", process = ?res, state = ?state);
// use this only if you want to run it as web application.
orbtk::initialize();
// Preset localization and language from given environment.
// if no dictionary is set for the chosen default language
// the content of the text property will be drawn.
//let language = lang.substring(0,5); // "de_DE.UTF-8" -> "de_DE"
//info!("GUI-Language: preset to {:?}", language);
let localization = RonLocalization::create()
.language(&lang)
.dictionary("de_DE", ADVOTRACKER_DE_DE)
.build();
Application::from_name("nwx.advotracker")
.localization(localization)
.theme(theme())
.window(|ctx| {
Window::new()
.title("AdvoTracker - DirectCall")
.position((500.0, 100.0))
.size(800.0, 600.0)
//.min_width(460.0)
//.min_height(380.0)
.resizeable(true)
.child(main_view::MainView::new().build(ctx))
.build(ctx)
})
.run();
state = t!("state.finished", lang);
res = t!("main.finished", lang);
trace!(target: "advotracker", process = ?res, state = ?state);
});
Ok(())
}
// View
widget!(MainView {
policycheck_view: PolicyCheck
});
impl Template for MainView {
fn template(self, _id: Entity, ctx: &mut BuildContext<'_>) -> Self {
let ticketdata_view = TicketdataView::new().build(ctx);
let policycheck_view = PolicycheckView::new().target(ticketdata_view.0).build(ctx);
self.name("MainView").child(
TabWidget::new()
.tab(ID_POLICY_CHECK_VIEW, policycheck_view)
.tab(ID_TICKET_DATA_VIEW, ticketdata_view)
.build(ctx),
)
}
}
// Macro that initializes the widget structures/variables for the policy check view
widget!(
/// Form to enter data of a ticket record
TicketdataView<TicketdataState> {
// language used inside the widget
lang: String,
// title used in the header
ticket_data_title: String,
// entity id that will receive the messages
target: u32
}
);
/// The template implementation of the ticket view
/// All GUI elements are styled using the "style" attribute referencing to a ron based css
impl Template for TicketdataView {
fn template(self, id: Entity, ctx: &mut BuildContext<'_>) -> Self {
let tenent_logo = Container::new()
.margin((16, 16, 0, 0))
.attach(Grid::column(0))
.v_align("center")
.child(
ImageWidget::new()
.image("assets/advotracker/hiedemann_logo.png")
.v_align("center")
.build(ctx),
)
.build(ctx);
let ticket_data_bottom_bar = Container::new()
.id(ID_TICKET_DATA_BOTTOM_BAR)
.style(STYLE_BOTTOM_BAR)
.attach(Grid::row(4))
.attach(Grid::column(1))
.attach(Grid::column_span(2))
.v_align("end")
.child(
Container::new()
.attach(Grid::column(1))
.h_align("end")
.v_align("end")
.child(
TextBlock::new()
.margin((0, 9, 48, 0))
.text("©Networkx GmbH")
.build(ctx),
)
.build(ctx),
)
.build(ctx);
let ticket_data_button_menu = Button::new()
.id(ID_TICKET_DATA_BUTTON_MENU)
.attach(Grid::row(0))
.attach(Grid::column(2))
.h_align("end")
.icon(material_icons_font::MD_MENU)
//.style("button_single_content")
.style(STYLE_BUTTON_MENU)
.build(ctx);
let ticket_data_form = Container::new()
.id(ID_TICKET_DATA_FORM)
.name(ID_TICKET_DATA_FORM)
.attach(Grid::row(2))
.attach(Grid::column(1))
.style("container_form")
.child(
Grid::new()
.id(ID_TICKET_DATA_GRID)
.name(ID_TICKET_DATA_GRID)
.columns(
Columns::create()
.push("auto") // Label
.push(16) // Delimiter
.push("*") // Data
.push(32), // Delimiter (2x margin)
)
.rows(
Rows::create()
.push("auto") // Row 0
.push(14) // Seperator
.push("auto") // Row 2
.push(14) // Seperator
.push("auto") // Row 4
.push(14) // Seperator
.push("auto") // Row 6
.push(14) // Seperator
.push("auto") // Row 8
.push(14) // Seperator
.push("auto") // Row 10
.push(14) // Seperator
.push("auto") // Row 12
.push(14), // Seperator
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_POLICY_CODE)
.attach(Grid::row(0))
.attach(Grid::column(0))
.text("Policy code")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_POLICY_CODE)
.attach(Grid::row(0))
.attach(Grid::column(2))
.text("")
.v_align("center")
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_POLICY_HOLDER)
.attach(Grid::row(2))
.attach(Grid::column(0))
.text("Policy holder")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_POLICY_HOLDER)
.attach(Grid::row(2))
.attach(Grid::column(2))
.text("")
.water_mark("Name des Versicherungsnehmers")
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_DEDUCTIBLE)
.attach(Grid::row(4))
.attach(Grid::column(0))
.text("Deductible")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_DEDUCTIBLE)
.attach(Grid::row(4))
.attach(Grid::column(2))
.text("")
.water_mark("im Beratungsgespräch erfragen")
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_CALLBACK_NUMBER)
.attach(Grid::row(6))
.attach(Grid::column(0))
.text("Callback number")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_CALLBACK_NUMBER)
.attach(Grid::row(6))
.attach(Grid::column(2))
.text("")
.water_mark("wie zu erreichen")
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_CALLBACK_DATE)
.attach(Grid::row(8))
.attach(Grid::column(0))
.text("Callback date")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_CALLBACK_DATE)
.attach(Grid::row(8))
.attach(Grid::column(2))
.text("")
.water_mark("Rückruf gewünscht um")
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_HARM_TYPE)
.attach(Grid::row(10))
.attach(Grid::column(0))
.text("Harm type")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_HARM_TYPE)
.name(ID_TICKET_DATA_HARM_TYPE)
.attach(Grid::row(10))
.attach(Grid::column(2))
.text("")
.water_mark("Schadenstyp")
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_IVR_COMMENT)
.attach(Grid::row(12))
.attach(Grid::column(0))
.text("IVR comment")
.h_align("end")
.v_align("center")
.build(ctx),
)
.child(
TextBox::new()
.id(ID_TICKET_DATA_IVR_COMMENT)
.attach(Grid::row(12))
.attach(Grid::column(2))
.text("")
.water_mark("Kommentar zur Haftung, bzw. zur Deckung")
//.height(48.0)
.build(ctx),
)
.build(ctx),
)
.build(ctx);
let ticket_data_form_action = Container::new()
.id(ID_TICKET_DATA_ACTION_GRID)
.attach(Grid::row(3))
.attach(Grid::column(1))
//.style(STYLE_CONTAINER_ACTION)
.padding(14)
.h_align("center")
.child(
Stack::new()
//.style(STYLE_STACK_ACTION)
.orientation("horizontal")
.spacing(50)
.child(
Button::new()
.id(ID_TICKET_DATA_ACTION_BUTTON_CLEAR)
.style(STYLE_BUTTON_ACTION)
.text("Clear")
.on_click(move |states, _| {
states.send_message(TicketdataAction::ClearForm(), id);
false
})
.build(ctx),
)
.child(
Button::new()
.id(ID_TICKET_DATA_ACTION_BUTTON_SEND)
.style(STYLE_BUTTON_ACTION)
.text("Send")
//.visibility(Visibility::Collapsed)
.on_click(move |states, _entity| {
states.send_message(TicketdataAction::SendForm(), id);
false
})
.build(ctx),
)
.build(ctx),
)
.build(ctx);
// vector with valid mail recipients addresses (mail_to)
let items_mail_to = vec![
PROP_MAIL_TO_1,
PROP_MAIL_TO_2,
PROP_MAIL_TO_3,
PROP_MAIL_TO_4,
];
let items_mail_to_count = items_mail_to.len();
// vector with valid carbon copy recipients addresses (mail_to)
let items_mail_cc = vec![PROP_MAIL_CC_1, PROP_MAIL_CC_2];
let items_mail_cc_count = items_mail_cc.len();
let ticket_data_form_mail = Container::new()
.id(ID_TICKET_DATA_CONTAINER_MAIL)
.name(ID_TICKET_DATA_CONTAINER_MAIL)
.attach(Grid::row(1))
.attach(Grid::column(1))
.style(STYLE_CONTAINER_MAIL)
.child(
Grid::new()
.columns(
Columns::create()
.push(16) // Delimiter
.push("stretch") // Label
.push(16) // Delimiter
.push("auto") // MailAddress
.push("32"), // Delimiter (2x margin)
)
.rows(
Rows::create()
.push("auto") // Row 0
.push(2) // Seperator
.push("auto") // Row 2
.push(2), // Seperator
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_MAIL_TO)
.attach(Grid::row(0))
.attach(Grid::column(1))
.style(STYLE_MAIL_LABEL)
.text("Recipient (To)")
.build(ctx),
)
.child(
/// WIP: get selected item
ComboBox::new()
.id(ID_TICKET_DATA_MAIL_TO)
.attach(Grid::row(0))
.attach(Grid::column(3))
.style(STYLE_MAIL_TO)
.count(items_mail_to_count)
.items_builder(move |ibc, index| {
let text_mail_to =
TicketdataView::items_mail_to_ref(&ibc.get_widget(id))[index]
.clone();
TextBox::new()
.text(text_mail_to)
.v_align("center")
.build(ibc)
})
.selected_index(0)
.build(ctx),
)
.child(
TextBlock::new()
.id(ID_TICKET_DATA_LABEL_MAIL_CC)
.attach(Grid::row(2))
.attach(Grid::column(1))
.style(STYLE_MAIL_LABEL)
.text("Copie (CC)")
.build(ctx),
)
.child(
ComboBox::new()
.id(ID_TICKET_DATA_MAIL_CC)
.attach(Grid::row(2))
.attach(Grid::column(3))
.style(STYLE_MAIL_CC)
.count(items_mail_cc_count)
.items_builder(move |ibc, index| {
let text_mail_cc =
TicketdataView::items_mail_cc_ref(&ibc.get_widget(id))[index]
.clone();
TextBox::new()
.text(text_mail_cc)
.v_align("center")
.build(ibc)
})
.selected_index(0)
.build(ctx),
)
.build(ctx),
)
.build(ctx);
let ticket_data_header_text = TextBlock::new()
.id(ID_TICKET_DATA_HEADER)
.attach(Grid::column(0))
.style(STYLE_HEADER_TEXT)
.text("Ticket Data")
.build(ctx);
let ticket_data_header_bar = Container::new()
.id(ID_TICKET_DATA_HEADER_BAR)
.attach(Grid::row(0))
.attach(Grid::column_span(3))
.style(STYLE_HEADER_BAR)
.child(tenent_logo)
.child(ticket_data_header_text)
.child(ticket_data_button_menu)
.build(ctx);
// Widget: Ticket data view
self.id(ID_TICKET_DATA_VIEW)
.name(ID_TICKET_DATA_VIEW)
.min_height(410.0)
.child(
Grid::new()
.id(ID_TICKET_DATA_GRID)
.columns(
Columns::create()
.push(50) // Left margin
.push("*") // Content
.push(50), // Right margin
)
.rows(
Rows::create()
.push("auto") // Header_Bar
.push("auto") // Mail_Form
.push("*") // Input_Form
.push("auto") // Action
.push("auto"), // Bottom_Bar
)
.child(ticket_data_header_bar) // row 0
.child(ticket_data_form_mail) // row 1
.child(ticket_data_form) // row 2
.child(ticket_data_form_action) // row 3
.child(ticket_data_bottom_bar) // row 4
.build(ctx),
)
}
}
// State
impl GlobalState for TicketdataState {}
/// Method definitions, that react on any given state change inside the `Ticketdata` widget.
impl TicketdataState {
/// Clear the text property of all children of the given form entity
pub fn clear_form(entity: Entity, ctx: &mut Context<'_>) {
if let Some(count) = ctx.get_widget(entity).children_count() {
info!(
"Widget name: {:?}",
ctx.get_widget(entity).get::<String>("name")
);
info!(
"Widget id: {:?}",
ctx.get_widget(entity).get::<String>("id")
);
}
// identify the form by its id
if let form_entity = ctx.child(ID_TICKET_DATA_GRID).entity() {
info!(
"Form id: {:?}",
ctx.get_widget(form_entity).get::<String>("id")
);
info!(
"Form node name: {:?}",
ctx.get_widget(form_entity).get::<String>("name")
);
// Loop through children
if let Some(count) = ctx.get_widget(form_entity).children_count() {
for c in 1..=count {
info!("WIP clear entry of child {:?}", c);
//info!("WIP clear entry of child {:?}: {:?}", c, ctx.get_child(form_entity).get::<String>("name"));
}
}
}
}
pub fn send_form(entity: Entity, ctx: &mut Context<'_>, lang: &str) {
// type conversion (String -> u64)
//let policy_code = ctx.child(ID_TICKET_DATA_POLICY_CODE).get::<String>("text").unwrap().parse::<u64>().unwrap();
// WIP: get selected items ComboBox'es
//let mail_cc_index = *TicketdataView::selected_index_ref(&ctx.widget()) as usize;
//let mail_cc_selected = TicketdataView::mail_cc_ref(&ctx.widget())[mail_cc_index].clone();
// create Email structures
let email = Email {
// WIP: mail_to -> selected index auslesen
//mail_to: ctx.child(ID_TICKET_DATA_MAIL_TO).get::<String>("text").to_string(),
// WIP: mail_cc -> selected index auslesen
//mail_cc: ctx.child(ID_TICKET_DATA_MAIL_CC).get::<String>("text").to_string(),
mail_to: PROP_MAIL_TO_1.to_string(),
mail_cc: PROP_MAIL_CC_1.to_string(),
mail_bcc: PROP_MAIL_BCC_1.to_string(),
mail_from: PROP_MAIL_FROM.to_string(),
mail_reply: PROP_MAIL_REPLY.to_string(),
subject: PROP_MAIL_SUBJECT.to_string(),
policy_code: ctx
.child(ID_TICKET_DATA_POLICY_CODE)
.get::<String>("text")
.to_string(),
policy_holder: ctx
.child(ID_TICKET_DATA_POLICY_HOLDER)
.get::<String>("text")
.to_string(),
deductible: ctx
.child(ID_TICKET_DATA_DEDUCTIBLE)
.get::<String>("text")
.to_string(),
callback_number: ctx
.child(ID_TICKET_DATA_CALLBACK_NUMBER)
.get::<String>("text")
.to_string(),
callback_date: ctx
.child(ID_TICKET_DATA_CALLBACK_DATE)
.get::<String>("text")
.to_string(),
harm_type: ctx
.child(ID_TICKET_DATA_HARM_TYPE)
.get::<String>("text")
.to_string(),
ivr_comment: ctx
.child(ID_TICKET_DATA_IVR_COMMENT)
.get::<String>("text")
.to_string(),
};
info!("WIP: Sending form to construct eMail to {:?}", email);
// send email via service
let _ = sendticketdata(&email, &lang);
}
}
/// Supported methods handled inside the `TicketState`
impl State for TicketdataState {
/// Initialize the state of widgets inside `TicketState`
fn init(&mut self, _: &mut Registry, ctx: &mut Context<'_>) {
let time_start = SystemTime::now();
trace!(target: "advotracker", ticketdata_state = "init", status = "started");
// Initialize required entities
self.button_menu = ctx
.entity_of_child(ID_TICKET_DATA_BUTTON_MENU)
.expect("TicketState.init: Can't find resource entity 'ID_TICKET_DATA_BUTTON_MENU'.");
self.target = Entity::from(
ctx.widget()
.try_clone::<u32>("target")
.expect("TicketState.init: Can't find resource entity 'target'."),
);
// Get language from environment
self.lang = TicketdataState::get_lang();
let time_end = SystemTime::now();
let duration = time_end.duration_since(time_start);
trace!(target: "advotracker", ticketdata_state = "init", status = "finished", duration = ?duration);
}
fn messages(
&mut self,
mut messages: MessageReader,
_registry: &mut Registry,
ctx: &mut Context<'_>,
) {
for message in messages.read::<TicketdataAction>() {
match message {
TicketdataAction::ClearForm() => {
info!("message: {:?} recieved", message);
TicketdataState::clear_form(ctx.entity(), ctx);
}
TicketdataAction::SendForm() => {
info!("message: {:?} recieved", message);
TicketdataState::send_form(ctx.entity(), ctx, &self.lang);
}
_ => {
println!("messages: action not implemented!");
}
}
}
for message in messages.read::<PolicycheckAction>() {
match message {
PolicycheckAction::UpdatePolicyCode => {
info!("Message received: 'PolicycheckAction::UpdatePolicyCode'");
TextBlock::text_set(&mut ctx.child(ID_TICKET_DATA_POLICY_CODE), policy_code);
}
_ => {
println!("messages: action not implemented!");
}
}
}
}
fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) {
let actions: Vec<TicketdataAction> = self.actions.drain(..).collect();
for action in actions {
match action {
TicketdataAction::ClearForm() => {
info!("update: send_message {:?}", action);
ctx.send_message(TicketdataAction::ClearForm(), self.target);
}
TicketdataAction::SendForm() => {
//ctx.send_message(TicketdataAction::SendForm(), self.ID_TICKETDATA_FORM);
info!("update: send_message {:?}", action);
}
_ => {
println!("TicketdataAction: action not implemented!");
}
}
}
}
}
/// send ticket data via eMail
pub fn sendticketdata(email: &Email, lang: &str) -> Result<(), Box<dyn Error>> {
let mut res = t!("sendticketdata.export.started", lang);
let mut state = t!("state.started", lang);
trace!(target: "sendticketdata", process = ?res, state = ?state);
let ascii_body = String::new()
+ &"Vers.-Schein/Schadennummer".to_string()
+ &(email.policy_code)
+ &"\n"
+ &"Versicherungsnehmer: ".to_string()
+ &(email.policy_holder)
+ &"\n"
+ &"Selbstbehalt: ".to_string()
+ &(email.deductible)
+ &"\n"
+ &"Rückrufnummer: ".to_string()
+ &(email.callback_number)
+ &"\n"
+ &"Erreichbarkeit: ".to_string()
+ &(email.callback_date)
+ &"\n"
+ &"Rechtsproblem: ".to_string()
+ &(email.harm_type)
+ &"\n"
+ &"Rechtsrat: ".to_string()
+ &(email.ivr_comment)
+ &"\n";
info!("email body: {:?}", ascii_body);
let message = Message::builder()
//.reply_to("Kanzlei Hiedemann <info@hiedemann.de>".parse().unwrap())
.to("Kanzlei Hiedemann <info@hiedemann.de>".parse().unwrap())
.cc(" <ralf.zerres@networkx.de>".parse().unwrap())
.reply_to((email.mail_reply).parse().unwrap())
//.to((email.mail_to).parse().unwrap())
//.cc((email.mail_cc).parse().unwrap())
//.bcc((email.mail_bcc).parse().unwrap())
.from((email.mail_from).parse().unwrap())
.subject(
String::new()
+ &email.subject.to_string()
+ &" (".to_string()
+ &email.policy_code.to_string()
+ &")".to_string(),
)
.multipart(
MultiPart::alternative() // This is composed of two parts.
.singlepart(
SinglePart::builder()
.header(header::ContentType(
"text/plain; charset=utf8".parse().unwrap(),
))
.body(String::from(ascii_body)),
),
)
.expect("failed to build email");
info!("message: {:?}", message);
trace!(target: "sendticketdata", email = ?email);
state = t!("state.finished", lang);
res = t!("sendticketdata.export.finished", lang);
trace!(target: "sendticketdata", process = ?res, state = ?state);
Ok(())
}