trackWork 0.15.0

A terminal-based time tracking application for managing work sessions
mod app;
mod config;
mod cursor;
mod dashboard;
mod db;
mod integrations;
mod models;
mod passphrase;
mod settings;
mod tasks;
mod termcolor;
mod triggers;
mod ui;
mod update_check;
mod whats_new;

use anyhow::Result;
use crossterm::{
    event::{
        self, DisableMouseCapture, EnableMouseCapture, Event, KeyEventKind,
        KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
    },
    execute,
    terminal::{
        disable_raw_mode, enable_raw_mode, supports_keyboard_enhancement, EnterAlternateScreen,
        LeaveAlternateScreen,
    },
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io;
use std::time::Duration;

use app::App;

fn main() -> Result<()> {
    // Setup terminal
    enable_raw_mode()?;
    // Ask the terminal for its real 16-color palette before any other I/O, so
    // the shimmer/breathe animations can match the user's theme.
    let term_palette = termcolor::query_palette();
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    // Ask the terminal to disambiguate keys (e.g. report Shift with arrow keys).
    // Many macOS terminals only send modifiers for arrow keys under this protocol.
    // Unsupported terminals (e.g. Terminal.app) silently keep legacy behavior.
    let keyboard_enhanced = matches!(supports_keyboard_enhancement(), Ok(true));
    if keyboard_enhanced {
        execute!(
            stdout,
            PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES)
        )?;
    }
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    // Create app
    let db_path = dirs::home_dir()
        .unwrap_or_else(|| std::path::PathBuf::from("."))
        .join(".timetrack.db");
    let mut app = App::new(db_path.to_str().unwrap())?;
    app.term_palette = term_palette;
    // Kick off the crates.io "newer release?" probe in the background — the
    // top bar will pick up the result once (and if) it lands.
    app.available_update = update_check::spawn_check();

    // If encrypted secrets file exists, prompt for passphrase before proceeding
    if app.secrets.has_encrypted_file() {
        app.input_mode = app::InputMode::PassphrasePrompt {
            passphrase: String::new(),
            cursor_pos: 0,
            error_message: None,
            confirm_delete: false,
        };
        // check_version will be called after successful unlock
    } else {
        // Check version and show What's New if needed
        app.check_version()?;
    }

    // Run app
    let res = run_app(&mut terminal, &mut app);

    // Restore terminal
    disable_raw_mode()?;
    if keyboard_enhanced {
        execute!(terminal.backend_mut(), PopKeyboardEnhancementFlags)?;
    }
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;

    if let Err(err) = res {
        println!("Error: {:?}", err);
    }

    Ok(())
}

fn run_app<B: ratatui::backend::Backend>(terminal: &mut Terminal<B>, app: &mut App) -> Result<()> {
    loop {
        // Auto-close any timer that crossed midnight while we were idle.
        let _ = app.tick();
        terminal.draw(|f| dashboard::draw_dashboard(f, app))?;

        // Poll for events with a timeout to allow UI refresh
        if event::poll(Duration::from_millis(100))? {
            if let Event::Key(key) = event::read()? {
                // Under the keyboard-enhancement protocol the terminal also emits
                // Release/Repeat events; only act on presses to avoid double-handling.
                if key.kind != KeyEventKind::Press {
                    continue;
                }
                // Handle input via the dashboard module
                let should_quit = dashboard::handle_input(app, key)?;
                if should_quit {
                    return Ok(());
                }
            }
        }
    }
}