Skip to content

Commit 66ac30c

Browse files
unhappychoiceclaude
andcommitted
feat: implement new details dialog for session results
- Add DetailsDialog screen for viewing session and best record details - Display personal best records with color-coded comparisons - Show score differences and achievement indicators - Unified display format: "Score | CPM | Acc" across all metrics 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent ce8ce79 commit 66ac30c

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed

src/game/screens/details_dialog.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
use crate::storage::repositories::session_repository::SessionRepository;
2+
use crate::{models::GitRepository, Result};
3+
use crossterm::{
4+
cursor::{Hide, MoveTo, Show},
5+
event::{self, Event, KeyCode},
6+
execute,
7+
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
8+
terminal::{self, ClearType},
9+
};
10+
use std::io::{stdout, Write};
11+
12+
pub struct DetailsDialog;
13+
14+
impl DetailsDialog {
15+
pub fn show_details(
16+
session_result: &crate::models::SessionResult,
17+
repo_info: &Option<GitRepository>,
18+
) -> Result<()> {
19+
let mut stdout = stdout();
20+
21+
// Clear screen
22+
execute!(stdout, terminal::Clear(ClearType::All))?;
23+
execute!(stdout, MoveTo(0, 0))?;
24+
execute!(stdout, ResetColor)?;
25+
26+
let (terminal_width, _terminal_height) = terminal::size()?;
27+
let center_col = terminal_width / 2;
28+
let mut current_row = 3u16;
29+
30+
// Dialog title
31+
let title = "=== SESSION DETAILS ===";
32+
let title_col = center_col.saturating_sub(title.len() as u16 / 2);
33+
execute!(stdout, MoveTo(title_col, current_row))?;
34+
execute!(
35+
stdout,
36+
SetAttribute(Attribute::Bold),
37+
SetForegroundColor(Color::Cyan)
38+
)?;
39+
execute!(stdout, Print(title))?;
40+
execute!(stdout, ResetColor)?;
41+
current_row += 3;
42+
43+
// Best Records Section
44+
if let Ok(Some(best_records)) = SessionRepository::get_best_records_global() {
45+
let best_records_title = "BEST RECORDS";
46+
let title_col = center_col.saturating_sub(best_records_title.len() as u16 / 2);
47+
execute!(stdout, MoveTo(title_col, current_row))?;
48+
execute!(
49+
stdout,
50+
SetAttribute(Attribute::Bold),
51+
SetForegroundColor(Color::Yellow)
52+
)?;
53+
execute!(stdout, Print(best_records_title))?;
54+
execute!(stdout, ResetColor)?;
55+
current_row += 2;
56+
57+
let records = [
58+
("Today's Best", &best_records.todays_best),
59+
("Weekly Best", &best_records.weekly_best),
60+
("All time Best", &best_records.all_time_best),
61+
];
62+
63+
// Find the longest label for alignment
64+
let max_label_width = records
65+
.iter()
66+
.map(|(label, _)| label.len())
67+
.max()
68+
.unwrap_or(0);
69+
70+
for (label, record_data) in records.iter() {
71+
if let Some(record) = record_data {
72+
let is_new_pb = session_result.session_score > record.score;
73+
74+
// Create aligned record line with padding
75+
let record_content = format!(
76+
"Score {:.0} | CPM {:.0} | Acc {:.1}%",
77+
record.score, record.cpm, record.accuracy
78+
);
79+
80+
let prefix = if is_new_pb { "*** NEW PB! " } else { "" };
81+
82+
let full_line = format!(
83+
"{}{:>width$}: {}",
84+
prefix,
85+
label,
86+
record_content,
87+
width = max_label_width
88+
);
89+
90+
let line_col = center_col.saturating_sub(full_line.len() as u16 / 2);
91+
execute!(stdout, MoveTo(line_col, current_row))?;
92+
93+
if is_new_pb {
94+
execute!(stdout, SetForegroundColor(Color::Yellow))?;
95+
execute!(stdout, Print("*** NEW PB! "))?;
96+
execute!(stdout, SetForegroundColor(Color::White))?;
97+
execute!(
98+
stdout,
99+
Print(&format!("{:>width$}: ", label, width = max_label_width))
100+
)?;
101+
execute!(stdout, SetForegroundColor(Color::Green))?;
102+
} else if session_result.session_score >= record.score {
103+
execute!(stdout, SetForegroundColor(Color::White))?;
104+
execute!(
105+
stdout,
106+
Print(&format!("{:>width$}: ", label, width = max_label_width))
107+
)?;
108+
execute!(stdout, SetForegroundColor(Color::Green))?;
109+
} else {
110+
execute!(stdout, SetForegroundColor(Color::White))?;
111+
execute!(
112+
stdout,
113+
Print(&format!("{:>width$}: ", label, width = max_label_width))
114+
)?;
115+
execute!(stdout, SetForegroundColor(Color::Grey))?;
116+
}
117+
118+
execute!(stdout, Print(&record_content))?;
119+
120+
// Always show score difference
121+
let diff = session_result.session_score - record.score;
122+
if diff > 0.0 {
123+
execute!(stdout, SetForegroundColor(Color::Green))?;
124+
execute!(stdout, Print(&format!(" (+{:.0})", diff)))?;
125+
} else if diff < 0.0 {
126+
execute!(stdout, SetForegroundColor(Color::Red))?;
127+
execute!(stdout, Print(&format!(" ({:.0})", diff)))?; // diff is already negative
128+
}
129+
130+
execute!(stdout, ResetColor)?;
131+
current_row += 1;
132+
} else {
133+
let no_record_line = format!(
134+
"{:>width$}: No previous record",
135+
label,
136+
width = max_label_width
137+
);
138+
let line_col = center_col.saturating_sub(no_record_line.len() as u16 / 2);
139+
execute!(stdout, MoveTo(line_col, current_row))?;
140+
execute!(stdout, SetForegroundColor(Color::DarkGrey))?;
141+
execute!(stdout, Print(&no_record_line))?;
142+
execute!(stdout, ResetColor)?;
143+
current_row += 1;
144+
}
145+
}
146+
current_row += 2;
147+
}
148+
149+
// Stage Results Section
150+
if !session_result.stage_results.is_empty() {
151+
let stage_label = if let Some(repo) = repo_info {
152+
format!(
153+
"Stage Results: [{}/{}]",
154+
repo.user_name, repo.repository_name
155+
)
156+
} else {
157+
"Stage Results:".to_string()
158+
};
159+
let stage_label_col = center_col.saturating_sub(stage_label.len() as u16 / 2);
160+
execute!(stdout, MoveTo(stage_label_col, current_row))?;
161+
execute!(
162+
stdout,
163+
SetAttribute(Attribute::Bold),
164+
SetForegroundColor(Color::Cyan)
165+
)?;
166+
execute!(stdout, Print(&stage_label))?;
167+
execute!(stdout, ResetColor)?;
168+
current_row += 2;
169+
170+
// Calculate maximum stage name width for alignment
171+
let max_stage_name_width = session_result
172+
.stage_results
173+
.iter()
174+
.enumerate()
175+
.map(|(i, stage)| {
176+
if !stage.challenge_path.is_empty() {
177+
stage.challenge_path.len()
178+
} else {
179+
format!("Stage {}", i + 1).len()
180+
}
181+
})
182+
.max()
183+
.unwrap_or(20);
184+
185+
for (i, stage_result) in session_result.stage_results.iter().enumerate() {
186+
let stage_name = if !stage_result.challenge_path.is_empty() {
187+
stage_result.challenge_path.clone()
188+
} else {
189+
format!("Stage {}", i + 1)
190+
};
191+
192+
let stage_content = format!(
193+
"Score {:.0} | CPM {:.0} | Acc {:.1}%",
194+
stage_result.challenge_score, stage_result.cpm, stage_result.accuracy
195+
);
196+
let result_line = format!(
197+
"{:>width$}: {}",
198+
stage_name,
199+
stage_content,
200+
width = max_stage_name_width
201+
);
202+
let result_col = center_col.saturating_sub(result_line.len() as u16 / 2);
203+
execute!(stdout, MoveTo(result_col, current_row))?;
204+
execute!(stdout, SetForegroundColor(Color::White))?;
205+
execute!(
206+
stdout,
207+
Print(&format!(
208+
"{:>width$}: ",
209+
stage_name,
210+
width = max_stage_name_width
211+
))
212+
)?;
213+
execute!(stdout, SetForegroundColor(Color::White))?;
214+
execute!(stdout, Print(&stage_content))?;
215+
execute!(stdout, ResetColor)?;
216+
current_row += 1;
217+
}
218+
current_row += 2;
219+
}
220+
221+
// Instructions
222+
let instruction = "[ESC] Return";
223+
let instruction_col = center_col.saturating_sub(instruction.len() as u16 / 2);
224+
execute!(stdout, MoveTo(instruction_col, current_row))?;
225+
execute!(stdout, SetForegroundColor(Color::Red))?;
226+
execute!(stdout, Print("[ESC]"))?;
227+
execute!(stdout, SetForegroundColor(Color::White))?;
228+
execute!(stdout, Print(" Return"))?;
229+
execute!(stdout, ResetColor)?;
230+
231+
stdout.flush()?;
232+
233+
// Disable cursor visibility
234+
execute!(stdout, Hide)?;
235+
236+
// Wait for ESC key only
237+
loop {
238+
if event::poll(std::time::Duration::from_millis(100))? {
239+
if let Event::Key(key_event) = event::read()? {
240+
if key_event.code == KeyCode::Esc {
241+
break;
242+
}
243+
}
244+
}
245+
}
246+
247+
// Restore cursor visibility
248+
execute!(stdout, Show)?;
249+
250+
Ok(())
251+
}
252+
}

0 commit comments

Comments
 (0)