koibumi-sync 0.0.1

An experimental Bitmessage client (sync version)
use std::sync::{atomic::AtomicBool, Arc};

use copypasta::ClipboardProvider;
use crossbeam_channel::Sender;
use log::error;

use koibumi_box_sync::Contact;
use koibumi_common_sync::boxes::{Boxes, DEFAULT_USER_ID};
use koibumi_core::identity::Private as PrivateIdentity;
use koibumi_node_sync::Command as NodeCommand;

use crate::{config::Config as GuiConfig, ids::Ids, log::Logger};

#[derive(Clone, Debug, Default)]
pub(crate) struct Tab {
    deterministic_password_value: String,
    chan_password_value: String,
}

impl Tab {
    // Draw the Ui.
    #[allow(clippy::too_many_arguments)]
    pub(crate) fn set_widgets(
        &mut self,
        ui: &mut conrod_core::UiCell,
        ids: &mut Ids,
        config: &GuiConfig,
        identities: &[PrivateIdentity],
        selected_index: Option<usize>,
        boxes: &mut Option<Boxes>,
        command_sender: &mut Sender<NodeCommand>,
        logger: &mut Logger,
    ) {
        let text_size = config.text_size();
        let font_id = ui.fonts.ids().next().unwrap();

        if boxes.is_none() {
            return;
        }
        let boxes = boxes.as_mut().unwrap();

        use conrod_core::{
            color,
            position::{Place, Relative},
            text, widget, Colorable, Labelable, Positionable, Sizeable, Widget,
        };

        widget::Canvas::new()
            .flow_down(&[
                (
                    ids.identities_list_canvas,
                    widget::Canvas::new().pad_bottom(2.0),
                ),
                (
                    ids.identities_buttons_canvas,
                    widget::Canvas::new()
                        .length((text_size as f64 + 8.0 + 4.0) * 4.0 + 4.0)
                        .pad_bottom(2.0),
                ),
            ])
            .top_left_of(ids.tabs_body)
            .wh_of(ids.tabs_body)
            .set(ids.identities, ui);

        let (mut items, scrollbar) = widget::List::flow_down(identities.len())
            .top_left_of(ids.identities_list_canvas)
            .wh_of(ids.identities_list_canvas)
            .item_size(text_size as f64 + 8.0)
            .scrollbar_on_top()
            .set(ids.identities_list, ui);

        while let Some(item) = items.next(ui) {
            let i = item.i;
            let label = boxes
                .user()
                .rich_alias(&identities[i].address().to_string());
            let c = if let Some(index) = selected_index {
                if i == index {
                    color::DARK_YELLOW
                } else {
                    color::DARK_CHARCOAL
                }
            } else {
                color::DARK_CHARCOAL
            };
            for _v in item.set(
                widget::Button::new()
                    .label(&label)
                    .label_color(color::WHITE)
                    .label_font_size(text_size as u32)
                    .label_x(Relative::Place(Place::Start(Some(2.0))))
                    .color(c),
                ui,
            ) {
                boxes.set_selected_identity_index(Some(i));
            }
        }

        if let Some(s) = scrollbar {
            s.set(ui)
        }

        let label = "Copy to clipboard";
        let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
        for _click in widget::Button::new()
            .top_left_with_margins_on(ids.identities_buttons_canvas, 2.0, 2.0)
            .w_h(width + 8.0, text_size as f64 + 8.0)
            .label_font_size(text_size as u32)
            .label(label)
            .color(color::DARK_GRAY)
            .label_color(color::WHITE)
            .set(ids.identities_copy_button, ui)
        {
            if boxes.selected_identity_index().is_none() {
                continue;
            }
            let index = boxes.selected_identity_index().unwrap();
            let address = boxes.user().private_identities()[index].address();
            let ctx = copypasta::ClipboardContext::new();
            if let Err(err) = ctx {
                error!("{}", err);
                continue;
            }
            let mut ctx = ctx.unwrap();
            if let Err(err) = ctx.set_contents(address.to_string()) {
                error!("{}", err);
            }
        }

        let label = "Subscribe";
        let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
        for _click in widget::Button::new()
            .right_from(ids.identities_copy_button, 4.0)
            .w_h(width + 8.0, text_size as f64 + 8.0)
            .label_font_size(text_size as u32)
            .label(label)
            .color(color::DARK_GRAY)
            .label_color(color::WHITE)
            .set(ids.identities_subscribe_button, ui)
        {
            if boxes.selected_identity_index().is_none() {
                continue;
            }
            let index = boxes.selected_identity_index().unwrap();
            let address = boxes.user().private_identities()[index].address();
            if boxes.user().subscriptions().contains(&address) {
                continue;
            }
            if let Err(err) = boxes.manager().subscribe(DEFAULT_USER_ID, &address) {
                error!("{}", err);
                continue;
            }
            if let Err(err) = command_sender.send(NodeCommand::Subscribe {
                id: DEFAULT_USER_ID.to_vec(),
                address: address.clone(),
            }) {
                error!("{}", err);
                continue;
            }
            boxes.user_mut().subscriptions_mut().push(address);
        }

        let label = "Generate random";
        let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
        for _click in widget::Button::new()
            .down_from(ids.identities_copy_button, 4.0)
            .w_h(width + 8.0, text_size as f64 + 8.0)
            .label_font_size(text_size as u32)
            .label(label)
            .color(color::DARK_GRAY)
            .label_color(color::WHITE)
            .set(ids.identities_generate_random_button, ui)
        {
            let cancel = Arc::new(AtomicBool::new(false));
            let identity = PrivateIdentity::random_builder().build(cancel);
            if let Err(err) = identity {
                error!("{}", err);
                continue;
            }
            let identity = identity.unwrap();
            if let Err(err) = boxes
                .manager()
                .add_private_identity(DEFAULT_USER_ID, identity.clone())
            {
                error!("{}", err);
                continue;
            }
            if let Err(err) = command_sender.send(NodeCommand::AddIdentity {
                id: DEFAULT_USER_ID.to_vec(),
                identity: identity.clone(),
            }) {
                error!("{}", err);
                continue;
            }
            boxes
                .user_mut()
                .private_identities_mut()
                .insert(0, identity);
            boxes.set_selected_identity_index(None);
            logger.info("Random address generated");
        }

        let label = "Generate deterministic";
        let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
        widget::Canvas::new()
            .flow_right(&[
                (
                    ids.identities_deterministic_edit_canvas,
                    widget::Canvas::new()
                        .scroll_kids_vertically()
                        .color(color::LIGHT_GRAY)
                        .pad_right(2.0),
                ),
                (
                    ids.identities_deterministic_button_canvas,
                    widget::Canvas::new().length(width + 8.0),
                ),
            ])
            .down_from(ids.identities_generate_random_button, 4.0)
            .w_of(ids.identities_buttons_canvas)
            .h(text_size as f64 + 8.0)
            .set(ids.identities_deterministic_canvas, ui);

        if let Some(edit) = widget::TextEdit::new(&self.deterministic_password_value)
            .top_left_with_margins_on(ids.identities_deterministic_edit_canvas, 2.0, 2.0)
            .padded_w_of(ids.identities_deterministic_edit_canvas, 2.0)
            .parent(ids.identities_deterministic_edit_canvas)
            .restrict_to_height(false)
            .font_size(text_size as u32)
            .wrap_by_character()
            .color(color::BLACK)
            .line_spacing((text_size / 2) as f64)
            .set(ids.identities_deterministic_edit, ui)
        {
            self.deterministic_password_value = edit;
        }

        widget::Scrollbar::y_axis(ids.identities_deterministic_edit_canvas)
            .auto_hide(true)
            .set(ids.identities_deterministic_edit_scrollbar, ui);

        for _click in widget::Button::new()
            .top_left_of(ids.identities_deterministic_button_canvas)
            .w_h(width + 8.0, text_size as f64 + 8.0)
            .label_font_size(text_size as u32)
            .label(label)
            .color(color::DARK_GRAY)
            .label_color(color::WHITE)
            .set(ids.identities_deterministic_button, ui)
        {
            let cancel = Arc::new(AtomicBool::new(false));
            let identities = PrivateIdentity::deterministic_builder(
                self.deterministic_password_value.as_bytes().to_vec(),
            )
            .build(1, cancel);
            if let Err(err) = identities {
                error!("{}", err);
                continue;
            }
            let identities = identities.unwrap();
            let identity = identities[0].clone();
            if let Err(err) = boxes
                .manager()
                .add_private_identity(DEFAULT_USER_ID, identity.clone())
            {
                error!("{}", err);
                continue;
            }
            if let Err(err) = command_sender.send(NodeCommand::AddIdentity {
                id: DEFAULT_USER_ID.to_vec(),
                identity: identity.clone(),
            }) {
                error!("{}", err);
                continue;
            }
            boxes
                .user_mut()
                .private_identities_mut()
                .insert(0, identity);
            boxes.set_selected_identity_index(None);
            self.deterministic_password_value = String::new();
            logger.info("Deterministic address generated");
        }

        let label = "Generate chan";
        let width = text::line::width(label, ui.fonts.get(font_id).unwrap(), text_size as u32);
        widget::Canvas::new()
            .flow_right(&[
                (
                    ids.identities_chan_edit_canvas,
                    widget::Canvas::new()
                        .scroll_kids_vertically()
                        .color(color::LIGHT_GRAY)
                        .pad_right(2.0),
                ),
                (
                    ids.identities_chan_button_canvas,
                    widget::Canvas::new().length(width + 8.0),
                ),
            ])
            .down_from(ids.identities_deterministic_canvas, 4.0)
            .w_of(ids.identities_buttons_canvas)
            .h(text_size as f64 + 8.0)
            .set(ids.identities_chan_canvas, ui);

        if let Some(edit) = widget::TextEdit::new(&self.chan_password_value)
            .top_left_with_margins_on(ids.identities_chan_edit_canvas, 2.0, 2.0)
            .padded_w_of(ids.identities_chan_edit_canvas, 2.0)
            .parent(ids.identities_chan_edit_canvas)
            .restrict_to_height(false)
            .font_size(text_size as u32)
            .wrap_by_character()
            .color(color::BLACK)
            .line_spacing((text_size / 2) as f64)
            .set(ids.identities_chan_edit, ui)
        {
            self.chan_password_value = edit;
        }

        widget::Scrollbar::y_axis(ids.identities_chan_edit_canvas)
            .auto_hide(true)
            .set(ids.identities_chan_edit_scrollbar, ui);

        for _click in widget::Button::new()
            .top_left_of(ids.identities_chan_button_canvas)
            .w_h(width + 8.0, text_size as f64 + 8.0)
            .label_font_size(text_size as u32)
            .label(label)
            .color(color::DARK_GRAY)
            .label_color(color::WHITE)
            .set(ids.identities_chan_button, ui)
        {
            let cancel = Arc::new(AtomicBool::new(false));
            let identity =
                PrivateIdentity::chan_builder(self.chan_password_value.as_bytes().to_vec())
                    .build(cancel);
            if let Err(err) = identity {
                error!("{}", err);
                continue;
            }
            let identity = identity.unwrap();
            if let Err(err) = boxes
                .manager()
                .add_private_identity(DEFAULT_USER_ID, identity.clone())
            {
                error!("{}", err);
                continue;
            }
            if let Err(err) = command_sender.send(NodeCommand::AddIdentity {
                id: DEFAULT_USER_ID.to_vec(),
                identity: identity.clone(),
            }) {
                error!("{}", err);
                continue;
            }
            let address = identity.address();
            boxes
                .user_mut()
                .private_identities_mut()
                .insert(0, identity);
            boxes.set_selected_identity_index(None);

            let contact = Contact::new(address.clone());
            if let Err(err) = boxes.manager().add_contact(DEFAULT_USER_ID, &contact) {
                error!("{}", err);
                continue;
            }
            boxes.user_mut().contacts_mut().push(contact);

            let alias = format!("[chan] {}", self.chan_password_value);
            if let Err(err) = boxes.manager().add_alias(DEFAULT_USER_ID, &address, &alias) {
                error!("{}", err);
                continue;
            }
            boxes
                .user_mut()
                .aliases_mut()
                .insert(address.to_string(), alias);

            self.chan_password_value = String::new();
            logger.info("chan address generated");
        }
    }
}