~nicohman/signal-rs

bc8848a1ac1ea6d39b949728fda6de5763f3424b — nicohman 11 months ago 7962d29
Move headerbar into its own widget
M src/axolotl.rs => src/axolotl.rs +23 -18
@@ 7,13 7,15 @@ pub struct AxolotlSession {
    pub messages: HashMap<String, BTreeMap<i64, Message>>,
}
impl AxolotlSession {
    pub async fn run(res_sender: glib::Sender<SignalResponse>,
        mut req_receiver: UnboundedReceiver<SignalRequest>) {
    pub async fn run(
        res_sender: glib::Sender<SignalResponse>,
        mut req_receiver: UnboundedReceiver<SignalRequest>,
    ) {
        let (socket, _response) = connect_async(Url::parse(CONNECTION).unwrap())
            .await
            .unwrap();
        let mut session = SignalSession::from_socket(socket);
        loop {
         loop {
            tokio::select! {
                next = session.stream.next() => {
                    res_sender.send(next.unwrap()).expect("Couldn't pass on websocket response");


@@ 34,8 36,14 @@ impl AxolotlSession {
        let msg_p = self.messages.get_mut(&message.source).unwrap();
        if !msg_p.contains_key(&message.sent_at) {
            let msg = Message::new(message.clone());
                        msg_p.insert(message.sent_at, msg);
            self.res_sender.send(SignalResponse::AddHist(message.source.clone(), message.sent_at, new)).expect("Couldn't send AddHist");
            msg_p.insert(message.sent_at, msg);
            self.res_sender
                .send(SignalResponse::AddHist(
                    message.source.clone(),
                    message.sent_at,
                    new,
                ))
                .expect("Couldn't send AddHist");
        }
    }
    pub fn add_messages_old(&mut self, messages: Vec<SignalMessage>) {


@@ 50,9 58,7 @@ impl AxolotlSession {
    }
}
impl SignalBackend for AxolotlSession {
	    fn new(
        res_sender: glib::Sender<SignalResponse>,
    ) -> AxolotlSession {
    fn new(res_sender: glib::Sender<SignalResponse>) -> AxolotlSession {
        AxolotlSession {
            res_sender,
            contacts: HashMap::new(),


@@ 66,12 72,13 @@ impl SignalBackend for AxolotlSession {
        &self.contacts
    }
    fn process_response(&mut self, response: SignalResponse) {
    	 match response {
    	 	SignalResponse::ChatList(chats) => {
    	 		for chat in chats {
    	 			self.add_messages_old(chat.messages);
    	 		}
    	 	},
        match response {
            SignalResponse::ChatList(chats) => {
                for chat in chats {
                    println!("{:?}", chat);
                    self.add_messages_old(chat.messages);
                }
            }
            SignalResponse::MoreMessageList(mut message_list) => {
                message_list
                    .messages


@@ 82,7 89,6 @@ impl SignalBackend for AxolotlSession {
                self.add_message(msg, true);
            }
            SignalResponse::ContactList(contacts) => {
                println!("{:?}", contacts);
                for contact in contacts.into_iter() {
                    if let Some(contact_p) = self.contacts.get_mut(&contact.tel) {
                        *contact_p = contact;


@@ 92,11 98,10 @@ impl SignalBackend for AxolotlSession {
                }
            }
            SignalResponse::MessageList(message_list) => {
                println!("{:?}", message_list);
                self.add_messages(message_list.messages.into_iter().rev().collect());
            }
            _ => {

            }
            _ => {}
        };
    }
}

A src/base.css => src/base.css +27 -0
@@ 0,0 1,27 @@
.msg {
    border: 1px solid #2e2e2e;
    border-radius: 5px;
    padding: 0.1em;
    padding-left: 0.4em;
    padding-right: 0.4em;
}
a {
    color: inherit;
    text-decoration: inherit;
}

.chat-name {
    font-weight: bold;
}

.when-label {
    font-size: 75%;
    padding-left: 2em;
}
.small-text {
    font-size: 75%;
}

.chat-last {
    font-size: 75%;
}
\ No newline at end of file

M src/main.rs => src/main.rs +16 -91
@@ 38,6 38,7 @@ use url::Url;
mod config;
/// Contains the custom widgets
mod widgets;
mod util;
use config::*;
use std::cell::*;
use std::process::Command;


@@ 56,8 57,6 @@ pub trait SignalBackend {
pub struct SignalState<T: SignalBackend> {
    pub message_box: Box,
    pub app_box: Box,
    // pub contacts: HashMap<String, Contact>,
    // pub messages: HashMap<String, BTreeMap<i64, Message>>,
    pub current: Option<String>,
    pub history: Option<ChatHistory>,
    pub scroll: ScrolledWindow,


@@ 69,7 68,6 @@ pub struct SignalState<T: SignalBackend> {
    pub current_chat: Rc<RefCell<Option<String>>>,
    pub button: Button,
    pub input: Entry,
    pub header: HeaderBar,
    pub pw: PasswordDialog,
    pub cc: CreateChat,
    pub ccontact: CreateContact,


@@ 77,6 75,7 @@ pub struct SignalState<T: SignalBackend> {
    pub user_tel: Option<String>,
    pub backend: Backend,
    pub sbackend: T,
    pub headerbar: Header,
}
/// An enum that represents a view that can be displayed.
#[derive(Clone, Debug)]


@@ 110,11 109,10 @@ where
        let chat_list = ChatList::new();
        let input = EntryBuilder::new().build();
        let button = Button::with_label("Send message");
        let header_bar = gtk::HeaderBarBuilder::new()
            .show_close_button(false)
            .build();
        add_class(&header_bar, "header");
        app_box.add(&header_bar);
                let headerbar = Header::new();


        app_box.add(&headerbar.headerbar);
        let msg_box = Box::new(Orientation::Vertical, 8);
        msg_box.pack_start(&scroll, true, true, 0);
        scroll.set_vexpand(true);


@@ 134,20 132,10 @@ where
        add_class(&app_box, "app-box");
        scroll.add(&message_box);
        app_box.add(&stack);
        let mut contacts = HashMap::new();
        contacts.insert(
            "+19074446266".to_string(),
            Contact {
                name: "Cassidy".to_string(),
                tel: "+19074446266".to_string(),
            },
        );
        SignalState {
            // contacts,
            message_box,
            app_box,
            stack,
            // messages: HashMap::new(),
            current: None,
            history: None,
            scroll,


@@ 158,7 146,7 @@ where
            current_chat: Rc::new(RefCell::new(None)),
            button,
            input,
            header: header_bar,
            headerbar,
            pw,
            cc,
            ccontact: create_contact,


@@ 227,7 215,7 @@ where
                self.stack.set_visible_child_name("create-chat");
            }
        };
        self.headerbar(view);
        self.headerbar.set_view(view, &self.req_sender, &self.res_sender);
    }
    pub fn update_chats(&mut self) {
        self.chat_list.chats = HashMap::new();


@@ 246,72 234,6 @@ where
            }
        }
    }
    /// Displays the headerbar for the given view
    fn headerbar(&mut self, view: View) {
        for child in self.header.get_children() {
            self.header.remove(&child);
        }
        if let View::Chats = view {
        } else {
            let back_button = ButtonBuilder::new()
                .label("gtk-go-back")
                .receives_default(true)
                .build();
            back_button
                .set_property("use_stock", &true)
                .expect("Couldn't set stock property");
            back_button.connect_clicked(clone!(@strong self.res_sender as res_sender => move |_| {
                res_sender.send(SignalResponse::ShowView(View::Chats)).expect("Couldn't send ShowView");
            }));
            self.header.add(&back_button);
        }
        match view {
            View::Chats => {
                self.req_sender
                    .send(SignalRequest::GetContacts)
                    .expect("Couldn't send GetContacts");
                self.header.add(&Label::new(Some("Signal")));
                let create_button = ButtonBuilder::new()
                    .label("gtk-add")
                    .receives_default(true)
                    .build();
                create_button
                    .set_property("use_stock", &true)
                    .expect("Couldn't set create button's stock property");
                create_button.set_halign(Align::End);
                create_button.connect_clicked(
                    clone!(@strong self.res_sender as res_sender => move |_| {
                        res_sender.send(SignalResponse::ShowView(View::CreateChat)).expect("Couldn't send ShowView");
                    }),
                );
                self.header.add(&create_button);
            }
            View::Messages(tel) => {
                let edit_button = Button::with_label("Edit");
                edit_button.connect_clicked(clone!(@strong self.res_sender as res_sender => move |_| {
                    res_sender.send(SignalResponse::ShowView(View::EditContact(tel.clone()))).expect("Couldn't send ShowView");
                }));
                self.header.add(&edit_button);
            }
            View::CreateChat => {
                let create_contact_button = ButtonBuilder::new()
                    .label("gtk-add")
                    .receives_default(true)
                    .build();
                create_contact_button
                    .set_property("use_stock", &true)
                    .expect("Couldn't set create button's stock property");
                create_contact_button.connect_clicked(
                    clone!(@strong self.res_sender as res_sender => move |_| {
                        res_sender.send(SignalResponse::ShowView(View::CreateContact)).expect("Couldn't send ShowView");
                    }),
                );
                self.header.add(&create_contact_button);
            }
            _ => {}
        }
        self.header.show_all();
    }
    /// Hides message history
    pub fn hide_history(&mut self) {
        if self.history.is_some() {


@@ 515,13 437,16 @@ impl Message {
#[tokio::main]
async fn main() -> Result<()> {
    let config: Config = confy::load("signalrs").expect("Couldn't handle config file");
    let css = include_str!("style.css");
    let base_css = include_str!("base.css");
    let style = include_str!("style.css");
    let signal_css = include_str!("signal.css");
    let _total: Vec<SignalResponse> = vec![];
    let application = Application::new(Some("com.nicohman"), Default::default()).unwrap();
    application.connect_activate(move |app| {
        let icon = gdk_pixbuf::Pixbuf::from_file("src/icon.png").unwrap();
        let provider = CssProvider::new();
        provider.load_from_data(css.as_bytes()).unwrap();
        provider.load_from_data(base_css.as_bytes()).unwrap();
        provider.load_from_data(style.as_bytes()).unwrap();
        let (res_sender, res_receiver) =
            glib::MainContext::channel::<SignalResponse>(glib::PRIORITY_DEFAULT);
        let l_res_sender = res_sender.clone();


@@ 542,7 467,7 @@ async fn main() -> Result<()> {
        window.set_icon(Some(&icon));
        window.set_border_width(10);
        window.set_position(WindowPosition::Center);
        window.set_default_size(400, 300);
        window.set_default_size(400, 800);
        state.connect_signals();
        window.add(&state.app_box);
        state.show_view(View::Chats);


@@ 599,11 524,11 @@ async fn main() -> Result<()> {
            }
        });
        std::thread::sleep_ms(1000);

        window.show_all();
        l_res_sender
            .send(SignalResponse::ShowView(View::Chats))
            .expect("Couldn't send ShowChats");

        window.show_all();
    });
    application.run(&args().collect::<Vec<_>>());
    Ok(())

A src/signal.css => src/signal.css +33 -0
@@ 0,0 1,33 @@
.msg-scroll listbox {
    background-color: black;
}
.msg-box {
    background-color: black;
}
window {
    background-color: black;
}
.header {
    background-color: black;
    color: white;
}
.chat-list {
    background-color: black;
    color: white;
}
.history-listbox {
    background-color: black;
}
.msg-box entry {
    background-color: #141414;
}
.msg-box button {
    background-color: #141414;
}

.outgoing {
    background-color: #2c6bed;
}
.msg {
    background-color: #2e2e2e;
}
\ No newline at end of file

M src/signal.rs => src/signal.rs +9 -11
@@ 1,18 1,16 @@
use crate::Message;
use enum_variant_type::EnumVariantType;
use futures_util::sink::SinkExt;
use futures_util::stream::*;
use futures_util::{future, StreamExt};
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize};
use std::convert::TryFrom;
use tokio_tungstenite::*;
use regex::Regex;
use crate::Message;
use tokio_tungstenite::tungstenite::protocol::Message as TMessage;
use tokio_tungstenite::*;
lazy_static! {
    static ref CONTACT_REG: Regex = Regex::new(
        r"Number: (\+[0-9]+) Name: (\S+)  Blocked:"
    )
    .unwrap();
    static ref CONTACT_REG: Regex =
        Regex::new(r"Number: (\+[0-9]+) Name: (\S+)  Blocked:").unwrap();
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]


@@ 107,10 105,10 @@ pub struct Chat {
    pub is_group: bool,
    pub last: String,
    pub timestamp: i64,
   // pub when: String,
    // pub when: String,
    //pub c_type: i32,
    #[serde(deserialize_with = "parse_messages")]
    pub messages:Vec<SignalMessage>,
    pub messages: Vec<SignalMessage>,
    /*#[serde(deserialize_with = "parse_messages")]
    pub messages: usize,*/
    /*#[serde(default)]


@@ 142,7 140,7 @@ pub struct SignalCLIMessage {
    pub message: Option<String>,
    pub is_read: Option<bool>,
    pub is_delivery: Option<bool>,
    pub data_message: Option<DataMessage>
    pub data_message: Option<DataMessage>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct SignalCLIContactInfo {


@@ 166,7 164,7 @@ pub struct DataMessage {
    pub timestamp: u64,
    pub message: Option<String>,
    pub expires_in_seconds: u64,
   // pub attachments: Option<Vec<Attachment>>,
    // pub attachments: Option<Vec<Attachment>>,
    //pub group_info: Option<GroupInfo>,
    pub destination: Option<String>,
}

M src/style.css => src/style.css +6 -26
@@ 1,42 1,22 @@
.msg {
    border: 1px solid #2e2e2e;
    border-radius: 5px;
    padding: 0.1em;
    padding-left: 0.4em;
    padding-right: 0.4em;
}
a {
    color: inherit;
    text-decoration: inherit;
}

.chat-name {
    font-weight: bold;
}

.when-label {
    font-size: 75%;
    padding-left: 2em;
}
.msg-scroll listbox {
    background-color: black;
    background-color: @theme_base_color;
}
.msg-box {
    background-color: black;
    background-color: @theme_base_color;
}
window {
    background-color: black;
    background-color: @theme_base_color;
}
.header {
    background-color: black;
    background-color: @titlebar_color;
    color: white;
}
.chat-list {
    background-color: black;
    background-color: @theme_base_color;
    color: white;
}
.history-listbox {
    background-color: black;
    background-color: @metacity_color;
}
.msg-box entry {
    background-color: #141414;

A src/util.rs => src/util.rs +13 -0
@@ 0,0 1,13 @@
/// Takes in an elapsed amount of milliseconds and represents it as a string, i.e. 45000 -> 45 sec
pub fn parse_time(elapsed: u128) -> String {
    let elapsed = elapsed / 1000;
    if elapsed < 60 {
        format!("{} sec", elapsed)
    } else if elapsed < 3600 {
        format!("{} min", elapsed / 60)
    } else if elapsed < (3600 * 24) {
        format!("{} hours", elapsed / 3600)
    } else {
        format!("{} days", elapsed / (3600 * 24))
    }
}

M src/widgets/chat_list.rs => src/widgets/chat_list.rs +6 -17
@@ 1,3 1,4 @@
use crate::util::parse_time;
use crate::*;
use gdk_pixbuf::*;
/// The list of chats


@@ 74,13 75,14 @@ impl ChatList {
fn build_chat_ui(chat: &Chat) -> ListBoxRow {
    let row = ListBoxRow::new();
    let b = Box::new(Orientation::Horizontal, 6);
    row.get_style_context().add_class("chat-entry");
    add_class(&row, "chat-entry");
    row.set_property("name", &chat.tel.clone())
        .expect("Couldn't set chat list name");
    let name_label = Label::new(Some(&chat.name));
    name_label.get_style_context().add_class("chat-name");
    let last_label = Label::new(Some(&chat.last));
    last_label.get_style_context().add_class("chat-last");
    add_class(&name_label, "chat-name");
    let last_label = Label::new(Some(&chat.last.trim()));
    add_class(&last_label, "chat-last");
    add_class(&last_label, "small-text");
    let when_label = Label::new(Some(&parse_time(chat.timestamp as u128)));
    /*let avatar = Image::new();
        if let Some(buf) = self.avatars.get(&chat.tel) {


@@ 118,16 120,3 @@ fn build_chat_ui(chat: &Chat) -> ListBoxRow {
    row.hide();
    row
}
fn parse_time(elapsed: u128) -> String {
    let elapsed = elapsed / 1000;
    println!("{}", elapsed);
    if elapsed < 60 {
        format!("{} sec", elapsed)
    } else if elapsed < 3600 {
        format!("{} min", elapsed / 60)
    } else if elapsed < (3600 * 24) {
        format!("{} hours", elapsed / 3600)
    } else {
        format!("{} days", elapsed / (3600 * 24))
    }
}

M src/widgets/edit_contact.rs => src/widgets/edit_contact.rs +0 -1
@@ 31,7 31,6 @@ impl EditContact {
        }
    }
    pub fn edit(&self, contact: Contact) {
        println!("{:?}", contact);
        self.current_tel.replace(contact.tel.clone());
        self.name_entry.get_buffer().set_text(&contact.name);
        self.phone_entry.get_buffer().set_text(&contact.tel);

A src/widgets/header.rs => src/widgets/header.rs +87 -0
@@ 0,0 1,87 @@
use crate::*;
pub struct Header {
	pub headerbar: HeaderBar,
	pub view_history: Vec<View>,
	pub label: Label,
}
impl Header {
	pub fn new() -> Header {
		        let headerbar = gtk::HeaderBarBuilder::new()
            .show_close_button(false)
            .build();
        add_class(&headerbar, "header");
		let label = Label::new(Some("Signal"));
		let view_history = vec![];
		Header {
			headerbar,
			view_history,
			label
		}
	} 
	pub fn set_view(&mut self, view: View, req_sender: &UnboundedSender<SignalRequest>, res_sender: &glib::Sender<SignalResponse>) {
		for child in self.headerbar.get_children() {
            self.headerbar.remove(&child);
        }
        self.headerbar.set_title(None);
        if let View::Chats = view {
        } else {
            let back_button = ButtonBuilder::new()
                .label("gtk-go-back")
                .receives_default(true)
                .build();
            back_button
                .set_property("use_stock", &true)
                .expect("Couldn't set stock property");
            back_button.connect_clicked(clone!(@strong res_sender => move |_| {
                res_sender.send(SignalResponse::ShowView(View::Chats)).expect("Couldn't send ShowView");
            }));
            self.headerbar.add(&back_button);
        }
        match view {
            View::Chats => {
                self.headerbar.set_title(Some("Signal"));
                req_sender
                    .send(SignalRequest::GetContacts)
                    .expect("Couldn't send GetContacts");
                let create_button = ButtonBuilder::new()
                    .label("gtk-add")
                    .receives_default(true)
                    .build();
                create_button
                    .set_property("use_stock", &true)
                    .expect("Couldn't set create button's stock property");
                create_button.set_halign(Align::End);
                create_button.connect_clicked(
                    clone!(@strong res_sender => move |_| {
                        res_sender.send(SignalResponse::ShowView(View::CreateChat)).expect("Couldn't send ShowView");
                    }),
                );
                self.headerbar.pack_end(&create_button);
            }
            View::Messages(tel) => {
                let edit_button = Button::with_label("Edit");
                edit_button.connect_clicked(clone!(@strong res_sender => move |_| {
                    res_sender.send(SignalResponse::ShowView(View::EditContact(tel.clone()))).expect("Couldn't send ShowView");
                }));
                self.headerbar.pack_end(&edit_button);
            }
            View::CreateChat => {
                let create_contact_button = ButtonBuilder::new()
                    .label("gtk-add")
                    .receives_default(true)
                    .build();
                create_contact_button
                    .set_property("use_stock", &true)
                    .expect("Couldn't set create button's stock property");
                create_contact_button.connect_clicked(
                    clone!(@strong res_sender => move |_| {
                        res_sender.send(SignalResponse::ShowView(View::CreateContact)).expect("Couldn't send ShowView");
                    }),
                );
                self.headerbar.add(&create_contact_button);
            }
            _ => {}
        }
        self.headerbar.show_all();
	}
}
\ No newline at end of file

M src/widgets/message.rs => src/widgets/message.rs +1 -13
@@ 1,3 1,4 @@
use crate::util::parse_time;
use core::fmt::Debug;
use std::fmt;
use crate::*;


@@ 64,16 65,3 @@ impl MessageUi {
        msg_box
    }
}
fn parse_time(elapsed: u128) -> String {
    let elapsed = elapsed / 1000;
    println!("{}", elapsed);
    if elapsed < 60 {
        format!("{} sec", elapsed)
    } else if elapsed < 3600 {
        format!("{} min", elapsed / 60)
    } else if elapsed < (3600 * 24) {
        format!("{} hours", elapsed / 3600)
    } else {
        format!("{} days", elapsed / (3600 * 24))
    }
}

M src/widgets/mod.rs => src/widgets/mod.rs +2 -0
@@ 5,6 5,7 @@ mod create_contact;
mod edit_contact;
mod message;
mod password;
mod header;
pub use chat_history::{ChatHistory, Element};
pub use chat_list::ChatList;
pub use create_chat::CreateChat;


@@ 12,3 13,4 @@ pub use create_contact::CreateContact;
pub use edit_contact::EditContact;
pub use message::MessageUi;
pub use password::PasswordDialog;
pub use header::Header;
\ No newline at end of file