use async_std::task;
use copypasta::ClipboardProvider;
use futures::{channel::mpsc::Sender, sink::SinkExt};
use iced::{
button, scrollable, text_input, Button, Color, Column, Command, Element, Length, Row,
Scrollable, Text, TextInput,
};
use log::{debug, error};
use rand::Rng;
use koibumi_common::boxes::Boxes;
use koibumi_core::{
content, crypto, encoding,
identity::{Private as PrivateIdentity, Public as PublicIdentity},
io::WriteTo,
object,
time::Time,
};
use koibumi_node::Command as NodeCommand;
use crate::{config::Config as GuiConfig, gui, log::Logger, style};
#[derive(Clone, Debug)]
pub enum Message {
SubTabSelected(SubTab),
MessageSubjectChanged(String),
MessageBodyChanged(String),
MessagePastePressed,
BroadcastSubjectChanged(String),
BroadcastBodyChanged(String),
BroadcastPastePressed,
ClearPressed,
SendPressed,
}
#[derive(Clone, Debug, Default)]
struct MessageSubTab {
subject: text_input::State,
subject_value: String,
body: text_input::State,
body_value: String,
paste_button: button::State,
body_scroll: scrollable::State,
}
#[derive(Clone, Debug, Default)]
struct BroadcastSubTab {
subject: text_input::State,
subject_value: String,
body: text_input::State,
body_value: String,
paste_button: button::State,
body_scroll: scrollable::State,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Tab {
sub_tabs: SubTabs,
sub_tab: SubTab,
message_sub_tab: MessageSubTab,
broadcast_sub_tab: BroadcastSubTab,
clear_button: button::State,
send_button: button::State,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum SubTab {
Message,
Broadcast,
}
impl Default for SubTab {
fn default() -> Self {
Self::Message
}
}
#[derive(Clone, Debug, Default)]
struct SubTabs {
message_button: button::State,
broadcast_button: button::State,
}
impl SubTabs {
pub(crate) fn view(&mut self, config: &GuiConfig, current_tab: SubTab) -> Row<gui::Message> {
let text_size = config.text_size();
let tab_button = |state, label, tab| {
Button::new(state, Text::new(label).size(text_size))
.style(style::TabButton::new(tab == current_tab))
.on_press(gui::Message::SendMessage(Message::SubTabSelected(tab)))
.padding(text_size / 6)
};
Row::new().spacing(text_size / 4).push(
Row::new()
.width(Length::Shrink)
.spacing(text_size / 4)
.push(tab_button(
&mut self.message_button,
"Send ordinary Message",
SubTab::Message,
))
.push(tab_button(
&mut self.broadcast_button,
"Send Message to your Subscribers",
SubTab::Broadcast,
)),
)
}
}
const RANDOM_TTL_SIZE: u64 = 600;
fn send_msg(
command_sender: &mut Sender<NodeCommand>,
from_identity: &PrivateIdentity,
to_identity: &PublicIdentity,
subject: &str,
body: &str,
logger: &mut Logger,
) {
debug!("Start send_msg");
let simple = encoding::Simple::new(subject.as_bytes().to_vec(), body.as_bytes().to_vec());
if let Err(err) = simple {
error!("{}", err);
return;
}
let simple = simple.unwrap();
let mut simple_bytes = Vec::new();
if let Err(err) = simple.write_to(&mut simple_bytes) {
error!("{}", err);
return;
}
let rand_ttl = rand::thread_rng().gen_range(0, RANDOM_TTL_SIZE);
let expires_time =
(Time::now().as_secs() + 60 * 60 * 24 * 2 + rand_ttl - RANDOM_TTL_SIZE / 2).into();
let object_type = object::ObjectKind::Msg.into();
let version = 1.into();
let stream_number = from_identity.address().stream();
let header = object::Header::new(expires_time, object_type, version, stream_number);
let mut header_bytes = Vec::new();
header.write_to(&mut header_bytes).unwrap();
let msg = content::Msg::new(
&header_bytes,
&from_identity,
&to_identity,
encoding::Encoding::Simple,
simple_bytes,
);
if let Err(err) = msg {
error!("{}", err);
return;
}
let msg = msg.unwrap();
let mut msg_bytes = Vec::new();
msg.write_to(&mut msg_bytes).unwrap();
let public_key = to_identity.public_encryption_key();
let encrypted = crypto::Encrypted::encrypt(msg_bytes, &public_key);
if let Err(err) = encrypted {
error!("{}", err);
return;
}
let encrypted = encrypted.unwrap();
let mut encrypted_bytes = Vec::new();
encrypted.write_to(&mut encrypted_bytes).unwrap();
let msg = object::Msg::new(encrypted_bytes);
let mut payload = Vec::new();
msg.write_to(&mut payload).unwrap();
if let Err(err) = task::block_on(command_sender.send(NodeCommand::Send { header, payload })) {
error!("{}", err);
}
debug!("End send_msg");
logger.info("Message sent");
}
fn send_broadcast(
command_sender: &mut Sender<NodeCommand>,
from_identity: &PrivateIdentity,
subject: &str,
body: &str,
logger: &mut Logger,
) {
debug!("Start send_broadcast");
let simple = encoding::Simple::new(subject.as_bytes().to_vec(), body.as_bytes().to_vec());
if let Err(err) = simple {
error!("{}", err);
return;
}
let simple = simple.unwrap();
let mut simple_bytes = Vec::new();
if let Err(err) = simple.write_to(&mut simple_bytes) {
error!("{}", err);
return;
}
let rand_ttl = rand::thread_rng().gen_range(0, RANDOM_TTL_SIZE);
let expires_time =
(Time::now().as_secs() + 60 * 60 * 24 * 2 + rand_ttl - RANDOM_TTL_SIZE / 2).into();
let object_type = object::ObjectKind::Broadcast.into();
let version = 5.into();
let stream_number = from_identity.address().stream();
let header = object::Header::new(expires_time, object_type, version, stream_number);
let mut header_bytes = Vec::new();
header.write_to(&mut header_bytes).unwrap();
if version.as_u64() == 5 {
from_identity
.address()
.broadcast_tag()
.write_to(&mut header_bytes)
.unwrap();
}
let broadcast = content::Broadcast::new(
&header_bytes,
&from_identity,
encoding::Encoding::Simple,
simple_bytes,
);
if let Err(err) = broadcast {
error!("{}", err);
return;
}
let broadcast = broadcast.unwrap();
let mut broadcast_bytes = Vec::new();
broadcast.write_to(&mut broadcast_bytes).unwrap();
let private_key = from_identity.address().broadcast_private_encryption_key();
if let Err(err) = private_key {
error!("{}", err);
return;
}
let private_key = private_key.unwrap();
let encrypted = crypto::Encrypted::encrypt(broadcast_bytes, &private_key.public_key());
if let Err(err) = encrypted {
error!("{}", err);
return;
}
let encrypted = encrypted.unwrap();
let mut encrypted_bytes = Vec::new();
encrypted.write_to(&mut encrypted_bytes).unwrap();
let broadcast_v5 =
object::BroadcastV5::new(from_identity.address().broadcast_tag(), encrypted_bytes);
let mut payload = Vec::new();
broadcast_v5.write_to(&mut payload).unwrap();
if let Err(err) = task::block_on(command_sender.send(NodeCommand::Send { header, payload })) {
error!("{}", err);
}
debug!("End send_broadcast");
logger.info("Broadcast sent");
}
impl Tab {
pub(crate) fn update(
&mut self,
message: Message,
state: gui::State,
boxes: &mut Option<Boxes>,
command_sender: &mut Sender<NodeCommand>,
logger: &mut Logger,
) -> Command<gui::Message> {
match message {
Message::SubTabSelected(sub_tab) => {
self.sub_tab = sub_tab;
Command::none()
}
Message::MessageSubjectChanged(s) => {
self.message_sub_tab.subject_value = s;
Command::none()
}
Message::MessageBodyChanged(s) => {
self.message_sub_tab.body_value = s;
Command::none()
}
Message::MessagePastePressed => {
let ctx = copypasta::ClipboardContext::new();
if let Err(err) = ctx {
error!("{}", err);
return Command::none();
}
let mut ctx = ctx.unwrap();
let content = ctx.get_contents();
if let Err(err) = content {
error!("{}", err);
return Command::none();
}
self.message_sub_tab.body_value = content.unwrap();
Command::none()
}
Message::BroadcastSubjectChanged(s) => {
self.broadcast_sub_tab.subject_value = s;
Command::none()
}
Message::BroadcastBodyChanged(s) => {
self.broadcast_sub_tab.body_value = s;
Command::none()
}
Message::BroadcastPastePressed => {
let ctx = copypasta::ClipboardContext::new();
if let Err(err) = ctx {
error!("{}", err);
return Command::none();
}
let mut ctx = ctx.unwrap();
let content = ctx.get_contents();
if let Err(err) = content {
error!("{}", err);
return Command::none();
}
self.broadcast_sub_tab.body_value = content.unwrap();
Command::none()
}
Message::ClearPressed => {
self.message_sub_tab.subject_value = String::new();
self.message_sub_tab.body_value = String::new();
self.broadcast_sub_tab.subject_value = String::new();
self.broadcast_sub_tab.body_value = String::new();
Command::none()
}
Message::SendPressed => {
if state != gui::State::Running {
logger.error("Run node before send message");
return Command::none();
}
if boxes.is_none() {
return Command::none();
}
let boxes = boxes.as_mut().unwrap();
if boxes.selected_identity_index().is_none() {
return Command::none();
}
let index = boxes.selected_identity_index().unwrap();
let from_identity = &boxes.user().private_identities()[index];
match self.sub_tab {
SubTab::Message => {
if boxes.selected_contact_index().is_none() {
return Command::none();
}
let to_index = boxes.selected_contact_index().unwrap();
let to_address = &boxes.user().contacts()[to_index].address().clone();
let to_identity = boxes.user().private_identity_by_address(to_address);
if to_identity.is_none() {
return Command::none();
}
let to_identity = to_identity.unwrap();
if !to_identity.chan() {
logger.error("Currently, restricted to chan");
return Command::none();
}
send_msg(
command_sender,
from_identity,
&to_identity.into(),
&self.message_sub_tab.subject_value,
&self.message_sub_tab.body_value,
logger,
);
boxes.set_selected_identity_index(None);
boxes.set_selected_contact_index(None);
self.message_sub_tab.subject_value = String::new();
self.message_sub_tab.body_value = String::new();
}
SubTab::Broadcast => {
send_broadcast(
command_sender,
from_identity,
&self.broadcast_sub_tab.subject_value,
&self.broadcast_sub_tab.body_value,
logger,
);
boxes.set_selected_identity_index(None);
boxes.set_selected_contact_index(None);
self.broadcast_sub_tab.subject_value = String::new();
self.broadcast_sub_tab.body_value = String::new();
}
}
Command::none()
}
}
}
pub(crate) fn view(
&mut self,
config: &GuiConfig,
state: gui::State,
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 sub_tabs = self.sub_tabs.view(config, self.sub_tab);
let mut column = Column::new().spacing(text_size / 4).push(sub_tabs);
let color = Color {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
match self.sub_tab {
SubTab::Message => {
let mut from = Row::new()
.spacing(text_size / 2)
.push(Text::new("From: ").size(text_size).color(color));
if let Some(index) = boxes.selected_identity_index() {
let identity = &boxes.user().private_identities()[index];
let alias = boxes.user().rich_alias(&identity.to_string());
from = from.push(Text::new(alias).size(text_size));
} else {
from = from.push(Text::new("-- Select from Identities tab --").size(text_size));
}
let mut to = Row::new()
.spacing(text_size / 2)
.push(Text::new("To: ").size(text_size).color(color));
if let Some(index) = boxes.selected_contact_index() {
let contact = &boxes.user().contacts()[index];
let alias = boxes.user().rich_alias(&contact.address().to_string());
to = to.push(Text::new(alias).size(text_size));
} else {
to = to.push(Text::new("-- Select from Contacts tab --").size(text_size));
}
let subject = Row::new()
.spacing(text_size / 2)
.push(Text::new("Subject: ").size(text_size).color(color))
.push(
TextInput::new(
&mut self.message_sub_tab.subject,
"Subject",
&self.message_sub_tab.subject_value,
|a| gui::Message::SendMessage(Message::MessageSubjectChanged(a)),
)
.size(text_size)
.padding(text_size / 4),
);
let body = Row::new()
.spacing(text_size / 2)
.push(Text::new("Body: ").size(text_size).color(color))
.push(
TextInput::new(
&mut self.message_sub_tab.body,
"Body",
&self.message_sub_tab.body_value,
|a| gui::Message::SendMessage(Message::MessageBodyChanged(a)),
)
.size(text_size)
.padding(text_size / 4),
);
let paste = Button::new(
&mut self.message_sub_tab.paste_button,
Text::new("Paste").size(text_size),
)
.on_press(gui::Message::SendMessage(Message::MessagePastePressed));
let content = Scrollable::new(&mut self.message_sub_tab.body_scroll)
.max_height(256)
.push(
Text::new(&self.message_sub_tab.body_value)
.size(text_size)
.width(Length::Fill),
);
column = column
.push(from)
.push(to)
.push(subject)
.push(body)
.push(paste)
.push(content);
}
SubTab::Broadcast => {
let mut from = Row::new()
.spacing(text_size / 2)
.push(Text::new("From: ").color(color).size(text_size));
if let Some(index) = boxes.selected_identity_index() {
let identity = &boxes.user().private_identities()[index];
let alias = boxes.user().rich_alias(&identity.to_string());
from = from.push(Text::new(alias).size(text_size));
} else {
from = from.push(Text::new("-- Select from Identities tab --").size(text_size));
}
let subject = Row::new()
.spacing(text_size / 2)
.push(Text::new("Subject: ").color(color).size(text_size))
.push(
TextInput::new(
&mut self.broadcast_sub_tab.subject,
"Subject",
&self.broadcast_sub_tab.subject_value,
|a| gui::Message::SendMessage(Message::BroadcastSubjectChanged(a)),
)
.size(text_size)
.padding(text_size / 4),
);
let body = Row::new()
.spacing(text_size / 2)
.push(Text::new("Body: ").color(color).size(text_size))
.push(
TextInput::new(
&mut self.broadcast_sub_tab.body,
"Body",
&self.broadcast_sub_tab.body_value,
|a| gui::Message::SendMessage(Message::BroadcastBodyChanged(a)),
)
.size(text_size)
.padding(text_size / 4),
);
let paste = Button::new(
&mut self.broadcast_sub_tab.paste_button,
Text::new("Paste").size(text_size),
)
.on_press(gui::Message::SendMessage(Message::BroadcastPastePressed));
let content = Scrollable::new(&mut self.broadcast_sub_tab.body_scroll)
.max_height(256)
.push(
Text::new(&self.broadcast_sub_tab.body_value)
.size(text_size)
.width(Length::Fill),
);
column = column
.push(from)
.push(subject)
.push(body)
.push(paste)
.push(content);
}
}
let clear_button = Button::new(&mut self.clear_button, Text::new("Clear").size(text_size))
.on_press(gui::Message::SendMessage(Message::ClearPressed));
let send_button = {
let color = match state {
gui::State::Running => style::SendButton::Enabled,
_ => style::SendButton::Disabled,
};
Button::new(&mut self.send_button, Text::new("Send").size(text_size))
.padding(text_size / 2)
.style(color)
.on_press(gui::Message::SendMessage(Message::SendPressed))
};
let buttons = Row::new()
.spacing(text_size / 4)
.push(clear_button)
.push(send_button);
column.push(buttons).into()
}
}