use ratatui::widgets::{ListState, ScrollbarState};
use std::collections::HashMap;
use std::time::Instant;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Screen {
MainMenu,
DotfileSelection,
StorageSetup,
SyncWithRemote,
ManageProfiles,
ProfileSelection, ManagePackages,
Settings,
}
#[derive(Debug, Clone)]
pub struct GitHubAuthState {
pub setup_mode: SetupMode, pub mode_selection_index: usize,
pub token_input: crate::utils::TextInput,
pub repo_name_input: crate::utils::TextInput,
pub repo_location_input: crate::utils::TextInput,
pub is_private: bool,
pub step: GitHubAuthStep,
pub error_message: Option<String>,
pub status_message: Option<String>,
pub help_scroll: usize,
pub input_focused: bool, pub focused_field: GitHubAuthField, pub is_editing_token: bool, pub repo_already_configured: bool, pub setup_data: Option<GitHubSetupData>,
pub local_repo_path_input: crate::utils::TextInput, #[allow(dead_code)]
pub local_step: LocalSetupStep, }
#[derive(Debug, Clone)]
pub struct GitHubSetupData {
pub token: String,
pub repo_name: String,
pub username: Option<String>,
pub repo_exists: Option<bool>,
pub is_private: bool, pub delay_until: Option<std::time::Instant>, pub is_new_repo: bool, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GitHubAuthField {
Token,
RepoName,
RepoLocation,
IsPrivate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GitHubAuthStep {
Input,
Processing,
SetupStep(GitHubSetupStep),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GitHubSetupStep {
Connecting,
ValidatingToken,
CheckingRepo,
CloningRepo,
CreatingRepo,
InitializingRepo,
DiscoveringProfiles,
Complete,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SetupMode {
#[default]
Choosing,
GitHub,
Local,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum LocalSetupStep {
#[default]
Input,
#[allow(dead_code)]
Validating,
#[allow(dead_code)]
Complete,
}
impl Default for GitHubAuthState {
fn default() -> Self {
let default_repo_path = dirs::home_dir()
.unwrap_or_else(|| std::path::PathBuf::from("."))
.join(".config")
.join("dotstate")
.join("storage");
Self {
setup_mode: SetupMode::default(),
mode_selection_index: 0,
token_input: crate::utils::TextInput::new(),
repo_name_input: crate::utils::TextInput::with_text(crate::config::default_repo_name()),
repo_location_input: crate::utils::TextInput::with_text(
default_repo_path.to_string_lossy().to_string(),
),
is_private: true, step: GitHubAuthStep::Input,
error_message: None,
status_message: None,
help_scroll: 0,
input_focused: true, focused_field: GitHubAuthField::Token,
is_editing_token: false,
repo_already_configured: false,
setup_data: None,
local_repo_path_input: crate::utils::TextInput::with_text(
default_repo_path.to_string_lossy().to_string(),
),
local_step: LocalSetupStep::Input,
}
}
}
#[derive(Debug, Clone)]
pub struct SyncWithRemoteState {
pub changed_files: Vec<String>,
pub is_syncing: bool,
pub sync_progress: Option<String>, pub sync_result: Option<String>, pub show_result_popup: bool, pub pulled_changes_count: Option<usize>, pub list_state: ListState,
pub scrollbar_state: ScrollbarState,
pub diff_content: Option<String>, pub preview_scroll: usize, pub result_scroll: u16, pub git_status: Option<crate::services::git_service::GitStatus>, }
impl Default for SyncWithRemoteState {
fn default() -> Self {
Self {
changed_files: Vec::new(),
is_syncing: false,
sync_progress: None,
sync_result: None,
show_result_popup: false,
pulled_changes_count: None,
list_state: ListState::default(),
scrollbar_state: ScrollbarState::new(0),
diff_content: None,
preview_scroll: 0,
result_scroll: 0,
git_status: None,
}
}
}
#[derive(Debug, Default)]
pub struct ProfileSelectionState {
pub profiles: Vec<String>, pub list_state: ListState,
#[allow(dead_code)] pub selected_profile: Option<String>, pub show_exit_warning: bool, pub show_create_popup: bool, pub create_name_input: crate::utils::TextInput, }
#[derive(Debug)]
pub struct PackageManagerState {
pub list_state: ListState,
pub packages: Vec<crate::utils::profile_manifest::Package>, pub popup_type: PackagePopupType,
pub is_checking: bool,
pub checking_index: Option<usize>,
pub package_statuses: Vec<PackageStatus>, pub checking_delay_until: Option<std::time::Instant>, pub installation_step: InstallationStep,
pub installation_output: Vec<String>, pub installation_output_scroll: u16, pub installation_delay_until: Option<std::time::Instant>, pub add_name_input: crate::utils::TextInput,
pub add_description_input: crate::utils::TextInput,
pub add_manager: Option<crate::utils::profile_manifest::PackageManager>,
pub add_manager_selected: usize, pub add_package_name_input: crate::utils::TextInput,
pub add_binary_name_input: crate::utils::TextInput,
pub add_install_command_input: crate::utils::TextInput, pub add_existence_check_input: crate::utils::TextInput, pub add_manager_check_input: crate::utils::TextInput, pub add_is_custom: bool, pub add_focused_field: AddPackageField,
pub add_editing_index: Option<usize>, pub add_validation_error: Option<String>, pub newly_added_index: Option<usize>, pub available_managers: Vec<crate::utils::profile_manifest::PackageManager>, pub manager_list_state: ListState, pub delete_confirm_input: crate::utils::TextInput,
pub delete_index: Option<usize>,
pub cache: crate::utils::package_cache::PackageCache,
pub active_profile: String,
pub import_available_sources: Vec<crate::utils::DiscoverySource>,
pub import_active_tab: usize,
pub import_focus: ImportFocus,
pub import_source_cache: HashMap<crate::utils::DiscoverySource, ImportSourceCache>,
pub import_selected: std::collections::HashSet<usize>, pub import_filter: crate::utils::TextInput,
pub import_list_state: ListState,
pub import_loading: bool,
pub import_spinner_tick: usize, pub import_discovery_rx: Option<std::sync::mpsc::Receiver<crate::utils::DiscoveryStatus>>, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PackagePopupType {
None,
Add,
Edit,
Delete,
InstallMissing, Import, }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ImportFocus {
Tabs,
#[default]
Filter,
List,
}
#[derive(Debug)]
pub struct ImportSourceCache {
pub packages: Vec<crate::utils::DiscoveredPackage>,
pub discovered_at: Instant,
pub selected: std::collections::HashSet<usize>,
}
#[derive(Debug, Clone)]
pub enum PackageStatus {
Unknown,
Installed,
NotInstalled,
Error(String), }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AddPackageField {
Name,
Description,
Manager,
PackageName, BinaryName,
InstallCommand, ExistenceCheck, ManagerCheck, }
impl Default for PackageManagerState {
fn default() -> Self {
Self {
list_state: ListState::default(),
packages: Vec::new(),
popup_type: PackagePopupType::None,
is_checking: false,
checking_index: None,
package_statuses: Vec::new(),
add_name_input: crate::utils::TextInput::new(),
add_description_input: crate::utils::TextInput::new(),
add_manager: None,
add_manager_selected: 0,
add_package_name_input: crate::utils::TextInput::new(),
add_binary_name_input: crate::utils::TextInput::new(),
add_install_command_input: crate::utils::TextInput::new(),
add_existence_check_input: crate::utils::TextInput::new(),
add_manager_check_input: crate::utils::TextInput::new(),
add_is_custom: false,
add_focused_field: AddPackageField::Name,
add_editing_index: None,
add_validation_error: None,
newly_added_index: None,
available_managers: Vec::new(),
manager_list_state: ListState::default(),
delete_confirm_input: crate::utils::TextInput::new(),
delete_index: None,
checking_delay_until: None,
installation_step: InstallationStep::NotStarted,
installation_output: Vec::new(),
installation_output_scroll: 0,
installation_delay_until: None,
cache: crate::utils::package_cache::PackageCache::default(),
active_profile: String::new(),
import_available_sources: Vec::new(),
import_active_tab: 0,
import_focus: ImportFocus::default(),
import_source_cache: HashMap::new(),
import_selected: std::collections::HashSet::new(),
import_filter: crate::utils::TextInput::new(),
import_list_state: ListState::default(),
import_loading: false,
import_spinner_tick: 0,
import_discovery_rx: None,
}
}
}
impl PackageManagerState {
#[must_use]
pub fn import_active_source(&self) -> Option<crate::utils::DiscoverySource> {
self.import_available_sources
.get(self.import_active_tab)
.copied()
}
#[must_use]
pub fn import_current_packages(&self) -> &[crate::utils::DiscoveredPackage] {
self.import_active_source()
.and_then(|s| self.import_source_cache.get(&s))
.map_or(&[], |c| c.packages.as_slice())
}
#[must_use]
pub fn import_cache_valid(&self, max_age_secs: u64) -> bool {
self.import_active_source()
.and_then(|s| self.import_source_cache.get(&s))
.is_some_and(|c| {
c.discovered_at.elapsed().as_secs() < max_age_secs && !c.packages.is_empty()
})
}
}
#[derive(Debug)]
pub enum InstallationStep {
NotStarted,
Installing {
package_index: usize,
package_name: String,
total_packages: usize,
packages_to_install: Vec<usize>, installed: Vec<usize>, failed: Vec<(usize, String)>, status_rx: Option<std::sync::mpsc::Receiver<InstallationStatus>>, },
Complete {
installed: Vec<usize>,
failed: Vec<(usize, String)>, },
}
#[derive(Debug, Clone)]
pub enum InstallationStatus {
Output(String), Complete {
success: bool,
error: Option<String>,
}, }
#[derive(Debug)]
pub struct UiState {
pub current_screen: Screen,
pub selected_index: usize,
pub has_changes_to_push: bool, pub git_status: Option<crate::services::git_service::GitStatus>, pub profile_selection: ProfileSelectionState,
pub input_mode_active: bool,
pub show_help_overlay: bool,
}
impl Default for UiState {
fn default() -> Self {
Self::new()
}
}
impl UiState {
#[must_use]
pub fn new() -> Self {
Self {
current_screen: Screen::MainMenu,
selected_index: 0,
has_changes_to_push: false,
git_status: None,
profile_selection: ProfileSelectionState::default(),
input_mode_active: false,
show_help_overlay: false,
}
}
}