use std::convert::TryFrom;
use async_std::task;
use iced::{
widget::{button, scrollable, Column, Row, Space, Text},
Color, Command, Element, Length,
};
use log::error;
use koibumi_common::boxes::{Boxes, DEFAULT_USER_ID};
use koibumi_core::{
encoding::{Encoding, Simple},
message::InvHash,
time::Time,
};
use crate::{config::Config as GuiConfig, gui, style};
#[derive(Clone, Debug)]
pub(crate) struct Entry {
entry: koibumi_box::MessageEntry,
}
impl Entry {
pub(crate) fn new(entry: koibumi_box::MessageEntry) -> Self {
Self { entry }
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Tab {
pub(crate) entries: Vec<Entry>,
selected: Option<InvHash>,
message: Option<koibumi_box::Message>,
}
fn time_to_string(time: Time) -> String {
if time.as_secs() > i64::MAX as u64 {
return "(unsupported)".to_string();
}
let time = ::time::OffsetDateTime::from_unix_timestamp(time.as_secs() as i64);
if time.is_err() {
return "(error)".to_string();
}
let mut time = time.unwrap();
if let Ok(local_offset) = ::time::UtcOffset::local_offset_at(time) {
time = time.to_offset(local_offset);
}
let format = time::format_description::parse("[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory]:[offset_minute]").unwrap();
if let Ok(s) = time.format(&format) {
s
} else {
"(error)".to_string()
}
}
impl Tab {
pub(crate) fn update(
&mut self,
message: gui::Message,
boxes: &mut Option<Boxes>,
) -> Command<gui::Message> {
match message {
gui::Message::MessagesEntryPressed(hash) => {
if boxes.is_none() {
return Command::none();
}
let boxes = boxes.as_mut().unwrap();
let message = task::block_on(async {
boxes.manager().get_message(DEFAULT_USER_ID, &hash).await
});
if let Err(err) = message {
error!("{}", err);
return Command::none();
}
let message = message.unwrap();
if message.is_none() {
return Command::none();
}
let message = message.unwrap();
self.message = Some(message);
if let Some(entry) = self
.entries
.iter_mut()
.find(|item| item.entry.hash() == &hash)
{
if !entry.entry.read() {
entry.entry.set_read(true);
if let Err(err) =
task::block_on(boxes.manager().set_read(DEFAULT_USER_ID, &hash, true))
{
error!("{}", err);
}
boxes.decrement_unread_count();
}
}
self.selected = Some(hash);
Command::none()
}
gui::Message::MessagesUnreadPressed(hash) => {
if hash.is_none() {
return Command::none();
}
let hash = hash.unwrap();
if boxes.is_none() {
return Command::none();
}
let boxes = boxes.as_mut().unwrap();
if let Some(entry) = self
.entries
.iter_mut()
.find(|item| item.entry.hash() == &hash)
{
if entry.entry.read() {
entry.entry.set_read(false);
if let Err(err) =
task::block_on(boxes.manager().set_read(DEFAULT_USER_ID, &hash, false))
{
error!("{}", err);
}
boxes.increment_unread_count();
}
}
Command::none()
}
gui::Message::MessagesCopyPressed => {
if boxes.is_none() {
return Command::none();
}
let boxes = boxes.as_ref().unwrap();
if self.message.is_none() {
return Command::none();
}
let message = self.message.as_ref().unwrap();
let from = boxes.user().rich_alias(&message.from_address().to_string());
let to = if let Some(address) = message.to_address() {
boxes.user().rich_alias(&address.to_string())
} else {
String::new()
};
let received = time_to_string(message.time());
let mut subject = String::new();
let mut body = String::new();
if message.encoding() == Encoding::Simple {
if let Ok(simple) = Simple::try_from(message.content()) {
subject = String::from_utf8_lossy(simple.subject()).to_string();
body = String::from_utf8_lossy(simple.body()).to_string();
}
}
let content = format!(
"Subject: {}\nFrom: {}\nTo: {}\nReceived: {}\n\n{}",
subject, from, to, received, body
);
iced::clipboard::write(content)
}
_ => panic!("Program error"),
}
}
pub(crate) fn view(&self, config: &GuiConfig, boxes: &Option<Boxes>) -> Element<gui::Message> {
let text_size = config.text_size();
if boxes.is_none() {
return Column::new()
.push(Text::new("inbox/outbox database error").size(text_size))
.into();
}
let boxes = boxes.as_ref().unwrap();
let entry_button = |label, selected, hash, read| {
let label = Text::new(label).size(text_size);
let style = if selected {
iced::theme::Button::Custom(Box::new(style::MessageEntryButton::Selected))
} else if read {
iced::theme::Button::Custom(Box::new(style::MessageEntryButton::Read))
} else {
iced::theme::Button::Custom(Box::new(style::MessageEntryButton::Unread))
};
button(label)
.style(style)
.on_press(gui::Message::MessagesEntryPressed(hash))
.padding(2)
};
let mut list = Column::new();
for entry in &self.entries {
let selected =
self.selected.is_some() && self.selected.as_ref().unwrap() == entry.entry.hash();
let row = Row::new().push(
entry_button(
entry.entry.subject(),
selected,
entry.entry.hash().clone(),
entry.entry.read(),
)
.width(Length::Fill),
);
list = list.push(row);
}
let list = scrollable(list).height(Length::Fill);
let buttons = Row::new()
.spacing(text_size / 4)
.push(
button(Text::new("Unread").size(text_size))
.on_press(gui::Message::MessagesUnreadPressed(self.selected.clone())),
)
.push(
button(Text::new("Copy").size(text_size))
.on_press(gui::Message::MessagesCopyPressed),
);
let mut subject = String::new();
let mut from = String::new();
let mut to = String::new();
let mut received = String::new();
let mut body = String::new();
if let Some(message) = &self.message {
from = boxes.user().rich_alias(&message.from_address().to_string());
to = if let Some(address) = message.to_address() {
boxes.user().rich_alias(&address.to_string())
} else {
String::new()
};
received = time_to_string(message.time());
if message.encoding() == Encoding::Simple {
if let Ok(simple) = Simple::try_from(message.content()) {
subject = String::from_utf8_lossy(simple.subject()).to_string();
body = String::from_utf8_lossy(simple.body()).to_string();
}
}
}
let color = Color {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
let subject = Row::new()
.spacing(text_size / 2)
.push(Text::new("Subject:").size(text_size).style(color))
.push(Text::new(subject).size(text_size));
let from = Row::new()
.spacing(text_size / 2)
.push(Text::new("From:").size(text_size).style(color))
.push(Text::new(from).size(text_size));
let to = Row::new()
.spacing(text_size / 2)
.push(Text::new("To:").size(text_size).style(color))
.push(Text::new(to).size(text_size));
let received = Row::new()
.spacing(text_size / 2)
.push(Text::new("Received:").size(text_size).style(color))
.push(Text::new(received).size(text_size));
let body =
scrollable(Column::new().push(Text::new(body).size(text_size).width(Length::Fill)))
.height(Length::Fill);
Column::new()
.spacing(text_size / 4)
.push(list)
.push(buttons)
.push(subject)
.push(from)
.push(to)
.push(received)
.push(Space::with_height(<u16 as Into<Length>>::into(
text_size / 4,
)))
.push(body)
.into()
}
}