use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::Paragraph;
use ratatui::Frame;
use crate::engine::models::{TestReport, TransferStats};
use crate::tui::theme::Theme;
use crate::utils::format::{format_bytes, format_millis, split_bps};
pub fn render(frame: &mut Frame, area: Rect, theme: &Theme, report: &TestReport) {
if area.height < 6 {
return;
}
let colors = &theme.colors;
let [title_area, _, speeds_area, _, latency_area, totals_area] = Layout::vertical([
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(3),
Constraint::Length(1),
Constraint::Length(1),
Constraint::Length(1),
])
.areas(area);
frame.render_widget(
Paragraph::new(
Line::from(Span::styled(
"✓ TEST COMPLETE",
Style::default()
.fg(colors.success)
.add_modifier(Modifier::BOLD),
))
.centered(),
),
title_area,
);
let [download_area, upload_area] =
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
.areas(speeds_area);
headline(
frame,
download_area,
"↓ DOWNLOAD",
&report.download,
colors.download,
colors.subtext,
);
headline(
frame,
upload_area,
"↑ UPLOAD",
&report.upload,
colors.upload,
colors.subtext,
);
let latency = &report.latency;
frame.render_widget(
Paragraph::new(
Line::from(vec![
Span::styled("ping ", Style::default().fg(colors.subtext)),
Span::styled(
format_millis(latency.average_ms),
Style::default().fg(colors.latency),
),
Span::styled(" jitter ", Style::default().fg(colors.subtext)),
Span::styled(
format_millis(latency.jitter_ms),
Style::default().fg(colors.latency),
),
Span::styled(" loss ", Style::default().fg(colors.subtext)),
Span::styled(
format!("{:.0}%", latency.packet_loss_pct),
Style::default().fg(colors.latency),
),
])
.centered(),
),
latency_area,
);
let transferred = report.download.bytes + report.upload.bytes;
frame.render_widget(
Paragraph::new(
Line::from(vec![
Span::styled(
format!("{} ", report.server_name),
Style::default().fg(colors.muted),
),
Span::styled(
format!("{} transferred", format_bytes(transferred)),
Style::default().fg(colors.muted),
),
])
.centered(),
),
totals_area,
);
}
fn headline(
frame: &mut Frame,
area: Rect,
label: &str,
stats: &TransferStats,
color: Color,
subtext: Color,
) {
let (value, unit) = split_bps(stats.average_bps);
let (peak, peak_unit) = split_bps(stats.peak_bps);
let lines = vec![
Line::from(Span::styled(label.to_owned(), Style::default().fg(subtext))).centered(),
Line::from(vec![
Span::styled(
value,
Style::default().fg(color).add_modifier(Modifier::BOLD),
),
Span::styled(format!(" {unit}"), Style::default().fg(subtext)),
])
.centered(),
Line::from(Span::styled(
format!("peak {peak} {peak_unit}"),
Style::default().fg(subtext),
))
.centered(),
];
frame.render_widget(Paragraph::new(lines), area);
}