diff --git a/Cargo.lock b/Cargo.lock index dfebbfc..1b224a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,19 @@ dependencies = [ "libc", ] +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi 0.3.9", +] + [[package]] name = "clipboard-win" version = "4.2.2" @@ -553,6 +566,7 @@ name = "degeon" version = "0.1.0" dependencies = [ "base64", + "chrono", "futures", "iced", "iced_native", @@ -992,7 +1006,7 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", ] [[package]] @@ -2795,6 +2809,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7572415bd688d401c52f6e36f4c8e805b9ae1622619303b9fa835d531db0acae" +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -2950,9 +2975,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" diff --git a/degeon/Cargo.toml b/degeon/Cargo.toml index b7354eb..84e822e 100644 --- a/degeon/Cargo.toml +++ b/degeon/Cargo.toml @@ -12,4 +12,5 @@ base64 = "0.13.0" serde = { version = "1.0" } serde_json = "1.0.72" futures = "0.3.18" -iced_native = "0.4.0" \ No newline at end of file +iced_native = "0.4.0" +chrono = "0.4.19" \ No newline at end of file diff --git a/degeon/src/chat.rs b/degeon/src/chat.rs new file mode 100644 index 0000000..acff823 --- /dev/null +++ b/degeon/src/chat.rs @@ -0,0 +1,135 @@ +use iced::{Align, Button, Column, Element, Length, Row, Text, TextInput}; +use iced_native::button; +use ironforce::{Keys, PublicKey}; +use crate::gui_events::GuiEvent; +use crate::message::{DegMessage, DegMessageContent, Profile}; +use crate::state; +use crate::styles::style; + +#[derive(Clone, Debug)] +pub struct Chat { + pub pkey: PublicKey, + pub messages: Vec, + pub profile: Profile, + pub scrolled: f32, + pub input: String, +} + +impl Chat { + pub fn new(pkey: PublicKey) -> Self { + Self { + pkey, + messages: vec![], + profile: Profile { name: "".to_string() }, + scrolled: 0.0, + input: "".to_string(), + } + } +} + +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) + }) + .padding(8) + .width(Length::Fill), + ) + .push( + Button::new(send_button_state, Text::new("Send")) + .on_press(GuiEvent::SendMessage) + .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.profile.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 pkey_clone = self.pkey.clone(); + let msgs = self.messages.iter().filter_map(move |msg| state::view_message(msg, pkey_clone.clone())).collect(); + Column::new() + .align_items(Align::End) + .height(Length::Fill) + .width(Length::FillPortion(4)) + .push(Self::header(self.profile.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, my_keys: &Keys) -> Chat { + let pkey = Keys::generate().get_public(); + Self { + messages: vec![ + DegMessage { + sender: pkey.clone(), + timestamp: chrono::Utc::now().timestamp(), + content: DegMessageContent::Text(format!("Example message {}", 2 * i)) + }, + DegMessage { + sender: my_keys.get_public(), + timestamp: chrono::Utc::now().timestamp(), + content: DegMessageContent::Text(format!("Example message {}", 2 * i + 1)) + }, + + ], + profile: Profile { name: format!("Example user ({})", i) }, + scrolled: 0.0, + pkey, + input: "".to_string(), + } + } +} diff --git a/degeon/src/degeon_worker.rs b/degeon/src/degeon_worker.rs new file mode 100644 index 0000000..096f074 --- /dev/null +++ b/degeon/src/degeon_worker.rs @@ -0,0 +1,209 @@ +use crate::chat::Chat; +use crate::gui_events::GuiEvent; +use crate::message::{Profile, ProtocolMsg}; +use futures::Stream; +use ironforce::res::IFResult; +use ironforce::{IronForce, Keys, Message, MessageType, PublicKey}; +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll}; + +#[derive(Clone)] +pub struct Degeon { + pub chats: Vec, + pub my_name: String, + pub keys: Keys, + pub ironforce: Arc>, +} + +impl Default for Degeon { + fn default() -> Self { + let ironforce = IronForce::from_file("".to_string()).unwrap(); + let keys = ironforce.keys.clone(); + println!("ID: {}", keys.get_public().get_short_id()); + let (_thread, ironforce) = ironforce.launch_main_loop(1000); + Self { + chats: vec![], + my_name: "".to_string(), + keys, + ironforce, + } + } +} + +impl Degeon { + pub fn get_profile(&self) -> Profile { + Profile { + name: self.my_name.clone(), + } + } + + pub fn chat_with(&self, pkey: &PublicKey) -> Option { + self.chats.iter().position(|chat| &chat.pkey == pkey) + } + + pub fn process_message(&self, msg: ironforce::Message) -> IFResult> { + let deg_msg: ProtocolMsg = + 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(); + println!("check_rec: {:?}, sender==self: {:?}", msg.check_recipient(&self.keys), sender == self.keys.get_public()); + if !msg.check_recipient(&self.keys) || sender == self.keys.get_public() { + return Ok(None); + } + println!("{:?}", deg_msg); + Ok(match °_msg { + ProtocolMsg::NewMessage(deg_msg) => { + Some(GuiEvent::NewMessageInChat(sender, deg_msg.clone())) + } + ProtocolMsg::ProfileRequest | ProtocolMsg::Ping => { + Some(GuiEvent::WeHaveToSendProfile(sender)) + } + ProtocolMsg::ProfileResponse(prof) => Some(GuiEvent::SetProfile(sender, prof.clone())), + }) + } + + pub fn send_multicast(&self, msg: ProtocolMsg) -> 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: ProtocolMsg, 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()?, + ) + } + + pub fn get_send_command( + &self, + msg: ProtocolMsg, + target: &PublicKey, + ) -> iced::Command { + let if_clone = self.ironforce.clone(); + let target = target.clone(); + let keys = self.keys.clone(); + + println!("Creating a send command: {:?}", msg); + iced::Command::perform(async {}, move |_| { + println!("Sending message: {:?}", msg); + if_clone + .lock() + .unwrap() + .send_to_all( + Message::build() + .message_type(MessageType::Broadcast) + .content(serde_json::to_vec(&msg).unwrap()) + // .recipient(&target) + .sign(&keys) + .build() + .unwrap(), + ) + .unwrap(); + GuiEvent::None + }) + } + + pub fn get_send_multicast_command(&self, msg: ProtocolMsg) -> iced::Command { + let keys = self.keys.clone(); + let if_clone = self.ironforce.clone(); + println!("Created a send command"); + iced::Command::perform( + async move { + println!("Sending message: {:?}", msg); + if_clone + .lock() + .unwrap() + .send_to_all( + Message::build() + .message_type(MessageType::Broadcast) + .content(serde_json::to_vec(&msg).unwrap()) + .sign(&keys) + .build() + .unwrap(), + ) + .unwrap() + }, + |_| GuiEvent::None, + ) + } +} + +impl Stream for Degeon { + type Item = GuiEvent; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + let timestamp_0 = std::time::Instant::now(); + let msg_raw = self.ironforce.lock().unwrap().read_message(); + let msg = msg_raw.as_ref().map(|msg| self.process_message(msg.clone()).unwrap()); + if msg_raw.is_some() { + let msg_deg: ProtocolMsg = match serde_json::from_slice(msg_raw.as_ref().unwrap().get_decrypted(&self.keys).unwrap().as_slice()) { + Ok(r) => r, + Err(_) => {println!("Couldn't deserialize {:?}", msg_raw); return Poll::Ready(Some(GuiEvent::None))} + }; + println!("{:?} -> {:?}", msg_deg, msg); + } + if timestamp_0.elapsed() < std::time::Duration::from_millis(5) { + std::thread::sleep(std::time::Duration::from_millis(5)); + } + match msg { + None => Poll::Ready(Some(GuiEvent::None)), + Some(None) => Poll::Ready(Some(GuiEvent::None)), + Some(Some(msg)) => Poll::Ready(Some(msg)), + } + } +} + +impl iced_native::subscription::Recipe for Degeon +where + H: std::hash::Hasher, +{ + type Output = GuiEvent; + + fn hash(&self, state: &mut H) { + use std::hash::Hash; + + std::any::TypeId::of::().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, + _input: futures::stream::BoxStream<'static, I>, + ) -> futures::stream::BoxStream<'static, Self::Output> { + Box::pin(self) + } +} diff --git a/degeon/src/gui_events.rs b/degeon/src/gui_events.rs index d1c85ac..1642634 100644 --- a/degeon/src/gui_events.rs +++ b/degeon/src/gui_events.rs @@ -1,12 +1,23 @@ -use crate::message::DegMessage; +use crate::message::{DegMessage, Profile}; use ironforce::PublicKey; +/// An enum with all possible events for this application #[derive(Clone, Debug)] pub enum GuiEvent { + /// Selection of a chat ChatSelect(usize), + /// The user changed the value of "new message" field Typed(String), - SendClick, + /// The user clicked "Send" + SendMessage, + /// New chat shall be created because we have an incoming request NewChat(PublicKey), + /// A new messaged arrived NewMessageInChat(PublicKey, DegMessage), - SetName(PublicKey, String), + /// A profile response arrived and we should store it + SetProfile(PublicKey, Profile), + /// We should send profile (in response to profile request) + WeHaveToSendProfile(PublicKey), + /// Nothing happened + None, } diff --git a/degeon/src/main.rs b/degeon/src/main.rs index 0c1a974..869cbd9 100644 --- a/degeon/src/main.rs +++ b/degeon/src/main.rs @@ -2,15 +2,18 @@ extern crate serde; mod message; mod state; mod gui_events; +mod chat; +mod styles; +mod degeon_worker; use iced::Application; use ironforce::res::IFResult; use ironforce::{IronForce, Message, MessageType, PublicKey}; -use crate::state::State; +use crate::state::DegeonApp; fn main() -> Result<(), Box> { // let ironforce = IronForce::from_file("".to_string()).unwrap(); // let _if_keys = ironforce.keys.clone(); - Ok(State::run(iced::Settings::default())?) + Ok(DegeonApp::run(iced::Settings::default())?) } diff --git a/degeon/src/message.rs b/degeon/src/message.rs index 2df5a0f..15afad6 100644 --- a/degeon/src/message.rs +++ b/degeon/src/message.rs @@ -1,15 +1,39 @@ +use ironforce::PublicKey; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] -pub enum DegMessage { +pub struct DegMessage { + pub sender: PublicKey, + pub timestamp: i64, + pub content: DegMessageContent, +} + +impl DegMessage { + pub fn new_text(text: String, my_key: &PublicKey) -> DegMessage { + Self { + sender: my_key.clone(), + timestamp: chrono::Utc::now().timestamp(), + content: DegMessageContent::Text(text), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum DegMessageContent { Text(String), File(Vec), - Service(ServiceMsg), + Service, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Profile { + pub name: String, } #[derive(Clone, Debug, Serialize, Deserialize)] -pub enum ServiceMsg { - NameRequest, - NameStatement(String), +pub enum ProtocolMsg { + ProfileRequest, + ProfileResponse(Profile), Ping, + NewMessage(DegMessage), } diff --git a/degeon/src/state.rs b/degeon/src/state.rs index 6a01786..c86e93c 100644 --- a/degeon/src/state.rs +++ b/degeon/src/state.rs @@ -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> { - 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> { + 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, - pub my_name: String, - pub keys: Keys, - pub ironforce: Arc>, -} - -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 { - self.chats.iter().position(|chat| &chat.pkey == pkey) - } - - pub fn process_message(&self, msg: ironforce::Message) -> IFResult> { - 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> { - 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 iced_native::subscription::Recipe for Degeon -where - H: std::hash::Hasher, -{ - type Output = GuiEvent; - - fn hash(&self, state: &mut H) { - use std::hash::Hash; - - std::any::TypeId::of::().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, - _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, } -impl State { +impl DegeonApp { fn chat_list<'a>( chats: &'a Vec, preview_button_states: &'a mut Vec, @@ -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) { 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() diff --git a/degeon/src/styles.rs b/degeon/src/styles.rs new file mode 100644 index 0000000..38e3635 --- /dev/null +++ b/degeon/src/styles.rs @@ -0,0 +1,55 @@ +pub mod style { + use iced::container::Style; + use iced::{Background, button, 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 + } else { + Color::BLACK + }, + ..button::Style::default() + } + } + } + + pub enum Container { + Primary, + MyMessage, + NotMyMessage, + } + + 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::MyMessage => Color::from_rgb(0., 0.1, 0.8), + Container::NotMyMessage => Color::from_rgb(0., 0.1, 0.4), + })), + border_radius: 5.0, + border_width: 0.6, + border_color: Color::TRANSPARENT, + } + } + } +}