Continue into another drill after completing one
This commit is contained in:
@@ -246,7 +246,13 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.last_result = Some(result);
|
self.last_result = Some(result);
|
||||||
|
|
||||||
|
// Adaptive mode auto-continues to next lesson (like keybr.com)
|
||||||
|
if self.lesson_mode == LessonMode::Adaptive {
|
||||||
|
self.start_lesson();
|
||||||
|
} else {
|
||||||
self.screen = AppScreen::LessonResult;
|
self.screen = AppScreen::LessonResult;
|
||||||
|
}
|
||||||
|
|
||||||
self.save_data();
|
self.save_data();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -207,7 +207,8 @@ fn handle_lesson_key(app: &mut App, key: KeyEvent) {
|
|||||||
match key.code {
|
match key.code {
|
||||||
KeyCode::Esc => {
|
KeyCode::Esc => {
|
||||||
let has_progress = app.lesson.as_ref().is_some_and(|l| l.cursor > 0);
|
let has_progress = app.lesson.as_ref().is_some_and(|l| l.cursor > 0);
|
||||||
if has_progress {
|
if has_progress && app.lesson_mode != LessonMode::Adaptive {
|
||||||
|
// Non-adaptive: show result screen for partial lesson
|
||||||
if let Some(ref lesson) = app.lesson {
|
if let Some(ref lesson) = app.lesson {
|
||||||
let result = LessonResult::from_lesson(lesson, &app.lesson_events, app.lesson_mode.as_str());
|
let result = LessonResult::from_lesson(lesson, &app.lesson_events, app.lesson_mode.as_str());
|
||||||
app.last_result = Some(result);
|
app.last_result = Some(result);
|
||||||
@@ -488,7 +489,7 @@ fn render_lesson(frame: &mut ratatui::Frame, app: &App) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(sidebar_area) = app_layout.sidebar {
|
if let Some(sidebar_area) = app_layout.sidebar {
|
||||||
let sidebar = StatsSidebar::new(lesson, app.theme);
|
let sidebar = StatsSidebar::new(lesson, app.last_result.as_ref(), app.theme);
|
||||||
frame.render_widget(sidebar, sidebar_area);
|
frame.render_widget(sidebar, sidebar_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
use ratatui::buffer::Buffer;
|
use ratatui::buffer::Buffer;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::{Constraint, Direction, Layout, Rect};
|
||||||
use ratatui::style::Style;
|
use ratatui::style::Style;
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::{Block, Paragraph, Widget};
|
use ratatui::widgets::{Block, Paragraph, Widget};
|
||||||
|
|
||||||
use crate::session::lesson::LessonState;
|
use crate::session::lesson::LessonState;
|
||||||
|
use crate::session::result::LessonResult;
|
||||||
use crate::ui::theme::Theme;
|
use crate::ui::theme::Theme;
|
||||||
|
|
||||||
pub struct StatsSidebar<'a> {
|
pub struct StatsSidebar<'a> {
|
||||||
lesson: &'a LessonState,
|
lesson: &'a LessonState,
|
||||||
|
last_result: Option<&'a LessonResult>,
|
||||||
theme: &'a Theme,
|
theme: &'a Theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StatsSidebar<'a> {
|
impl<'a> StatsSidebar<'a> {
|
||||||
pub fn new(lesson: &'a LessonState, theme: &'a Theme) -> Self {
|
pub fn new(lesson: &'a LessonState, last_result: Option<&'a LessonResult>, theme: &'a Theme) -> Self {
|
||||||
Self { lesson, theme }
|
Self { lesson, last_result, theme }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,6 +24,23 @@ impl Widget for StatsSidebar<'_> {
|
|||||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
let colors = &self.theme.colors;
|
let colors = &self.theme.colors;
|
||||||
|
|
||||||
|
let has_last = self.last_result.is_some();
|
||||||
|
|
||||||
|
// Split sidebar into current stats and last lesson sections
|
||||||
|
let sections = if has_last {
|
||||||
|
Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(10), Constraint::Min(10)])
|
||||||
|
.split(area)
|
||||||
|
} else {
|
||||||
|
Layout::default()
|
||||||
|
.direction(Direction::Vertical)
|
||||||
|
.constraints([Constraint::Min(10), Constraint::Length(0)])
|
||||||
|
.split(area)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Current lesson stats
|
||||||
|
{
|
||||||
let wpm = self.lesson.wpm();
|
let wpm = self.lesson.wpm();
|
||||||
let accuracy = self.lesson.accuracy();
|
let accuracy = self.lesson.accuracy();
|
||||||
let progress = self.lesson.progress() * 100.0;
|
let progress = self.lesson.progress() * 100.0;
|
||||||
@@ -39,13 +58,13 @@ impl Widget for StatsSidebar<'_> {
|
|||||||
let lines = vec![
|
let lines = vec![
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("WPM: ", Style::default().fg(colors.fg())),
|
Span::styled("WPM: ", Style::default().fg(colors.fg())),
|
||||||
Span::styled(&*wpm_str, Style::default().fg(colors.accent())),
|
Span::styled(wpm_str, Style::default().fg(colors.accent())),
|
||||||
]),
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("Accuracy: ", Style::default().fg(colors.fg())),
|
Span::styled("Accuracy: ", Style::default().fg(colors.fg())),
|
||||||
Span::styled(
|
Span::styled(
|
||||||
&*acc_str,
|
acc_str,
|
||||||
Style::default().fg(if accuracy >= 95.0 {
|
Style::default().fg(if accuracy >= 95.0 {
|
||||||
colors.success()
|
colors.success()
|
||||||
} else if accuracy >= 85.0 {
|
} else if accuracy >= 85.0 {
|
||||||
@@ -58,21 +77,21 @@ impl Widget for StatsSidebar<'_> {
|
|||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("Progress: ", Style::default().fg(colors.fg())),
|
Span::styled("Progress: ", Style::default().fg(colors.fg())),
|
||||||
Span::styled(&*prog_str, Style::default().fg(colors.accent())),
|
Span::styled(prog_str, Style::default().fg(colors.accent())),
|
||||||
]),
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("Correct: ", Style::default().fg(colors.fg())),
|
Span::styled("Correct: ", Style::default().fg(colors.fg())),
|
||||||
Span::styled(&*correct_str, Style::default().fg(colors.success())),
|
Span::styled(correct_str, Style::default().fg(colors.success())),
|
||||||
]),
|
]),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("Errors: ", Style::default().fg(colors.fg())),
|
Span::styled("Errors: ", Style::default().fg(colors.fg())),
|
||||||
Span::styled(&*incorrect_str, Style::default().fg(colors.error())),
|
Span::styled(incorrect_str, Style::default().fg(colors.error())),
|
||||||
]),
|
]),
|
||||||
Line::from(""),
|
Line::from(""),
|
||||||
Line::from(vec![
|
Line::from(vec![
|
||||||
Span::styled("Time: ", Style::default().fg(colors.fg())),
|
Span::styled("Time: ", Style::default().fg(colors.fg())),
|
||||||
Span::styled(&*elapsed_str, Style::default().fg(colors.fg())),
|
Span::styled(elapsed_str, Style::default().fg(colors.fg())),
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -82,6 +101,59 @@ impl Widget for StatsSidebar<'_> {
|
|||||||
.style(Style::default().bg(colors.bg()));
|
.style(Style::default().bg(colors.bg()));
|
||||||
|
|
||||||
let paragraph = Paragraph::new(lines).block(block);
|
let paragraph = Paragraph::new(lines).block(block);
|
||||||
paragraph.render(area, buf);
|
paragraph.render(sections[0], buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last lesson stats
|
||||||
|
if let Some(last) = self.last_result {
|
||||||
|
let wpm_str = format!("{:.0}", last.wpm);
|
||||||
|
let acc_str = format!("{:.1}%", last.accuracy);
|
||||||
|
let chars_str = format!("{}", last.total_chars);
|
||||||
|
let time_str = format!("{:.1}s", last.elapsed_secs);
|
||||||
|
let errors_str = format!("{}", last.incorrect);
|
||||||
|
|
||||||
|
let lines = vec![
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled("WPM: ", Style::default().fg(colors.fg())),
|
||||||
|
Span::styled(wpm_str, Style::default().fg(colors.accent())),
|
||||||
|
]),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled("Accuracy: ", Style::default().fg(colors.fg())),
|
||||||
|
Span::styled(
|
||||||
|
acc_str,
|
||||||
|
Style::default().fg(if last.accuracy >= 95.0 {
|
||||||
|
colors.success()
|
||||||
|
} else if last.accuracy >= 85.0 {
|
||||||
|
colors.warning()
|
||||||
|
} else {
|
||||||
|
colors.error()
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled("Chars: ", Style::default().fg(colors.fg())),
|
||||||
|
Span::styled(chars_str, Style::default().fg(colors.fg())),
|
||||||
|
]),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled("Errors: ", Style::default().fg(colors.fg())),
|
||||||
|
Span::styled(errors_str, Style::default().fg(colors.error())),
|
||||||
|
]),
|
||||||
|
Line::from(""),
|
||||||
|
Line::from(vec![
|
||||||
|
Span::styled("Time: ", Style::default().fg(colors.fg())),
|
||||||
|
Span::styled(time_str, Style::default().fg(colors.fg())),
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
|
||||||
|
let block = Block::bordered()
|
||||||
|
.title(" Last Lesson ")
|
||||||
|
.border_style(Style::default().fg(colors.border()))
|
||||||
|
.style(Style::default().bg(colors.bg()));
|
||||||
|
|
||||||
|
let paragraph = Paragraph::new(lines).block(block);
|
||||||
|
paragraph.render(sections[1], buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user