Implement Phase 1: Foundation and cache system

- Add Cargo project with TUI and async dependencies
- Implement cache-only architecture for low bandwidth operation
- Add file scanner with media type detection
- Create two-panel TUI layout (file tree and status)
- Add config file support for scan path management
- Implement XDG-compliant cache and config directories
- Add Gitea CI/CD workflow for automated releases
This commit is contained in:
2025-12-06 12:32:17 +01:00
parent a1fd8eb6e5
commit 7ce264fd96
11 changed files with 1007 additions and 0 deletions

92
src/state/mod.rs Normal file
View File

@@ -0,0 +1,92 @@
use crate::cache::{Cache, FileTreeNode};
use crate::config::Config;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlayerState {
Stopped,
Playing,
Paused,
}
pub struct AppState {
pub cache: Cache,
pub config: Config,
pub selected_index: usize,
pub scroll_offset: usize,
pub player_state: PlayerState,
pub current_file: Option<PathBuf>,
pub current_position: f64,
pub current_duration: f64,
pub volume: i64,
pub should_quit: bool,
pub flattened_items: Vec<FlattenedItem>,
}
#[derive(Debug, Clone)]
pub struct FlattenedItem {
pub node: FileTreeNode,
pub depth: usize,
pub is_expanded: bool,
}
impl AppState {
pub fn new(cache: Cache, config: Config) -> Self {
let flattened_items = flatten_tree(&cache.file_tree, 0);
Self {
cache,
config,
selected_index: 0,
scroll_offset: 0,
player_state: PlayerState::Stopped,
current_file: None,
current_position: 0.0,
current_duration: 0.0,
volume: 100,
should_quit: false,
flattened_items,
}
}
pub fn move_selection_up(&mut self) {
if self.selected_index > 0 {
self.selected_index -= 1;
}
}
pub fn move_selection_down(&mut self) {
if self.selected_index < self.flattened_items.len().saturating_sub(1) {
self.selected_index += 1;
}
}
pub fn get_selected_item(&self) -> Option<&FlattenedItem> {
self.flattened_items.get(self.selected_index)
}
pub fn refresh_flattened_items(&mut self) {
self.flattened_items = flatten_tree(&self.cache.file_tree, 0);
if self.selected_index >= self.flattened_items.len() {
self.selected_index = self.flattened_items.len().saturating_sub(1);
}
}
}
fn flatten_tree(nodes: &[FileTreeNode], depth: usize) -> Vec<FlattenedItem> {
let mut result = Vec::new();
for node in nodes {
result.push(FlattenedItem {
node: node.clone(),
depth,
is_expanded: true, // For now, all directories are expanded
});
if node.is_dir && !node.children.is_empty() {
result.extend(flatten_tree(&node.children, depth + 1));
}
}
result
}