use std::convert::TryFrom;
use async_std::task;
use copypasta::ClipboardProvider;
use iced::{
button, scrollable, Button, Color, Column, Command, Element, Length, Row, Scrollable, Space,
Text,
};
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 enum Message {
EntryPressed(InvHash),
UnreadPressed(Option<InvHash>),
CopyPressed,
}
#[derive(Clone, Debug)]
pub(crate) struct Entry {
entry: koibumi_box::MessageEntry,
button: button::State,
}
impl Entry {
pub(crate) fn new(entry: koibumi_box::MessageEntry) -> Self {
Self {
entry,
button: button::State::new(),
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Tab {
pub(crate) entries: Vec<Entry>,
list_scroll: scrollable::State,
content_scroll: scrollable::State,
selected: Option<InvHash>,
message: Option<koibumi_box::Message>,
unread_button: button::State,
copy_button: button::State,
}
fn time_to_string(time: Time) -> String {
if time.as_secs() > i64::MAX as u64 {
return "(unsupported)".to_string();
}
let mut time = ::time::OffsetDateTime::from_unix_timestamp(time.as_secs() as i64);
if let Ok(local_offset) = ::time::UtcOffset::try_local_offset_at(time) {
time = time.to_offset(local_offset);
}
time.format("%F %T %z")
}
impl Tab {
pub(crate) fn update(
&mut self,
message: Message,
boxes: &mut Option<Boxes>,
) -> Command<gui::Message> {
match message {
Message::EntryPressed(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()
}
Message::UnreadPressed(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()
}
Message::CopyPressed => {
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
);
let ctx = copypasta::ClipboardContext::new();
if let Err(err) = ctx {
error!("{}", err);
return Command::none();
}
let mut ctx = ctx.unwrap();
if let Err(err) = ctx.set_contents(content) {
error!("{}", err);
}
Command::none()
}
}
}
pub(crate) fn view(
&mut 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 = |state, label, selected, hash, read| {
let label = Text::new(label).size(text_size);
let style = if selected {
style::MessageEntryButton::Selected
} else if read {
style::MessageEntryButton::Read
} else {
style::MessageEntryButton::Unread
};
Button::new(state, label)
.style(style)
.on_press(gui::Message::MessagesMessage(Message::EntryPressed(hash)))
.padding(2)
};
let mut list = Scrollable::new(&mut self.list_scroll).max_height(text_size as u32 * 8);
for entry in &mut self.entries {
let selected =
self.selected.is_some() && self.selected.as_ref().unwrap() == entry.entry.hash();
let row = Row::new().push(
entry_button(
&mut entry.button,
entry.entry.subject(),
selected,
entry.entry.hash().clone(),
entry.entry.read(),
)
.width(Length::Fill),
);
list = list.push(row);
}
let buttons = Row::new()
.spacing(text_size / 4)
.push(
Button::new(&mut self.unread_button, Text::new("Unread").size(text_size)).on_press(
gui::Message::MessagesMessage(Message::UnreadPressed(self.selected.clone())),
),
)
.push(
Button::new(&mut self.copy_button, Text::new("Copy").size(text_size))
.on_press(gui::Message::MessagesMessage(Message::CopyPressed)),
);
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).color(color))
.push(Text::new(subject).size(text_size));
let from = Row::new()
.spacing(text_size / 2)
.push(Text::new("From:").size(text_size).color(color))
.push(Text::new(from).size(text_size));
let to = Row::new()
.spacing(text_size / 2)
.push(Text::new("To:").size(text_size).color(color))
.push(Text::new(to).size(text_size));
let received = Row::new()
.spacing(text_size / 2)
.push(Text::new("Received:").size(text_size).color(color))
.push(Text::new(received).size(text_size));
let body = Scrollable::new(&mut self.content_scroll)
.max_height(text_size as u32 * 24)
.push(Text::new(body).size(text_size).width(Length::Fill));
Column::new()
.spacing(text_size / 4)
.push(list)
.push(buttons)
.push(subject)
.push(from)
.push(to)
.push(received)
.push(Space::with_height((text_size / 4).into()))
.push(body)
.into()
}
}