use async_std::task;
use futures::{channel::mpsc::Sender, sink::SinkExt};
use iced::{
widget::{button, scrollable, Column, Row, Text, TextInput},
Color, Command, Element, Length,
};
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, Default)]
struct MessageSubTab {
subject_value: String,
body_value: String,
}
#[derive(Clone, Debug, Default)]
struct BroadcastSubTab {
subject_value: String,
body_value: String,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Tab {
sub_tabs: SubTabs,
sub_tab: SubTab,
message_sub_tab: MessageSubTab,
broadcast_sub_tab: BroadcastSubTab,
}
#[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 {}
impl SubTabs {
pub(crate) fn view(&self, config: &GuiConfig, current_tab: SubTab) -> Row<gui::Message> {
let text_size = config.text_size();
let tab_button = |label, tab| {
button(Text::new(label).size(text_size))
.style(iced::theme::Button::Custom(Box::new(
style::TabButton::new(tab == current_tab),
)))
.on_press(gui::Message::SendSubTabSelected(tab))
.padding(text_size / 6)
};
Row::new().spacing(text_size / 4).push(
Row::new()
.width(Length::Shrink)
.spacing(text_size / 4)
.push(tab_button("Send ordinary Message", SubTab::Message))
.push(tab_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: gui::Message,
state: gui::State,
boxes: &mut Option<Boxes>,
command_sender: &mut Sender<NodeCommand>,
logger: &mut Logger,
) -> Command<gui::Message> {
match message {
gui::Message::SendSubTabSelected(sub_tab) => {
self.sub_tab = sub_tab;
Command::none()
}
gui::Message::SendMessageSubjectChanged(s) => {
self.message_sub_tab.subject_value = s;
Command::none()
}
gui::Message::SendMessageBodyChanged(s) => {
self.message_sub_tab.body_value = s;
Command::none()
}
gui::Message::SendMessagePastePressed => {
iced::clipboard::read(|s| gui::Message::SendMessagePasteContent(s))
}
gui::Message::SendMessagePasteContent(s) => {
if let Some(s) = s {
self.message_sub_tab.body_value = s;
}
Command::none()
}
gui::Message::SendBroadcastSubjectChanged(s) => {
self.broadcast_sub_tab.subject_value = s;
Command::none()
}
gui::Message::SendBroadcastBodyChanged(s) => {
self.broadcast_sub_tab.body_value = s;
Command::none()
}
gui::Message::SendBroadcastPastePressed => {
iced::clipboard::read(|s| gui::Message::SendBroadcastPasteContent(s))
}
gui::Message::SendBroadcastPasteContent(s) => {
if let Some(s) = s {
self.broadcast_sub_tab.body_value = s;
}
Command::none()
}
gui::Message::SendClearPressed => {
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()
}
gui::Message::SendSendPressed => {
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()
}
_ => panic!("Program error"),
}
}
pub(crate) fn view(
&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).style(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).style(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).style(color))
.push(
TextInput::new("Subject", &self.message_sub_tab.subject_value)
.on_input(gui::Message::SendMessageSubjectChanged)
.size(text_size)
.padding(text_size / 4),
);
let body = Row::new()
.spacing(text_size / 2)
.push(Text::new("Body: ").size(text_size).style(color))
.push(
TextInput::new("Body", &self.message_sub_tab.body_value)
.on_input(gui::Message::SendMessageBodyChanged)
.size(text_size)
.padding(text_size / 4),
);
let paste = button(Text::new("Paste").size(text_size))
.on_press(gui::Message::SendMessagePastePressed);
let content = scrollable(
Text::new(&self.message_sub_tab.body_value)
.size(text_size)
.width(Length::Fill),
)
.height(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: ").style(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: ").style(color).size(text_size))
.push(
TextInput::new("Subject", &self.broadcast_sub_tab.subject_value)
.on_input(gui::Message::SendBroadcastSubjectChanged)
.size(text_size)
.padding(text_size / 4),
);
let body = Row::new()
.spacing(text_size / 2)
.push(Text::new("Body: ").style(color).size(text_size))
.push(
TextInput::new("Body", &self.broadcast_sub_tab.body_value)
.on_input(gui::Message::SendBroadcastBodyChanged)
.size(text_size)
.padding(text_size / 4),
);
let paste = button(Text::new("Paste").size(text_size))
.on_press(gui::Message::SendBroadcastPastePressed);
let content = scrollable(
Text::new(&self.broadcast_sub_tab.body_value)
.size(text_size)
.width(Length::Fill),
)
.height(Length::Fill);
column = column
.push(from)
.push(subject)
.push(body)
.push(paste)
.push(content);
}
}
let clear_button =
button(Text::new("Clear").size(text_size)).on_press(gui::Message::SendClearPressed);
let send_button = {
let color = match state {
gui::State::Running => {
iced::theme::Button::Custom(Box::new(style::SendButton::Enabled))
}
_ => iced::theme::Button::Custom(Box::new(style::SendButton::Disabled)),
};
button(Text::new("Send").size(text_size))
.padding(text_size / 2)
.style(color)
.on_press(gui::Message::SendSendPressed)
};
let buttons = Row::new()
.spacing(text_size / 4)
.push(clear_button)
.push(send_button);
column.push(buttons).into()
}
}