gm-tui 0.0.1

TUI-based Ethereum wallet
Documentation
use std::sync::{atomic::AtomicBool, mpsc, Arc};

use ratatui::{buffer::Buffer, layout::Rect, style::Stylize, text::Line, widgets::Widget};
use strum::EnumIter;

use super::{account::AccountPage, Page};
use crate::{
    disk::{Config, DiskInterface},
    tui::{
        app::{
            widgets::form::{Form, FormItemIndex, FormWidget},
            SharedState,
        },
        events::Event,
        traits::{Component, HandleResult, RectUtil},
    },
};

#[derive(PartialEq, EnumIter)]
pub enum FormItem {
    CreateOrImportWallet,
    AlchemyApiKey,
    Save,
    Display,
}

impl FormItemIndex for FormItem {
    fn index(self) -> usize {
        self as usize
    }
}

impl TryFrom<FormItem> for FormWidget {
    type Error = crate::Error;
    fn try_from(value: FormItem) -> crate::Result<Self> {
        let widget = match value {
            FormItem::CreateOrImportWallet => FormWidget::Button {
                label: "Create or Import Wallet",
            },
            FormItem::AlchemyApiKey => {
                let mut config = Config::load()?;
                if config.alchemy_api_key.is_none() {
                    config.alchemy_api_key = Some("".to_string());
                };

                FormWidget::InputBox {
                    label: "Alchemy API Key",
                    text: config.alchemy_api_key.unwrap_or_default(),
                    empty_text: Some("Please get an Alchemy API key from https://www.alchemy.com/"),
                    currency: None,
                }
            }
            FormItem::Save => FormWidget::Button { label: "Save" },
            FormItem::Display => FormWidget::DisplayText(String::new()),
        };
        Ok(widget)
    }
}

pub struct CompleteSetupPage {
    pub form: Form<FormItem>,
}

impl CompleteSetupPage {
    pub fn new() -> crate::Result<Self> {
        Ok(Self {
            form: Form::init(|form| {
                let config = Config::load()?;
                if config.current_account.is_some() {
                    form.hide_item(FormItem::CreateOrImportWallet);
                }
                if config
                    .alchemy_api_key
                    .map(|s| !s.is_empty())
                    .unwrap_or(false)
                {
                    form.hide_item(FormItem::AlchemyApiKey);
                    form.hide_item(FormItem::Save);
                    form.hide_item(FormItem::Display);
                }
                Ok(())
            })?,
        })
    }
}
impl Component for CompleteSetupPage {
    fn set_focus(&mut self, focus: bool) {
        self.form.set_form_focus(focus);
    }

    fn reload(&mut self, _ss: &SharedState) -> crate::Result<()> {
        let fresh = Self::new()?;
        self.form = fresh.form;
        Ok(())
    }

    fn handle_event(
        &mut self,
        event: &Event,
        _area: Rect,
        transmitter: &mpsc::Sender<Event>,
        _shutdown_signal: &Arc<AtomicBool>,
        _shared_state: &SharedState,
    ) -> crate::Result<HandleResult> {
        let display_text = self.form.get_text_mut(FormItem::Display);
        *display_text = "".to_string();

        let mut handle_result = HandleResult::default();

        self.form.handle_event(
            event,
            |_, _| Ok(()),
            |label, form| {
                if label == FormItem::CreateOrImportWallet {
                    handle_result
                        .page_inserts
                        .push(Page::Account(AccountPage::new()?));
                } else {
                    handle_result.reload = true;

                    let mut config = Config::load()?;
                    config.alchemy_api_key = Some(form.get_text(FormItem::AlchemyApiKey).clone());
                    config.save()?;
                    transmitter.send(Event::ConfigUpdate)?;

                    let display_text = form.get_text_mut(FormItem::Display);
                    *display_text = "Configuration saved".to_string();
                }
                Ok(())
            },
        )?;

        Ok(handle_result)
    }

    fn render_component(&self, area: Rect, buf: &mut Buffer, ss: &SharedState) -> Rect
    where
        Self: Sized,
    {
        Line::from("Complete Setup").bold().render(area, buf);
        let Ok(area) = area.consume_height(2) else {
            return area;
        };

        if self.form.visible_count() == 0 {
            Line::from("You have completed the setup please press ESC to return back.")
                .render(area, buf);
        } else {
            "Complete the following steps to get started:".render(area, buf);
            let Ok(area) = area.consume_height(2) else {
                return area;
            };

            self.form.render(area, buf, &ss.theme);
        }
        area
    }
}