koibumi 0.0.10

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

use async_std::task;
use futures::{channel::mpsc::Sender, sink::SinkExt};
use iced::{
    widget::{button, scrollable, Column, Row, Text, TextInput},
    Command, Element, Length,
};
use log::error;

use koibumi_box::Contact;
use koibumi_common::boxes::{Boxes, DEFAULT_USER_ID};
use koibumi_core::identity::Private as PrivateIdentity;
use koibumi_node::Command as NodeCommand;

use crate::{config::Config as GuiConfig, gui, log::Logger, style};

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

impl Tab {
    pub(crate) fn update(
        &mut self,
        message: gui::Message,
        boxes: &mut Option<Boxes>,
        command_sender: &mut Sender<NodeCommand>,
        logger: &mut Logger,
    ) -> Command<gui::Message> {
        if boxes.is_none() {
            return Command::none();
        }
        let boxes = boxes.as_mut().unwrap();
        match message {
            gui::Message::IdentitiesIdentitySelected(index) => {
                boxes.set_selected_identity_index(Some(index));
                Command::none()
            }

            gui::Message::IdentitiesCopyPressed => {
                if boxes.selected_identity_index().is_none() {
                    return Command::none();
                }
                let index = boxes.selected_identity_index().unwrap();
                let address = boxes.user().private_identities()[index].address();
                iced::clipboard::write(address.to_string())
            }
            gui::Message::IdentitiesSubscribePressed => {
                if boxes.selected_identity_index().is_none() {
                    return Command::none();
                }
                let index = boxes.selected_identity_index().unwrap();
                let address = boxes.user().private_identities()[index].address();
                if boxes.user().subscriptions().contains(&address) {
                    return Command::none();
                }
                if let Err(err) =
                    task::block_on(boxes.manager().subscribe(DEFAULT_USER_ID, &address))
                {
                    error!("{}", err);
                    return Command::none();
                }
                if let Err(err) = task::block_on(command_sender.send(NodeCommand::Subscribe {
                    id: DEFAULT_USER_ID.to_vec(),
                    address: address.clone(),
                })) {
                    error!("{}", err);
                    return Command::none();
                }
                boxes.user_mut().subscriptions_mut().push(address);
                Command::none()
            }

            gui::Message::IdentitiesGenerateRandomPressed => {
                let cancel = Arc::new(AtomicBool::new(false));
                let identity = PrivateIdentity::random_builder().build(cancel);
                if let Err(err) = identity {
                    error!("{}", err);
                    return Command::none();
                }
                let identity = identity.unwrap();
                if let Err(err) = task::block_on(
                    boxes
                        .manager()
                        .add_private_identity(DEFAULT_USER_ID, identity.clone()),
                ) {
                    error!("{}", err);
                    return Command::none();
                }
                if let Err(err) = task::block_on(command_sender.send(NodeCommand::AddIdentity {
                    id: DEFAULT_USER_ID.to_vec(),
                    identity: identity.clone(),
                })) {
                    error!("{}", err);
                    return Command::none();
                }
                boxes
                    .user_mut()
                    .private_identities_mut()
                    .insert(0, identity);
                boxes.set_selected_identity_index(None);
                logger.info("Random address generated");
                Command::none()
            }

            gui::Message::IdentitiesDeterministicPasswordChanged(password) => {
                self.deterministic_password_value = password;
                Command::none()
            }
            gui::Message::IdentitiesGenerateDeterministicPressed => {
                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);
                    return Command::none();
                }
                let identities = identities.unwrap();
                let identity = identities[0].clone();
                if let Err(err) = task::block_on(
                    boxes
                        .manager()
                        .add_private_identity(DEFAULT_USER_ID, identity.clone()),
                ) {
                    error!("{}", err);
                    return Command::none();
                }
                if let Err(err) = task::block_on(command_sender.send(NodeCommand::AddIdentity {
                    id: DEFAULT_USER_ID.to_vec(),
                    identity: identity.clone(),
                })) {
                    error!("{}", err);
                    return Command::none();
                }
                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");
                Command::none()
            }

