|
|
|
@ -1,5 +1,8 @@
|
|
|
|
|
use crate::chat::Chat; |
|
|
|
|
use crate::degeon_worker::Degeon; |
|
|
|
|
use crate::gui_events::GuiEvent; |
|
|
|
|
use crate::message::{DegMessage, ServiceMsg}; |
|
|
|
|
use crate::message::{DegMessage, DegMessageContent, Profile, ProtocolMsg}; |
|
|
|
|
use crate::styles::style; |
|
|
|
|
use core::default::Default; |
|
|
|
|
use futures::Stream; |
|
|
|
|
use iced::{ |
|
|
|
@ -13,326 +16,36 @@ use std::pin::Pin;
|
|
|
|
|
use std::sync::{Arc, Mutex}; |
|
|
|
|
use std::task::{Context, Poll}; |
|
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)] |
|
|
|
|
pub struct Chat { |
|
|
|
|
pkey: PublicKey, |
|
|
|
|
messages: Vec<(bool, DegMessage)>, |
|
|
|
|
name: String, |
|
|
|
|
scrolled: f32, |
|
|
|
|
pub input: String, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Chat { |
|
|
|
|
pub fn new(pkey: PublicKey) -> Self { |
|
|
|
|
Self { |
|
|
|
|
pkey, |
|
|
|
|
messages: vec![], |
|
|
|
|
name: "".to_string(), |
|
|
|
|
scrolled: 0.0, |
|
|
|
|
input: "".to_string(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn view_message(msg: &(bool, DegMessage)) -> Option<Element<GuiEvent>> { |
|
|
|
|
let msg = &msg.1; |
|
|
|
|
match msg { |
|
|
|
|
DegMessage::Text(t) => Some( |
|
|
|
|
iced::Container::new(Text::new(t.as_str())) |
|
|
|
|
.padding(10) |
|
|
|
|
.style(style::Container::Message) |
|
|
|
|
.into(), |
|
|
|
|
), |
|
|
|
|
DegMessage::File(_) => None, |
|
|
|
|
DegMessage::Service(_) => None, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
mod style { |
|
|
|
|
use iced::container::Style; |
|
|
|
|
use iced::{button, Background, Color, Vector}; |
|
|
|
|
|
|
|
|
|
#[derive(Clone, Copy, PartialEq, Eq)] |
|
|
|
|
pub enum Button { |
|
|
|
|
Primary, |
|
|
|
|
Secondary, |
|
|
|
|
Destructive, |
|
|
|
|
InactiveChat, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl button::StyleSheet for Button { |
|
|
|
|
fn active(&self) -> button::Style { |
|
|
|
|
button::Style { |
|
|
|
|
background: Some(Background::Color(match self { |
|
|
|
|
Button::Primary => Color::from_rgb(0.11, 0.35, 0.75), |
|
|
|
|
Button::Secondary => Color::from_rgb(0.3, 0.1, 0.7), |
|
|
|
|
Button::Destructive => Color::from_rgb(0.8, 0.2, 0.2), |
|
|
|
|
Button::InactiveChat => Color::from_rgb(0.3, 0.52, 0.9), |
|
|
|
|
})), |
|
|
|
|
border_radius: 5.0, |
|
|
|
|
shadow_offset: Vector::new(1.0, 1.0), |
|
|
|
|
text_color: if self != &Button::InactiveChat { |
|
|
|
|
Color::WHITE |
|
|
|
|
pub fn view_message(msg: &DegMessage, pkey: PublicKey) -> Option<Element<GuiEvent>> { |
|
|
|
|
let is_from_me = pkey != msg.sender; |
|
|
|
|
match &msg.content { |
|
|
|
|
DegMessageContent::Text(t) => Some( |
|
|
|
|
iced::Row::new() |
|
|
|
|
.push( |
|
|
|
|
iced::Container::new(Text::new(t.as_str())) |
|
|
|
|
.padding(10) |
|
|
|
|
.style(if is_from_me { |
|
|
|
|
style::Container::MyMessage |
|
|
|
|
} else { |
|
|
|
|
style::Container::NotMyMessage |
|
|
|
|
}) |
|
|
|
|
.align_x(if is_from_me { Align::End } else { Align::Start }), |
|
|
|
|
) |
|
|
|
|
.align_items(if is_from_me { Align::End } else { Align::Start }) |
|
|
|
|
.width(if is_from_me { |
|
|
|
|
Length::Shrink |
|
|
|
|
} else { |
|
|
|
|
Color::BLACK |
|
|
|
|
}, |
|
|
|
|
..button::Style::default() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub enum Container { |
|
|
|
|
Primary, |
|
|
|
|
Message, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl iced::container::StyleSheet for Container { |
|
|
|
|
fn style(&self) -> Style { |
|
|
|
|
iced::container::Style { |
|
|
|
|
text_color: Some(Color::WHITE), |
|
|
|
|
background: Some(Background::Color(match self { |
|
|
|
|
Container::Primary => Color::from_rgb(18. / 256., 25. / 256., 70. / 256.), |
|
|
|
|
Container::Message => Color::from_rgb(0., 0.1, 0.8), |
|
|
|
|
})), |
|
|
|
|
border_radius: 5.0, |
|
|
|
|
border_width: 0.6, |
|
|
|
|
border_color: Color::TRANSPARENT, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Chat { |
|
|
|
|
pub fn header<'a>(name: String) -> Element<'a, GuiEvent> { |
|
|
|
|
iced::container::Container::new(Text::new(name.as_str()).color(iced::Color::WHITE)) |
|
|
|
|
.style(style::Container::Primary) |
|
|
|
|
.width(Length::Fill) |
|
|
|
|
.height(Length::Units(50)) |
|
|
|
|
.padding(10) |
|
|
|
|
.into() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn send_field<'a>( |
|
|
|
|
input: String, |
|
|
|
|
text_input_state: &'a mut iced::text_input::State, |
|
|
|
|
send_button_state: &'a mut iced::button::State, |
|
|
|
|
) -> Element<'a, GuiEvent> { |
|
|
|
|
Row::new() |
|
|
|
|
.width(Length::Fill) |
|
|
|
|
.padding(15) |
|
|
|
|
.push( |
|
|
|
|
TextInput::new(text_input_state, "Message", input.as_str(), |st| { |
|
|
|
|
GuiEvent::Typed(st) |
|
|
|
|
Length::Fill |
|
|
|
|
}) |
|
|
|
|
.padding(8) |
|
|
|
|
.width(Length::Fill), |
|
|
|
|
) |
|
|
|
|
.push( |
|
|
|
|
Button::new(send_button_state, Text::new("Send")) |
|
|
|
|
.on_press(GuiEvent::SendClick) |
|
|
|
|
.style(style::Button::Secondary) |
|
|
|
|
.padding(20) |
|
|
|
|
.width(Length::Units(80)), |
|
|
|
|
) |
|
|
|
|
.spacing(25) |
|
|
|
|
.height(Length::Units(100)) |
|
|
|
|
.into() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn preview<'a>( |
|
|
|
|
&'a self, |
|
|
|
|
state: &'a mut button::State, |
|
|
|
|
i: usize, |
|
|
|
|
is_selected: bool, |
|
|
|
|
) -> Element<'a, GuiEvent> { |
|
|
|
|
Button::new(state, Text::new(self.name.as_str())) |
|
|
|
|
.width(Length::Fill) |
|
|
|
|
.padding(10) |
|
|
|
|
.style(if is_selected { |
|
|
|
|
style::Button::Primary |
|
|
|
|
} else { |
|
|
|
|
style::Button::InactiveChat |
|
|
|
|
}) |
|
|
|
|
.on_press(GuiEvent::ChatSelect(i)) |
|
|
|
|
.into() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn view<'a>( |
|
|
|
|
&'a self, |
|
|
|
|
text_input_state: &'a mut iced::text_input::State, |
|
|
|
|
send_button_state: &'a mut iced::button::State, |
|
|
|
|
) -> Element<'a, GuiEvent> { |
|
|
|
|
let msgs = self.messages.iter().filter_map(view_message).collect(); |
|
|
|
|
Column::new() |
|
|
|
|
.align_items(Align::End) |
|
|
|
|
.height(Length::Fill) |
|
|
|
|
.width(Length::FillPortion(4)) |
|
|
|
|
.push(Self::header(self.name.clone())) |
|
|
|
|
.push( |
|
|
|
|
Column::with_children(msgs) |
|
|
|
|
.padding(20) |
|
|
|
|
.spacing(10) |
|
|
|
|
.align_items(Align::End) |
|
|
|
|
.height(Length::FillPortion(9)), |
|
|
|
|
) |
|
|
|
|
.spacing(10) |
|
|
|
|
.push(Self::send_field( |
|
|
|
|
self.input.to_string(), |
|
|
|
|
text_input_state, |
|
|
|
|
send_button_state, |
|
|
|
|
)) |
|
|
|
|
.into() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn example(i: usize) -> Chat { |
|
|
|
|
Self { |
|
|
|
|
pkey: Keys::generate().get_public(), |
|
|
|
|
messages: vec![(false, DegMessage::Text(format!("Example message {}", i)))], |
|
|
|
|
name: format!("Example user ({})", i), |
|
|
|
|
scrolled: 0.0, |
|
|
|
|
input: "".to_string(), |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Clone)] |
|
|
|
|
pub struct Degeon { |
|
|
|
|
pub chats: Vec<Chat>, |
|
|
|
|
pub my_name: String, |
|
|
|
|
pub keys: Keys, |
|
|
|
|
pub ironforce: Arc<Mutex<IronForce>>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Default for Degeon { |
|
|
|
|
fn default() -> Self { |
|
|
|
|
let ironforce = IronForce::from_file("".to_string()).unwrap(); |
|
|
|
|
let keys = ironforce.keys.clone(); |
|
|
|
|
let (_thread, ironforce) = ironforce.launch_main_loop(500); |
|
|
|
|
Self { |
|
|
|
|
chats: vec![], |
|
|
|
|
my_name: "".to_string(), |
|
|
|
|
keys, |
|
|
|
|
ironforce, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Degeon { |
|
|
|
|
pub fn chat_with(&self, pkey: &PublicKey) -> Option<usize> { |
|
|
|
|
self.chats.iter().position(|chat| &chat.pkey == pkey) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn process_message(&self, msg: ironforce::Message) -> IFResult<Option<GuiEvent>> { |
|
|
|
|
let deg_msg: DegMessage = |
|
|
|
|
match serde_json::from_slice(msg.get_decrypted(&self.keys)?.as_slice()) { |
|
|
|
|
Ok(r) => r, |
|
|
|
|
Err(_) => return Ok(None) |
|
|
|
|
}; |
|
|
|
|
let sender = msg.get_sender(&self.keys).unwrap(); |
|
|
|
|
Ok(match °_msg { |
|
|
|
|
DegMessage::Text(_) | DegMessage::File(_) => { |
|
|
|
|
Some(GuiEvent::NewMessageInChat(sender, deg_msg)) |
|
|
|
|
} |
|
|
|
|
DegMessage::Service(msg) => match msg { |
|
|
|
|
ServiceMsg::NameRequest => self |
|
|
|
|
.send_message( |
|
|
|
|
DegMessage::Service(ServiceMsg::NameStatement(self.my_name.clone())), |
|
|
|
|
&sender, |
|
|
|
|
) |
|
|
|
|
.map(|_| None)?, |
|
|
|
|
ServiceMsg::NameStatement(name) => { |
|
|
|
|
Some(GuiEvent::SetName(sender, name.to_string())) |
|
|
|
|
} |
|
|
|
|
ServiceMsg::Ping => self |
|
|
|
|
.send_message(DegMessage::Service(ServiceMsg::NameStatement(self.my_name.clone())), &sender) |
|
|
|
|
.map(|_| None)?, |
|
|
|
|
}, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn send_multicast(&self, msg: DegMessage) -> IFResult<()> { |
|
|
|
|
self.ironforce.lock().unwrap().send_to_all( |
|
|
|
|
Message::build() |
|
|
|
|
.message_type(MessageType::Broadcast) |
|
|
|
|
.content(serde_json::to_vec(&msg)?) |
|
|
|
|
.sign(&self.keys) |
|
|
|
|
.build()?, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pub fn send_message(&self, msg: DegMessage, target: &PublicKey) -> IFResult<()> { |
|
|
|
|
// if self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
|
|
|
|
|
// println!("Creating a tunnel");
|
|
|
|
|
// self.ironforce
|
|
|
|
|
// .lock()
|
|
|
|
|
// .unwrap()
|
|
|
|
|
// .initialize_tunnel_creation(target)?;
|
|
|
|
|
// let mut counter = 0;
|
|
|
|
|
// while self.ironforce.lock().unwrap().get_tunnel(target).is_none() {
|
|
|
|
|
// std::thread::sleep(std::time::Duration::from_millis(350));
|
|
|
|
|
// counter += 1;
|
|
|
|
|
// if counter > 100 {
|
|
|
|
|
// return Err(IFError::TunnelNotFound);
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
self.ironforce.lock().unwrap().send_to_all( |
|
|
|
|
Message::build() |
|
|
|
|
.message_type(MessageType::Broadcast) |
|
|
|
|
.content(serde_json::to_vec(&msg)?) |
|
|
|
|
.recipient(target) |
|
|
|
|
.sign(&self.keys) |
|
|
|
|
.build()?, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Stream for Degeon { |
|
|
|
|
type Item = GuiEvent; |
|
|
|
|
|
|
|
|
|
fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { |
|
|
|
|
println!("Degeon worker is being polled"); |
|
|
|
|
let msg = self.ironforce.lock().unwrap().read_message(); |
|
|
|
|
println!("Msg: {:?}", msg); |
|
|
|
|
match msg.map(|msg| self.process_message(msg).unwrap()) { |
|
|
|
|
None | Some(None) => Poll::Pending, |
|
|
|
|
Some(Some(msg)) => { |
|
|
|
|
Poll::Ready(Some(msg)) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl<H, I> iced_native::subscription::Recipe<H, I> for Degeon |
|
|
|
|
where |
|
|
|
|
H: std::hash::Hasher, |
|
|
|
|
{ |
|
|
|
|
type Output = GuiEvent; |
|
|
|
|
|
|
|
|
|
fn hash(&self, state: &mut H) { |
|
|
|
|
use std::hash::Hash; |
|
|
|
|
|
|
|
|
|
std::any::TypeId::of::<Self>().hash(state); |
|
|
|
|
self.ironforce.lock().unwrap().hash(state); |
|
|
|
|
|
|
|
|
|
// std::time::SystemTime::now().hash(state);
|
|
|
|
|
std::time::UNIX_EPOCH |
|
|
|
|
.elapsed() |
|
|
|
|
.unwrap() |
|
|
|
|
.as_secs() |
|
|
|
|
.hash(state); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fn stream( |
|
|
|
|
self: Box<Self>, |
|
|
|
|
_input: futures::stream::BoxStream<'static, I>, |
|
|
|
|
) -> futures::stream::BoxStream<'static, Self::Output> { |
|
|
|
|
Box::pin(self) |
|
|
|
|
.into(), |
|
|
|
|
), |
|
|
|
|
DegMessageContent::File(_) => None, |
|
|
|
|
DegMessageContent::Service => None, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[derive(Default)] |
|
|
|
|
pub struct State { |
|
|
|
|
pub struct DegeonApp { |
|
|
|
|
pub data: Degeon, |
|
|
|
|
selected_chat: usize, |
|
|
|
|
send_button_state: iced::button::State, |
|
|
|
@ -340,7 +53,7 @@ pub struct State {
|
|
|
|
|
preview_button_states: Vec<button::State>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl State { |
|
|
|
|
impl DegeonApp { |
|
|
|
|
fn chat_list<'a>( |
|
|
|
|
chats: &'a Vec<Chat>, |
|
|
|
|
preview_button_states: &'a mut Vec<button::State>, |
|
|
|
@ -382,26 +95,25 @@ impl State {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
impl Application for State { |
|
|
|
|
impl Application for DegeonApp { |
|
|
|
|
type Executor = iced::executor::Default; |
|
|
|
|
type Message = GuiEvent; |
|
|
|
|
type Flags = (); |
|
|
|
|
|
|
|
|
|
fn new(_: ()) -> (Self, iced::Command<GuiEvent>) { |
|
|
|
|
let mut st = Self::default(); |
|
|
|
|
st.data.chats = vec![Chat::example(1), Chat::example(2)]; |
|
|
|
|
st.data.chats = vec![ |
|
|
|
|
Chat::example(1, &st.data.keys), |
|
|
|
|
Chat::example(2, &st.data.keys), |
|
|
|
|
]; |
|
|
|
|
st.preview_button_states = vec![Default::default(), Default::default()]; |
|
|
|
|
st.data.my_name = "John".to_string(); |
|
|
|
|
st.data |
|
|
|
|
.send_multicast(DegMessage::Service(ServiceMsg::Ping)) |
|
|
|
|
.unwrap(); |
|
|
|
|
st.data.send_multicast(ProtocolMsg::Ping).unwrap(); |
|
|
|
|
let data_clone = st.data.clone(); |
|
|
|
|
std::thread::spawn(move || { |
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(10)); |
|
|
|
|
loop { |
|
|
|
|
data_clone |
|
|
|
|
.send_multicast(DegMessage::Service(ServiceMsg::Ping)) |
|
|
|
|
.unwrap(); |
|
|
|
|
data_clone.send_multicast(ProtocolMsg::Ping).unwrap(); |
|
|
|
|
std::thread::sleep(std::time::Duration::from_secs(120)); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
@ -416,18 +128,21 @@ impl Application for State {
|
|
|
|
|
match message { |
|
|
|
|
GuiEvent::ChatSelect(i) => self.selected_chat = i, |
|
|
|
|
GuiEvent::Typed(st) => self.data.chats[self.selected_chat].input = st, |
|
|
|
|
GuiEvent::SendClick => { |
|
|
|
|
GuiEvent::SendMessage => { |
|
|
|
|
if self.data.chats[self.selected_chat].input.is_empty() { |
|
|
|
|
return iced::Command::none(); |
|
|
|
|
} |
|
|
|
|
let new_msg = DegMessage::Text(self.data.chats[self.selected_chat].input.clone()); |
|
|
|
|
let new_msg = DegMessage::new_text( |
|
|
|
|
self.data.chats[self.selected_chat].input.clone(), |
|
|
|
|
&self.data.keys.get_public(), |
|
|
|
|
); |
|
|
|
|
self.data.chats[self.selected_chat].input = String::new(); |
|
|
|
|
self.data.chats[self.selected_chat] |
|
|
|
|
.messages |
|
|
|
|
.push((true, new_msg.clone())); |
|
|
|
|
.push(new_msg.clone()); |
|
|
|
|
let data_cloned = self.data.clone(); |
|
|
|
|
let target = self.data.chats[self.selected_chat].pkey.clone(); |
|
|
|
|
std::thread::spawn(move || data_cloned.send_message(new_msg, &target).unwrap()); |
|
|
|
|
std::thread::spawn(move || data_cloned.send_message(ProtocolMsg::NewMessage(new_msg), &target).unwrap()); |
|
|
|
|
} |
|
|
|
|
GuiEvent::NewChat(pkey) => { |
|
|
|
|
if self.data.chat_with(&pkey).is_none() { |
|
|
|
@ -439,14 +154,22 @@ impl Application for State {
|
|
|
|
|
self.data.chats.push(Chat::new(pkey.clone())) |
|
|
|
|
} |
|
|
|
|
let ind = self.data.chat_with(&pkey).unwrap(); |
|
|
|
|
self.data.chats[ind].messages.push((false, msg)) |
|
|
|
|
self.data.chats[ind].messages.push(msg) |
|
|
|
|
} |
|
|
|
|
GuiEvent::SetName(pkey, name) => { |
|
|
|
|
GuiEvent::SetProfile(pkey, name) => { |
|
|
|
|
if self.data.chat_with(&pkey).is_none() { |
|
|
|
|
self.data.chats.push(Chat::new(pkey.clone())) |
|
|
|
|
} |
|
|
|
|
let ind = self.data.chat_with(&pkey).unwrap(); |
|
|
|
|
self.data.chats[ind].name = name; |
|
|
|
|
self.data.chats[ind].profile = name; |
|
|
|
|
} |
|
|
|
|
GuiEvent::None => {} |
|
|
|
|
GuiEvent::WeHaveToSendProfile(target) => { |
|
|
|
|
println!("WHTSP"); |
|
|
|
|
return self.data.get_send_command( |
|
|
|
|
ProtocolMsg::ProfileResponse(self.data.get_profile()), |
|
|
|
|
&target, |
|
|
|
|
) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
iced::Command::none() |
|
|
|
|