droidkraft-tui 1.2.0

A beautiful Terminal User Interface (TUI) for Android development and ADB commands
Documentation
use crate::adb::{AdbManager, DeviceStatus};
use crate::devtools::DevToolsState;
use crate::effects::EffectsManager;
use crate::fastboot::FastbootManager;
use crate::logcat::LogcatState;
use crate::menu::{Menu, MenuCommand};
use crate::theme::{Theme, ThemeSelector};

use std::time::Instant;

/// Mode of the logcat save dialog.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogcatSaveMode {
    /// Text input for the save path.
    PathInput,
    /// File explorer overlay for "Save As…" browsing.
    FileBrowser,
}

/// Application state following Elm architecture
/// All mutable state is contained within this model
#[derive(Debug)]
pub struct Model {
    /// Current application state
    pub state: AppState,

    /// Menu state and navigation
    pub menu: Menu,

    /// Visual effects manager
    pub effects: EffectsManager,

    /// Last tick time for animations
    pub last_tick: Instant,

    /// Command execution result (success)
    pub command_result: Option<String>,

    /// Command execution error
    pub command_error: Option<String>,

    /// Loading animation counter
    pub loading_counter: u64,

    /// Scroll position for result view
    pub scroll_position: usize,

    /// Original result lines (before wrapping)
    pub result_lines: Vec<String>,

    /// Wrapped lines for display (handles wide content)
    pub wrapped_lines: Vec<String>,

    /// Reveal animation counter for result display
    pub reveal_counter: u64,

    /// Whether the application should continue running
    pub running: bool,

    /// ADB client manager
    pub adb_manager: AdbManager,

    /// Fastboot manager (shells out to the `fastboot` binary)
    pub fastboot_manager: FastbootManager,

    /// Label of the last executed command (shown in the result title)
    pub last_command_label: Option<String>,

    /// Live device status shown in the header status bar.
    pub device_status: DeviceStatus,

    /// When true the next tick will fetch fresh device info.
    pub needs_device_refresh: bool,

    /// Logcat viewer state
    pub logcat: LogcatState,

    /// Whether the save dialog is active in logcat view.
    pub logcat_save_active: bool,

    /// The path input for the save dialog.
    pub logcat_save_path: String,

    /// Cursor position in the save path input.
    pub logcat_save_cursor: usize,

    /// Whether to save only filtered entries (true) or all entries (false).
    pub logcat_save_filtered_only: bool,

    /// Save format (TXT or JSON).
    pub logcat_save_format: crate::logcat::SaveFormat,

    /// Current mode of the save dialog.
    pub logcat_save_mode: LogcatSaveMode,

    /// File explorer for "Save As…" browsing.
    pub logcat_file_explorer: Option<tui_file_explorer::FileExplorer>,

    /// Dev Tools state.
    pub devtools: DevToolsState,

    /// Custom-ROM flasher state.
    pub rom_flash: crate::rom_flash::RomFlashState,

    /// Current active colour theme.
    pub theme: Theme,
    /// Theme selector overlay state.
    pub theme_selector: ThemeSelector,
}

/// Application states
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AppState {
    /// Initial startup animation
    Startup,

    /// Main menu navigation
    Menu,

    /// Loading animation during command execution
    Loading,

    /// Showing command results
    ShowResult,

    /// Logcat viewer
    Logcat,

    /// Dev Tools workstation
    DevMode,

    /// Custom-ROM flasher
    RomFlash,
}

impl Default for Model {
    fn default() -> Self {
        Self::new()
    }
}

impl Model {
    /// Create a new model with initial state
    pub fn new() -> Self {
        Self {
            state: AppState::Menu,
            menu: Menu::new(),
            effects: EffectsManager::new(),
            last_tick: Instant::now(),
            command_result: None,
            command_error: None,
            loading_counter: 0,
            scroll_position: 0,
            result_lines: Vec::new(),
            wrapped_lines: Vec::new(),
            reveal_counter: 0,
            running: true,
            adb_manager: AdbManager::new(),
            fastboot_manager: FastbootManager::new(),
            last_command_label: None,
            device_status: DeviceStatus::default(),
            needs_device_refresh: true,
            // Skip startup animation — go straight to menu
            logcat: LogcatState::new(),
            logcat_save_active: false,
            logcat_save_path: String::new(),
            logcat_save_cursor: 0,
            logcat_save_filtered_only: false,
            logcat_save_format: crate::logcat::SaveFormat::Text,
            logcat_save_mode: LogcatSaveMode::PathInput,
            logcat_file_explorer: None,
            devtools: DevToolsState::new(),
            rom_flash: crate::rom_flash::RomFlashState::new(),
            theme: Theme::default(),
            theme_selector: ThemeSelector::default(),
        }
    }

    /// Check if the application should quit
    pub fn should_quit(&self) -> bool {
        !self.running
    }

    /// Check if we're in the result view
    pub fn is_showing_result(&self) -> bool {
        self.state == AppState::ShowResult
    }

    /// Check if we're in the menu
    pub fn is_in_menu(&self) -> bool {
        self.state == AppState::Menu
    }

    /// Check if we're in logcat view
    pub fn is_in_logcat(&self) -> bool {
        self.state == AppState::Logcat
    }

    /// Check if startup animation is complete
    pub fn is_startup_complete(&self) -> bool {
        self.state != AppState::Startup || self.effects.is_startup_complete()
    }

    /// Get the currently selected command
    pub fn get_selected_command(&self) -> MenuCommand {
        self.menu.get_selected_command()
    }

    /// Clear result state
    pub fn clear_results(&mut self) {
        self.command_result = None;
        self.command_error = None;
        self.scroll_position = 0;
        self.result_lines.clear();
        self.wrapped_lines.clear();
    }

    /// Set command result (success)
    pub fn set_result(&mut self, output: String) {
        self.command_result = Some(output.clone());
        self.result_lines = output.lines().map(|s| s.to_string()).collect();
        self.scroll_position = 0;
        self.reveal_counter = 0;
    }

    /// Set command error
    pub fn set_error(&mut self, error: String) {
        self.command_error = Some(error.clone());
        self.result_lines = error.lines().map(|s| s.to_string()).collect();
        self.scroll_position = 0;
        self.reveal_counter = 0;
    }

    /// Get total number of lines in current result
    pub fn total_result_lines(&self) -> usize {
        self.wrapped_lines.len()
    }

    /// Check if scrolling is available
    pub fn can_scroll(&self) -> bool {
        self.wrapped_lines.len() > 1
    }

    /// Update wrapped lines for current terminal width
    pub fn update_wrapped_lines(&mut self, max_width: usize) {
        if self.result_lines.is_empty() {
            self.wrapped_lines = vec!["No output".to_string()];
            return;
        }

        self.wrapped_lines = self
            .result_lines
            .iter()
            .flat_map(|line| droidkraft_core::utils::wrap_text(line, max_width))
            .collect();
    }
}