            gui::Message::IdentitiesChanPasswordChanged(password) => {
                self.chan_password_value = password;
                Command::none()
            }
            gui::Message::IdentitiesGenerateChanPressed => {
                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);
                    return Command::none();
                }
                let identity = identity.unwrap();
                if let Err(err) = task::block_on(
                    boxes
                        .manager()
                        .add_private_identity(DEFAULT_USER_ID, identity.clone()),
                ) {
                    error!("{}", err);
                    return Command::none();
                }
                if let Err(err) = task::block_on(command_sender.send(NodeCommand::AddIdentity {
                    id: DEFAULT_USER_ID.to_vec(),
                    identity: identity.clone(),
                })) {
                    error!("{}", err);
                    return Command::none();
                }
                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) =
                    task::block_on(boxes.manager().add_contact(DEFAULT_USER_ID, &contact))
                {
                    error!("{}", err);
                    return Command::none();
                }
                boxes.user_mut().contacts_mut().push(contact);

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

                self.chan_password_value = String::new();
                logger.info("chan address generated");
                Command::none()
            }
            _ => panic!("Program error"),
        }
    }

    pub(crate) fn view(
        &self,
        config: &GuiConfig,
        identities: &[PrivateIdentity],
        selected_index: Option<usize>,
        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 address_button = |label, i, selected| {
            let label = Text::new(label).size(text_size);
            let style = if selected {
                iced::theme::Button::Custom(Box::new(style::AddressButton::Selected))
            } else {
                iced::theme::Button::Custom(Box::new(style::AddressButton::Normal))
            };
            button(label)
                .style(style)
                .on_press(gui::Message::IdentitiesIdentitySelected(i))
                .padding(2)
        };

        let mut list = Column::new();
        let mut i = 0;
        for identity in identities {
            let alias = boxes.user().rich_alias(&identity.to_string());
            let mut selected = false;
            if let Some(index) = selected_index {
                selected = index == i;
            }
            let row = Row::new().push(address_button(alias, i, selected));
            list = list.push(row);
            i += 1;
        }
        let list = scrollable(list).height(Length::Fill);
        // XXX .spacing(text_size / 4);

        let buttons = Row::new()
            .spacing(text_size / 4)
            .push(
                button(Text::new("Copy to clipboard").size(text_size))
                    .on_press(gui::Message::IdentitiesCopyPressed),
            )
            .push(
                button(Text::new("Subscribe").size(text_size))
                    .on_press(gui::Message::IdentitiesSubscribePressed),
            );

        let generate_random = Row::new().spacing(text_size / 4).push(
            button(Text::new("Generate random").size(text_size))
                .on_press(gui::Message::IdentitiesGenerateRandomPressed),
        );

        let generate_deterministic = Row::new()
            .spacing(text_size / 4)
            .push(
                TextInput::new("Password", &self.deterministic_password_value)
                    .on_input(gui::Message::IdentitiesDeterministicPasswordChanged)
                    .size(text_size)
                    .padding(text_size / 4),
            )
            .push(
                button(Text::new("Generate deterministic").size(text_size))
                    .on_press(gui::Message::IdentitiesGenerateDeterministicPressed),
            );

        let generate_chan = Row::new()
            .spacing(text_size / 4)
            .push(
                TextInput::new("Password", &self.chan_password_value)
                    .on_input(gui::Message::IdentitiesChanPasswordChanged)
                    .size(text_size)
                    .padding(text_size / 4),
            )
            .push(
                button(Text::new("Generate chan").size(text_size))
                    .on_press(gui::Message::IdentitiesGenerateChanPressed),
            );

        Column::new()
            .spacing(text_size / 4)
            .push(list)
            .push(buttons)
            .push(generate_random)
            .push(generate_deterministic)
            .push(generate_chan)
            .into()
    }
}